feat: implement preview fit mode controls and overlay for improved visualization management

This commit is contained in:
2025-10-18 02:52:48 +03:00
parent 73a5f69dde
commit 9ef7b1188f
5 changed files with 120 additions and 3 deletions

View File

@@ -160,10 +160,19 @@
<!-- Preview Panel --> <!-- Preview Panel -->
<div class="panel preview-panel" id="preview-panel"> <div class="panel preview-panel" id="preview-panel">
<div class="panel-header"> <div class="panel-header">
Preview <span>Preview</span>
<div class="preview-controls">
<span class="view-label">Fit:</span>
<div class="view-toggle-group">
<button class="btn btn-toggle active" id="preview-fit-default" title="Display at original spec dimensions">Original</button>
<button class="btn btn-toggle" id="preview-fit-width" title="Scale to fit preview pane width">Width</button>
<button class="btn btn-toggle" id="preview-fit-full" title="Scale to fit entire preview pane">Full</button>
</div>
</div>
</div> </div>
<div class="panel-content"> <div class="panel-content" style="position: relative;">
<div id="vega-preview" style="height: 100%; width: 100%; overflow: auto;"></div> <div id="vega-preview" style="height: 100%; width: 100%; overflow: auto; display: flex; align-items: center; justify-content: center;"></div>
<div id="preview-overlay" class="preview-overlay" style="display: none;"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -378,6 +378,19 @@ document.addEventListener('DOMContentLoaded', function () {
switchViewMode('published'); 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 // Publish and Revert buttons
document.getElementById('publish-btn').addEventListener('click', publishDraft); document.getElementById('publish-btn').addEventListener('click', publishDraft);
document.getElementById('revert-btn').addEventListener('click', revertDraft); document.getElementById('revert-btn').addEventListener('click', revertDraft);

View File

@@ -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 // Resolve dataset references in a spec
async function resolveDatasetReferences(spec) { async function resolveDatasetReferences(spec) {
// If spec has data.name, look it up // If spec has data.name, look it up
@@ -83,12 +114,18 @@ async function renderVisualization() {
// Resolve dataset references // Resolve dataset references
spec = await resolveDatasetReferences(spec); spec = await resolveDatasetReferences(spec);
// Apply preview fit mode
spec = applyPreviewFitMode(spec, previewFitMode);
// Render with Vega-Embed (use global variable) // Render with Vega-Embed (use global variable)
await window.vegaEmbed('#vega-preview', spec, { await window.vegaEmbed('#vega-preview', spec, {
actions: false, // Hide action menu for cleaner look actions: false, // Hide action menu for cleaner look
renderer: 'svg' // Use SVG for better quality renderer: 'svg' // Use SVG for better quality
}); });
// Hide overlay after successful render
hidePreviewOverlay();
} catch (error) { } catch (error) {
// Handle rendering errors gracefully // Handle rendering errors gracefully
previewContainer.innerHTML = ` previewContainer.innerHTML = `
@@ -99,6 +136,9 @@ async function renderVisualization() {
<em>Check your JSON syntax and Vega-Lite specification.</em> <em>Check your JSON syntax and Vega-Lite specification.</em>
</div> </div>
`; `;
// Hide overlay after error
hidePreviewOverlay();
} }
} }
@@ -110,6 +150,9 @@ function debouncedRender() {
return; return;
} }
// Show overlay to indicate rendering is pending
showPreviewOverlay();
clearTimeout(renderTimeout); clearTimeout(renderTimeout);
const debounceTime = getSetting('performance.renderDebounce') || 1500; const debounceTime = getSetting('performance.renderDebounce') || 1500;
renderTimeout = setTimeout(renderVisualization, debounceTime); renderTimeout = setTimeout(renderVisualization, debounceTime);
@@ -121,6 +164,42 @@ function updateRenderDebounce(newDebounce) {
// No need to do anything special here // 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 // Load Vega libraries dynamically with UMD builds
function loadVegaLibraries() { function loadVegaLibraries() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -148,6 +148,11 @@ function initializeResize() {
document.body.style.cursor = 'col-resize'; document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none'; 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(); e.preventDefault();
}); });
}); });
@@ -194,6 +199,11 @@ function initializeResize() {
updatePanelMemory(); updatePanelMemory();
saveLayoutToStorage(); saveLayoutToStorage();
// Re-render preview if fit mode is not 'default' (adaptive re-render on resize)
if (typeof previewFitMode !== 'undefined' && previewFitMode !== 'default') {
debouncedRender();
}
} }
}); });
} }

View File

@@ -68,6 +68,7 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
/* Controls */ /* Controls */
.editor-controls { display: flex; align-items: center; gap: 6px; height: 20px; } .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-label { font-size: 10px; margin-right: 4px; }
.view-toggle-group, .view-toggle-group,
.dataset-toggle-group { display: flex; } .dataset-toggle-group { display: flex; }
@@ -173,6 +174,11 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
/* Panel Content */ /* 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); } .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 */
.placeholder { color: var(--win-gray-dark); font-style: italic; text-align: center; margin-top: 40px; font-size: 12px; } .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); } :root[data-theme="experimental"] .placeholder { color: var(--win-gray-light); }