diff --git a/index.html b/index.html index f7039a4..251e948 100644 --- a/index.html +++ b/index.html @@ -28,7 +28,10 @@

Editor

- +
+ + +
diff --git a/src/SnippetManager.js b/src/SnippetManager.js index 70808cc..a264519 100644 --- a/src/SnippetManager.js +++ b/src/SnippetManager.js @@ -2,10 +2,21 @@ export class SnippetManager { constructor() { this.currentSnippetId = null; this.hasUnsavedChanges = false; + this.isDraftVersion = false; + this.drafts = new Map(); // Store draft versions + this.readOnlyMode = false; this.loadSnippets(); + this.loadDrafts(); this.setupUI(); } + hasDraftChanges(id) { + if (!this.drafts.has(id)) return false; + const snippet = this.snippets.find(s => s.id === id); + const draft = this.drafts.get(id); + return JSON.stringify(snippet.content) !== JSON.stringify(draft); + } + loadSnippets() { // Try to load from localStorage const stored = localStorage.getItem('vegaSnippets'); @@ -17,10 +28,23 @@ export class SnippetManager { } } + loadDrafts() { + const stored = localStorage.getItem('vegaDrafts'); + if (stored) { + const draftsObj = JSON.parse(stored); + this.drafts = new Map(Object.entries(draftsObj)); + } + } + saveToStorage() { localStorage.setItem('vegaSnippets', JSON.stringify(this.snippets)); } + saveDraftsToStorage() { + const draftsObj = Object.fromEntries(this.drafts); + localStorage.setItem('vegaDrafts', JSON.stringify(draftsObj)); + } + renderSnippetList() { const container = document.getElementById('snippet-list'); container.innerHTML = ''; @@ -28,25 +52,32 @@ export class SnippetManager { this.snippets.forEach(snippet => { const div = document.createElement('div'); div.className = `snippet-item ${snippet.id === this.currentSnippetId ? 'active' : ''}`; - div.textContent = snippet.name; + 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) { - if (this.hasUnsavedChanges) { - if (!confirm('You have unsaved changes. Do you want to discard them?')) { - return; - } - } - + loadSnippet(id, forceDraft = null) { const snippet = this.snippets.find(s => s.id === id); if (snippet) { this.currentSnippetId = id; - this.editor.setValue(JSON.stringify(snippet.content, null, 2)); + const hasChanges = this.hasDraftChanges(id); + + // Default to draft if available, unless explicitly specified + this.isDraftVersion = forceDraft !== null ? forceDraft : hasChanges; + + const content = this.isDraftVersion && this.drafts.has(id) ? + this.drafts.get(id) : + snippet.content; + + this.editor.setValue(JSON.stringify(content, null, 2)); this.hasUnsavedChanges = false; + this.updateReadOnlyState(); this.updateSaveButton(); + this.updateVersionSwitch(); this.renderSnippetList(); } } @@ -71,6 +102,26 @@ export class SnippetManager { this.loadSnippet(id); } + saveDraft() { + if (!this.currentSnippetId) return; + + try { + const content = JSON.parse(this.editor.getValue()); + const currentSnippet = this.snippets.find(s => s.id === this.currentSnippetId); + + // Only save draft if content is different from saved version + if (JSON.stringify(content) !== JSON.stringify(currentSnippet.content)) { + this.drafts.set(this.currentSnippetId, content); + this.isDraftVersion = true; + this.saveDraftsToStorage(); + this.renderSnippetList(); + this.updateVersionSwitch(); + } + } catch (e) { + console.error('Invalid JSON in editor'); + } + } + saveCurrentSnippet() { if (!this.currentSnippetId) return; @@ -80,9 +131,14 @@ export class SnippetManager { if (snippetIndex !== -1) { this.snippets[snippetIndex].content = content; + this.drafts.delete(this.currentSnippetId); // Remove draft after saving + this.saveDraftsToStorage(); this.saveToStorage(); this.hasUnsavedChanges = false; + this.isDraftVersion = false; this.updateSaveButton(); + this.updateVersionSwitch(); + this.renderSnippetList(); } } catch (e) { alert('Invalid JSON in editor'); @@ -94,6 +150,19 @@ export class SnippetManager { saveButton.disabled = !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(); @@ -101,6 +170,12 @@ export class SnippetManager { // 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(); } @@ -108,15 +183,33 @@ export class SnippetManager { setEditor(editor) { this.editor = editor; - // Setup change tracking and visualization update let timeoutId = null; - editor.onDidChangeModelContent(() => { + 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; + this.drafts.delete(this.currentSnippetId); + this.saveDraftsToStorage(); + 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(); - // Debounce visualization updates + // Auto-save to draft if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(() => { + this.saveDraft(); try { const value = editor.getValue(); this.updateVisualization(value); @@ -132,6 +225,12 @@ export class SnippetManager { } } + 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; diff --git a/styles.css b/styles.css index 9d883d2..6fadea4 100644 --- a/styles.css +++ b/styles.css @@ -63,6 +63,14 @@ cursor: not-allowed; } +.button.secondary { + background: #607D8B; +} + +.button.secondary:hover { + background: #546E7A; +} + .snippet-list { padding: 1rem; flex-grow: 1; @@ -93,4 +101,10 @@ .preview-panel>*:not(.panel-header) { padding: 1rem; +} + +.editor-controls { + display: flex; + gap: 0.5rem; + align-items: center; } \ No newline at end of file