diff --git a/src/EditorManager.js b/src/EditorManager.js new file mode 100644 index 0000000..468c3f3 --- /dev/null +++ b/src/EditorManager.js @@ -0,0 +1,67 @@ +export class EditorManager { + constructor(snippetManager) { + this.snippetManager = snippetManager; + this.editor = null; + this.timeoutId = null; + } + + setEditor(editor) { + this.editor = editor; + this.setupEditorEvents(); + } + + setupEditorEvents() { + this.editor.onDidChangeModelContent(() => { + // Skip event handling if in read-only mode + if (this.snippetManager.readOnlyMode) return; + + this.snippetManager.hasUnsavedChanges = true; + this.snippetManager.uiManager.updateSaveButton(true); + + // Auto-save to draft + if (this.timeoutId) clearTimeout(this.timeoutId); + this.timeoutId = setTimeout(() => { + this.snippetManager.saveDraft(); + this.updateVisualization(); + }, 1000); + }); + } + + revertChanges() { + const snippet = this.snippetManager.snippets.find( + s => s.id === this.snippetManager.currentSnippetId + ); + this.editor.setValue(JSON.stringify(snippet.content, null, 2)); + } + + updateVisualization() { + try { + const value = this.editor.getValue(); + const content = JSON.parse(value); + this.snippetManager.visualizationManager.updateVisualization(content); + } catch (e) { + console.error('Invalid JSON:', e); + } + } + + updateReadOnlyState(readOnly) { + if (!this.editor) { + throw new Error('Editor not initialized'); + } + this.editor.updateOptions({ readOnly }); + } + + getValue() { + if (!this.editor) { + throw new Error('Editor not initialized'); + } + return this.editor.getValue(); + } + + setValue(content) { + if (!this.editor) { + throw new Error('Editor not initialized'); + } + this.editor.setValue(JSON.stringify(content, null, 2)); + } +} diff --git a/src/PanelResizer.js b/src/PanelResizer.js index bc14bdc..64ed4cf 100644 --- a/src/PanelResizer.js +++ b/src/PanelResizer.js @@ -102,15 +102,16 @@ export class PanelResizer { document.removeEventListener('mouseup', this.handleDragEnd); // Trigger Monaco editor resize - if (window.editor) { - window.editor.layout(); + if (this.snippetManager.editorManager.editor) { + this.snippetManager.editorManager.editor.layout(); } // Update visualization after resize is complete - if (this.snippetManager && typeof this.snippetManager.updateVisualization === 'function') { - // Get the current editor value and update visualization - const editorValue = this.snippetManager.editor.getValue(); - this.snippetManager.updateVisualization(editorValue); + try { + const editorValue = this.snippetManager.editorManager.getValue(); + this.snippetManager.visualizationManager.updateVisualization(editorValue); + } catch (e) { + console.error('Error updating visualization after resize:', e); } } diff --git a/src/SnippetManager.js b/src/SnippetManager.js index 25bda4f..86640a2 100644 --- a/src/SnippetManager.js +++ b/src/SnippetManager.js @@ -1,13 +1,29 @@ -import { defaultSnippets } from './config.js'; +import { StorageManager } from './StorageManager.js'; +import { UIManager } from './UIManager.js'; +import { VisualizationManager } from './VisualizationManager.js'; +import { EditorManager } from './EditorManager.js'; export class SnippetManager { constructor() { + this.storageManager = new StorageManager(); + this.uiManager = new UIManager(this); + this.visualizationManager = new VisualizationManager(); + this.editorManager = new EditorManager(this); + this.currentSnippetId = null; this.hasUnsavedChanges = false; this.isDraftVersion = false; this.readOnlyMode = false; - this.loadSnippets(); - this.setupUI(); + + this.snippets = this.storageManager.loadSnippets(); + this.uiManager.renderSnippetList(this.snippets, this.currentSnippetId); + } + + setEditor(editor) { + this.editorManager.setEditor(editor); + if (this.snippets.length > 0) { + this.loadSnippet(this.snippets[0].id); + } } hasDraftChanges(id) { @@ -15,33 +31,6 @@ export class SnippetManager { return snippet && snippet.draft !== undefined; } - loadSnippets() { - const stored = localStorage.getItem('vegaSnippets'); - this.snippets = stored ? JSON.parse(stored) : defaultSnippets; - if (!stored) { - this.saveToStorage(); - } - } - - saveToStorage() { - localStorage.setItem('vegaSnippets', JSON.stringify(this.snippets)); - } - - renderSnippetList() { - const container = document.getElementById('snippet-list'); - container.innerHTML = ''; - - this.snippets.forEach(snippet => { - const div = document.createElement('div'); - div.className = `snippet-item ${snippet.id === this.currentSnippetId ? 'active' : ''}`; - const hasChanges = this.hasDraftChanges(snippet.id); - const indicator = hasChanges ? '🟡' : '🟢'; - div.textContent = `${indicator} ${snippet.name}`; - div.onclick = () => this.loadSnippet(snippet.id); - container.appendChild(div); - }); - } - loadSnippet(id, forceDraft = null) { const snippet = this.snippets.find(s => s.id === id); if (snippet) { @@ -53,12 +42,13 @@ export class SnippetManager { snippet.draft : snippet.content; - this.editor.setValue(JSON.stringify(content, null, 2)); + this.editorManager.setValue(content); this.hasUnsavedChanges = false; this.updateReadOnlyState(); - this.updateSaveButton(); - this.updateVersionSwitch(); - this.renderSnippetList(); + this.uiManager.updateSaveButton(this.hasUnsavedChanges); + this.uiManager.updateVersionSwitch(this.currentSnippetId, this.isDraftVersion, this.hasDraftChanges(this.currentSnippetId)); + this.uiManager.renderSnippetList(this.snippets, this.currentSnippetId); + this.visualizationManager.updateVisualization(content); } } @@ -78,7 +68,7 @@ export class SnippetManager { }; this.snippets.push(newSnippet); - this.saveToStorage(); + this.storageManager.saveSnippets(this.snippets); this.loadSnippet(id); } @@ -86,7 +76,7 @@ export class SnippetManager { if (!this.currentSnippetId) return; try { - const content = JSON.parse(this.editor.getValue()); + const content = JSON.parse(this.editorManager.getValue()); const snippetIndex = this.snippets.findIndex(s => s.id === this.currentSnippetId); if (snippetIndex !== -1) { @@ -94,9 +84,10 @@ export class SnippetManager { if (JSON.stringify(content) !== JSON.stringify(currentSnippet.content)) { this.snippets[snippetIndex].draft = content; this.isDraftVersion = true; - this.saveToStorage(); - this.renderSnippetList(); - this.updateVersionSwitch(); + this.storageManager.saveSnippets(this.snippets); + this.uiManager.renderSnippetList(this.snippets, this.currentSnippetId); + this.uiManager.updateVersionSwitch(this.currentSnippetId, this.isDraftVersion, this.hasDraftChanges(this.currentSnippetId)); + this.visualizationManager.updateVisualization(content); } } } catch (e) { @@ -108,124 +99,49 @@ export class SnippetManager { if (!this.currentSnippetId) return; try { - const content = JSON.parse(this.editor.getValue()); + const content = JSON.parse(this.editorManager.getValue()); const snippetIndex = this.snippets.findIndex(s => s.id === this.currentSnippetId); if (snippetIndex !== -1) { this.snippets[snippetIndex].content = content; delete this.snippets[snippetIndex].draft; // Remove draft after saving - this.saveToStorage(); + this.storageManager.saveSnippets(this.snippets); this.hasUnsavedChanges = false; this.isDraftVersion = false; - this.updateSaveButton(); - this.updateVersionSwitch(); - this.renderSnippetList(); + this.uiManager.updateSaveButton(this.hasUnsavedChanges); + this.uiManager.updateVersionSwitch(this.currentSnippetId, this.isDraftVersion, this.hasDraftChanges(this.currentSnippetId)); + this.uiManager.renderSnippetList(this.snippets, this.currentSnippetId); } } catch (e) { alert('Invalid JSON in editor'); } } + handleReadOnlyOverride() { + this.readOnlyMode = false; + this.isDraftVersion = true; + delete this.snippets.find(s => s.id === this.currentSnippetId).draft; + this.storageManager.saveSnippets(this.snippets); + this.uiManager.updateVersionSwitch(this.currentSnippetId, this.isDraftVersion, this.hasDraftChanges(this.currentSnippetId)); + this.uiManager.renderSnippetList(this.snippets, this.currentSnippetId); + } + + // Remove these methods as they're now in UIManager updateSaveButton() { - const saveButton = document.getElementById('save-snippet'); - saveButton.disabled = !this.hasUnsavedChanges; + this.uiManager.updateSaveButton(this.hasUnsavedChanges); } updateVersionSwitch() { - const versionSwitch = document.getElementById('version-switch'); - if (!versionSwitch) return; - - const hasChanges = this.hasDraftChanges(this.currentSnippetId); - versionSwitch.style.display = hasChanges ? 'block' : 'none'; - - const buttonText = this.isDraftVersion ? - 'View Saved Version (Read-only)' : - 'Switch to Draft Version (Editable)'; - versionSwitch.textContent = buttonText; - } - - setupUI() { - // New snippet button - document.getElementById('new-snippet').onclick = () => this.createNewSnippet(); - - // Save button - document.getElementById('save-snippet').onclick = () => this.saveCurrentSnippet(); - - // Version switch button - const versionSwitch = document.getElementById('version-switch'); - versionSwitch.onclick = () => { - this.loadSnippet(this.currentSnippetId, !this.isDraftVersion); - }; - - // Initial render - this.renderSnippetList(); - } - - setEditor(editor) { - this.editor = editor; - - let timeoutId = null; - editor.onDidChangeModelContent((e) => { - // Only show warning if we're in read-only mode AND this is a user edit - // (not a programmatic change from loadSnippet) - if (this.readOnlyMode && e.isUndoRedo === false) { - if (confirm('Editing the saved version will overwrite your draft. Continue?')) { - this.readOnlyMode = false; - this.isDraftVersion = true; - delete this.snippets.find(s => s.id === this.currentSnippetId).draft; - this.saveToStorage(); - this.updateVersionSwitch(); - this.renderSnippetList(); - } else { - // Revert the change - const snippet = this.snippets.find(s => s.id === this.currentSnippetId); - this.editor.setValue(JSON.stringify(snippet.content, null, 2)); - return; - } - } - - this.hasUnsavedChanges = true; - this.updateSaveButton(); - - // Auto-save to draft - if (timeoutId) clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - this.saveDraft(); - try { - const value = editor.getValue(); - this.updateVisualization(value); - } catch (e) { - console.error('Invalid JSON:', e); - } - }, 1000); - }); - - // Load first snippet if available - if (this.snippets.length > 0) { - this.loadSnippet(this.snippets[0].id); - } + this.uiManager.updateVersionSwitch( + this.currentSnippetId, + this.isDraftVersion, + this.hasDraftChanges(this.currentSnippetId) + ); } updateReadOnlyState() { const hasChanges = this.hasDraftChanges(this.currentSnippetId); this.readOnlyMode = hasChanges && !this.isDraftVersion; - this.editor.updateOptions({ readOnly: this.readOnlyMode }); - } - - async updateVisualization(spec) { - try { - const parsedSpec = typeof spec === 'string' ? JSON.parse(spec) : spec; - parsedSpec.width = parsedSpec.width? parsedSpec.width : 'container'; - parsedSpec.height = parsedSpec.height? parsedSpec.height : 'container'; - await vegaEmbed('#vis', parsedSpec, { - actions: true, // This adds the export/view source buttons - theme: 'light' - }); - } catch (err) { - console.error('Error rendering visualization:', err); - // Optionally show error in the preview panel - document.getElementById('vis').innerHTML = - `