feat: implement draft versioning and UI controls for snippet management

This commit is contained in:
2025-01-19 02:16:31 +02:00
parent ad1d5865ab
commit adb575ea00
3 changed files with 129 additions and 13 deletions

View File

@@ -28,7 +28,10 @@
<div class="panel"> <div class="panel">
<div class="panel-header"> <div class="panel-header">
<h2>Editor</h2> <h2>Editor</h2>
<button class="button" id="save-snippet" disabled>Save Changes</button> <div class="editor-controls">
<button class="button secondary" id="version-switch" style="display: none">View Saved Version</button>
<button class="button" id="save-snippet" disabled>Save Changes</button>
</div>
</div> </div>
<div id="monaco-editor"></div> <div id="monaco-editor"></div>
</div> </div>

View File

@@ -2,10 +2,21 @@ export class SnippetManager {
constructor() { constructor() {
this.currentSnippetId = null; this.currentSnippetId = null;
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
this.isDraftVersion = false;
this.drafts = new Map(); // Store draft versions
this.readOnlyMode = false;
this.loadSnippets(); this.loadSnippets();
this.loadDrafts();
this.setupUI(); 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() { loadSnippets() {
// Try to load from localStorage // Try to load from localStorage
const stored = localStorage.getItem('vegaSnippets'); 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() { saveToStorage() {
localStorage.setItem('vegaSnippets', JSON.stringify(this.snippets)); localStorage.setItem('vegaSnippets', JSON.stringify(this.snippets));
} }
saveDraftsToStorage() {
const draftsObj = Object.fromEntries(this.drafts);
localStorage.setItem('vegaDrafts', JSON.stringify(draftsObj));
}
renderSnippetList() { renderSnippetList() {
const container = document.getElementById('snippet-list'); const container = document.getElementById('snippet-list');
container.innerHTML = ''; container.innerHTML = '';
@@ -28,25 +52,32 @@ export class SnippetManager {
this.snippets.forEach(snippet => { this.snippets.forEach(snippet => {
const div = document.createElement('div'); const div = document.createElement('div');
div.className = `snippet-item ${snippet.id === this.currentSnippetId ? 'active' : ''}`; 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); div.onclick = () => this.loadSnippet(snippet.id);
container.appendChild(div); container.appendChild(div);
}); });
} }
loadSnippet(id) { loadSnippet(id, forceDraft = null) {
if (this.hasUnsavedChanges) {
if (!confirm('You have unsaved changes. Do you want to discard them?')) {
return;
}
}
const snippet = this.snippets.find(s => s.id === id); const snippet = this.snippets.find(s => s.id === id);
if (snippet) { if (snippet) {
this.currentSnippetId = id; 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.hasUnsavedChanges = false;
this.updateReadOnlyState();
this.updateSaveButton(); this.updateSaveButton();
this.updateVersionSwitch();
this.renderSnippetList(); this.renderSnippetList();
} }
} }
@@ -71,6 +102,26 @@ export class SnippetManager {
this.loadSnippet(id); 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() { saveCurrentSnippet() {
if (!this.currentSnippetId) return; if (!this.currentSnippetId) return;
@@ -80,9 +131,14 @@ export class SnippetManager {
if (snippetIndex !== -1) { if (snippetIndex !== -1) {
this.snippets[snippetIndex].content = content; this.snippets[snippetIndex].content = content;
this.drafts.delete(this.currentSnippetId); // Remove draft after saving
this.saveDraftsToStorage();
this.saveToStorage(); this.saveToStorage();
this.hasUnsavedChanges = false; this.hasUnsavedChanges = false;
this.isDraftVersion = false;
this.updateSaveButton(); this.updateSaveButton();
this.updateVersionSwitch();
this.renderSnippetList();
} }
} catch (e) { } catch (e) {
alert('Invalid JSON in editor'); alert('Invalid JSON in editor');
@@ -94,6 +150,19 @@ export class SnippetManager {
saveButton.disabled = !this.hasUnsavedChanges; 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() { setupUI() {
// New snippet button // New snippet button
document.getElementById('new-snippet').onclick = () => this.createNewSnippet(); document.getElementById('new-snippet').onclick = () => this.createNewSnippet();
@@ -101,6 +170,12 @@ export class SnippetManager {
// Save button // Save button
document.getElementById('save-snippet').onclick = () => this.saveCurrentSnippet(); 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 // Initial render
this.renderSnippetList(); this.renderSnippetList();
} }
@@ -108,15 +183,33 @@ export class SnippetManager {
setEditor(editor) { setEditor(editor) {
this.editor = editor; this.editor = editor;
// Setup change tracking and visualization update
let timeoutId = null; 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.hasUnsavedChanges = true;
this.updateSaveButton(); this.updateSaveButton();
// Debounce visualization updates // Auto-save to draft
if (timeoutId) clearTimeout(timeoutId); if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
this.saveDraft();
try { try {
const value = editor.getValue(); const value = editor.getValue();
this.updateVisualization(value); 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) { async updateVisualization(spec) {
try { try {
const parsedSpec = typeof spec === 'string' ? JSON.parse(spec) : spec; const parsedSpec = typeof spec === 'string' ? JSON.parse(spec) : spec;

View File

@@ -63,6 +63,14 @@
cursor: not-allowed; cursor: not-allowed;
} }
.button.secondary {
background: #607D8B;
}
.button.secondary:hover {
background: #546E7A;
}
.snippet-list { .snippet-list {
padding: 1rem; padding: 1rem;
flex-grow: 1; flex-grow: 1;
@@ -93,4 +101,10 @@
.preview-panel>*:not(.panel-header) { .preview-panel>*:not(.panel-header) {
padding: 1rem; padding: 1rem;
}
.editor-controls {
display: flex;
gap: 0.5rem;
align-items: center;
} }