feat: add StorageManager, VisualizationManager, UIManager, EditorManager, and update PanelResizer for improved snippet handling and visualization

This commit is contained in:
2025-01-19 03:46:25 +02:00
parent 9bb476c135
commit 811d981ab9
6 changed files with 208 additions and 142 deletions

View File

@@ -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 =
`<div style="color: red; padding: 1rem;">Error rendering visualization: ${err.message}</div>`;
}
this.editorManager.updateReadOnlyState(this.readOnlyMode);
}
}