mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
feat: implement preview fit mode controls and overlay for improved visualization management
This commit is contained in:
15
index.html
15
index.html
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
Reference in New Issue
Block a user