diff --git a/index.html b/index.html index a372f9f..86d0b75 100644 --- a/index.html +++ b/index.html @@ -160,10 +160,19 @@
- Preview + Preview +
+ Fit: +
+ + + +
+
-
-
+
+
+
diff --git a/src/js/app.js b/src/js/app.js index a7c7407..29b1214 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -378,6 +378,19 @@ document.addEventListener('DOMContentLoaded', function () { switchViewMode('published'); }); + // Preview fit mode buttons + document.getElementById('preview-fit-default').addEventListener('click', () => { + setPreviewFitMode('default'); + }); + + document.getElementById('preview-fit-width').addEventListener('click', () => { + setPreviewFitMode('width'); + }); + + document.getElementById('preview-fit-full').addEventListener('click', () => { + setPreviewFitMode('full'); + }); + // Publish and Revert buttons document.getElementById('publish-btn').addEventListener('click', publishDraft); document.getElementById('revert-btn').addEventListener('click', revertDraft); diff --git a/src/js/editor.js b/src/js/editor.js index 999fe15..a61d9cf 100644 --- a/src/js/editor.js +++ b/src/js/editor.js @@ -1,3 +1,34 @@ +// Track preview fit mode +let previewFitMode = 'default'; // 'default', 'width', or 'full' + +// Apply fit mode to spec dimensions +function applyPreviewFitMode(spec, fitMode) { + if (!spec || typeof spec !== 'object') return spec; + + // Clone to avoid mutation + const modifiedSpec = JSON.parse(JSON.stringify(spec)); + + if (fitMode === 'width') { + // Fit to width - get preview pane width + const previewPane = document.getElementById('vega-preview'); + if (previewPane) { + const containerWidth = previewPane.offsetWidth; + modifiedSpec.width = containerWidth - 10; // 10px padding for scroll + // Keep original aspect ratio by not setting height + } + } else if (fitMode === 'full') { + // Fit to full pane - get both dimensions + const previewPane = document.getElementById('vega-preview'); + if (previewPane) { + modifiedSpec.width = previewPane.offsetWidth - 10; // 10px padding + modifiedSpec.height = previewPane.offsetHeight - 10; // 10px padding + } + } + // 'default' mode leaves original dimensions untouched + + return modifiedSpec; +} + // Resolve dataset references in a spec async function resolveDatasetReferences(spec) { // If spec has data.name, look it up @@ -83,12 +114,18 @@ async function renderVisualization() { // Resolve dataset references spec = await resolveDatasetReferences(spec); + // Apply preview fit mode + spec = applyPreviewFitMode(spec, previewFitMode); + // Render with Vega-Embed (use global variable) await window.vegaEmbed('#vega-preview', spec, { actions: false, // Hide action menu for cleaner look renderer: 'svg' // Use SVG for better quality }); + // Hide overlay after successful render + hidePreviewOverlay(); + } catch (error) { // Handle rendering errors gracefully previewContainer.innerHTML = ` @@ -99,6 +136,9 @@ async function renderVisualization() { Check your JSON syntax and Vega-Lite specification. `; + + // Hide overlay after error + hidePreviewOverlay(); } } @@ -110,6 +150,9 @@ function debouncedRender() { return; } + // Show overlay to indicate rendering is pending + showPreviewOverlay(); + clearTimeout(renderTimeout); const debounceTime = getSetting('performance.renderDebounce') || 1500; renderTimeout = setTimeout(renderVisualization, debounceTime); @@ -121,6 +164,42 @@ function updateRenderDebounce(newDebounce) { // No need to do anything special here } +// Set preview fit mode and update button states +function setPreviewFitMode(mode) { + previewFitMode = mode; + + // Update button states + document.getElementById('preview-fit-default').classList.toggle('active', mode === 'default'); + document.getElementById('preview-fit-width').classList.toggle('active', mode === 'width'); + document.getElementById('preview-fit-full').classList.toggle('active', mode === 'full'); + + // Re-render with new fit mode + renderVisualization(); +} + +// Show preview overlay (dims the visualization) +function showPreviewOverlay() { + const overlay = document.getElementById('preview-overlay'); + if (overlay) { + overlay.style.display = 'block'; + // Trigger reflow to enable transition + void overlay.offsetHeight; + overlay.classList.add('active'); + } +} + +// Hide preview overlay +function hidePreviewOverlay() { + const overlay = document.getElementById('preview-overlay'); + if (overlay) { + overlay.classList.remove('active'); + // Wait for transition to finish before hiding + setTimeout(() => { + overlay.style.display = 'none'; + }, 200); + } +} + // Load Vega libraries dynamically with UMD builds function loadVegaLibraries() { return new Promise((resolve, reject) => { diff --git a/src/js/panel-manager.js b/src/js/panel-manager.js index ea3cef4..12e9630 100644 --- a/src/js/panel-manager.js +++ b/src/js/panel-manager.js @@ -148,6 +148,11 @@ function initializeResize() { document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; + // Show overlay if resizing preview panel (in non-default fit mode) + if (typeof previewFitMode !== 'undefined' && previewFitMode !== 'default') { + showPreviewOverlay(); + } + e.preventDefault(); }); }); @@ -194,6 +199,11 @@ function initializeResize() { updatePanelMemory(); saveLayoutToStorage(); + + // Re-render preview if fit mode is not 'default' (adaptive re-render on resize) + if (typeof previewFitMode !== 'undefined' && previewFitMode !== 'default') { + debouncedRender(); + } } }); } diff --git a/src/styles.css b/src/styles.css index b32fdec..554f5c1 100644 --- a/src/styles.css +++ b/src/styles.css @@ -68,6 +68,7 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun /* Controls */ .editor-controls { display: flex; align-items: center; gap: 6px; height: 20px; } +.preview-controls { display: flex; align-items: center; gap: 6px; height: 20px; } .view-label { font-size: 10px; margin-right: 4px; } .view-toggle-group, .dataset-toggle-group { display: flex; } @@ -173,6 +174,11 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun /* Panel Content */ .panel-content { flex: 1; padding: 8px; overflow: hidden; background: var(--bg-white); border: 1px inset var(--win-gray); display: flex; flex-direction: column; color: var(--text-primary); } +/* Preview Overlay - dims visualization during editing/resizing */ +.preview-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.25); pointer-events: none; transition: opacity 0.2s ease; z-index: 10; } +.preview-overlay.active { opacity: 1; } +:root[data-theme="experimental"] .preview-overlay { background: rgba(0, 0, 0, 0.4); } + /* Placeholder */ .placeholder { color: var(--win-gray-dark); font-style: italic; text-align: center; margin-top: 40px; font-size: 12px; } :root[data-theme="experimental"] .placeholder { color: var(--win-gray-light); }