From 3015c6e39b92c113f58b361c1c3c1c1f2741ed13 Mon Sep 17 00:00:00 2001 From: Oleh Omelchenko Date: Sun, 19 Jan 2025 01:07:25 +0200 Subject: [PATCH] move scripts --- index.html | 310 +---------------------------------------------------- script.js | 308 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 309 deletions(-) create mode 100644 script.js diff --git a/index.html b/index.html index 05564c5..abdc17e 100644 --- a/index.html +++ b/index.html @@ -41,315 +41,7 @@ - + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..19290be --- /dev/null +++ b/script.js @@ -0,0 +1,308 @@ + +// Default snippets +const defaultSnippets = [ + { + id: 'simple-bar', + name: 'Simple Bar Chart', + content: { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "description": "A simple bar chart with embedded data.", + "data": { + "values": [ + { "category": "A", "value": 28 }, + { "category": "B", "value": 55 }, + { "category": "C", "value": 43 } + ] + }, + "mark": "bar", + "encoding": { + "x": { "field": "category", "type": "nominal" }, + "y": { "field": "value", "type": "quantitative" } + } + } + }, + { + id: 'scatter-plot', + name: 'Basic Scatter Plot', + content: { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "description": "A scatter plot example.", + "data": { + "values": [ + { "x": 1, "y": 28 }, { "x": 2, "y": 55 }, { "x": 3, "y": 43 } + ] + }, + "mark": "point", + "encoding": { + "x": { "field": "x", "type": "quantitative" }, + "y": { "field": "y", "type": "quantitative" } + } + } + } +]; + +class PanelResizer { + constructor() { + this.handleDragStart = this.handleDragStart.bind(this); + this.handleDrag = this.handleDrag.bind(this); + this.handleDragEnd = this.handleDragEnd.bind(this); + this.loadLayout(); + this.initializeResizeHandles(); + } + + loadLayout() { + const stored = localStorage.getItem('panelLayout'); + if (stored) { + const layout = JSON.parse(stored); + document.documentElement.style.setProperty('--snippet-width', layout.snippetWidth); + document.documentElement.style.setProperty('--editor-width', layout.editorWidth); + document.documentElement.style.setProperty('--preview-width', layout.previewWidth); + } + } + + saveLayout() { + const layout = { + snippetWidth: document.documentElement.style.getPropertyValue('--snippet-width'), + editorWidth: document.documentElement.style.getPropertyValue('--editor-width'), + previewWidth: document.documentElement.style.getPropertyValue('--preview-width') + }; + localStorage.setItem('panelLayout', JSON.stringify(layout)); + } + + initializeResizeHandles() { + const handles = document.querySelectorAll('.resize-handle'); + handles.forEach((handle, index) => { + handle.addEventListener('mousedown', (e) => this.handleDragStart(e, index)); + }); + } + + handleDragStart(e, handleIndex) { + this.activeHandle = handleIndex; + this.startX = e.clientX; + this.handle = e.target; + this.handle.classList.add('active'); + + // Get the panels adjacent to the handle + const panels = document.querySelectorAll('.panel'); + this.leftPanel = panels[handleIndex]; + this.rightPanel = panels[handleIndex + 1]; + + // Store initial widths + this.leftWidth = this.leftPanel.getBoundingClientRect().width; + this.rightWidth = this.rightPanel.getBoundingClientRect().width; + + document.addEventListener('mousemove', this.handleDrag); + document.addEventListener('mouseup', this.handleDragEnd); + } + + handleDrag(e) { + if (!this.handle) return; + + const dx = e.clientX - this.startX; + const containerWidth = document.querySelector('.container').getBoundingClientRect().width; + + // Calculate new widths as fractions + const newLeftWidth = `${(this.leftWidth + dx) / containerWidth}fr`; + const newRightWidth = `${(this.rightWidth - dx) / containerWidth}fr`; + + // Apply new widths based on which handle is being dragged + if (this.activeHandle === 0) { + document.documentElement.style.setProperty('--snippet-width', newLeftWidth); + document.documentElement.style.setProperty('--editor-width', newRightWidth); + } else { + document.documentElement.style.setProperty('--editor-width', newLeftWidth); + document.documentElement.style.setProperty('--preview-width', newRightWidth); + } + } + + handleDragEnd() { + if (!this.handle) return; + + this.handle.classList.remove('active'); + this.handle = null; + this.saveLayout(); + + document.removeEventListener('mousemove', this.handleDrag); + document.removeEventListener('mouseup', this.handleDragEnd); + + // Trigger Monaco editor resize + if (window.editor) { + window.editor.layout(); + } + } +} + +// Snippet management +class SnippetManager { + constructor() { + this.currentSnippetId = null; + this.hasUnsavedChanges = false; + this.loadSnippets(); + this.setupUI(); + } + + loadSnippets() { + // Try to load from localStorage + const stored = localStorage.getItem('vegaSnippets'); + this.snippets = stored ? JSON.parse(stored) : defaultSnippets; + + // Initialize localStorage if empty + 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' : ''}`; + div.textContent = 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; + } + } + + const snippet = this.snippets.find(s => s.id === id); + if (snippet) { + this.currentSnippetId = id; + this.editor.setValue(JSON.stringify(snippet.content, null, 2)); + this.hasUnsavedChanges = false; + this.updateSaveButton(); + this.renderSnippetList(); + } + } + + createNewSnippet() { + const name = prompt('Enter snippet name:', 'New Snippet'); + if (!name) return; + + const id = 'snippet-' + Date.now(); + const newSnippet = { + id, + name, + content: { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "description": "New visualization", + "mark": "bar" + } + }; + + this.snippets.push(newSnippet); + this.saveToStorage(); + this.loadSnippet(id); + } + + saveCurrentSnippet() { + if (!this.currentSnippetId) return; + + try { + const content = JSON.parse(this.editor.getValue()); + const snippetIndex = this.snippets.findIndex(s => s.id === this.currentSnippetId); + + if (snippetIndex !== -1) { + this.snippets[snippetIndex].content = content; + this.saveToStorage(); + this.hasUnsavedChanges = false; + this.updateSaveButton(); + } + } catch (e) { + alert('Invalid JSON in editor'); + } + } + + updateSaveButton() { + const saveButton = document.getElementById('save-snippet'); + saveButton.disabled = !this.hasUnsavedChanges; + } + + setupUI() { + // New snippet button + document.getElementById('new-snippet').onclick = () => this.createNewSnippet(); + + // Save button + document.getElementById('save-snippet').onclick = () => this.saveCurrentSnippet(); + + // Initial render + this.renderSnippetList(); + } + + setEditor(editor) { + this.editor = editor; + + // Setup change tracking and visualization update + let timeoutId = null; + editor.onDidChangeModelContent(() => { + this.hasUnsavedChanges = true; + this.updateSaveButton(); + + // Debounce visualization updates + if (timeoutId) clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + try { + const value = editor.getValue(); + this.updateVisualization(value); + } catch (e) { + console.error('Invalid JSON:', e); + } + }, 300); + }); + + // Load first snippet if available + if (this.snippets.length > 0) { + this.loadSnippet(this.snippets[0].id); + } + } + + async updateVisualization(spec) { + try { + const parsedSpec = typeof spec === 'string' ? JSON.parse(spec) : spec; + 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 = + `
Error rendering visualization: ${err.message}
`; + } + } +} + +// Initialize Monaco +require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' } }); + +const snippetManager = new SnippetManager(); + +require(['vs/editor/editor.main'], async function () { + // Create editor instance + const editor = monaco.editor.create(document.getElementById('monaco-editor'), { + language: 'json', + theme: 'vs-light', + wordWrap: 'on', + minimap: { enabled: false }, + automaticLayout: true, + formatOnPaste: true, + formatOnType: true + }); + + const resizer = new PanelResizer(); + window.editor = editor; + + // Connect editor to snippet manager + snippetManager.setEditor(editor); +});