llm-semantic-visualizer / src /ui /panels /LayoutPanel.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
8.38 kB
// Layout Settings panel (extracted from Sidebar.js in H4 4.1).
// Owns the "Layout Settings" card: mode selector, THREADS/RELIEF sub-mode toggles,
// thread spacing/width/thickness sliders, and the COMPARE linear-separation slider.
// The relief controls (with the nested SAE + model sub-panels) live inside this card
// and are composed from ReliefPanel to keep the DOM identical.
import { STATE, CONFIG } from '../../core/State.js';
import { updateAllThreadPositions } from '../../visualizer/LayoutEngine.js';
import { updateThreadVisibility } from '../../visualizer/ThreadManager.js';
import { reliefVisualizerMarkup, wireReliefPanel } from './ReliefPanel.js';
import { wireModelPanel } from './ModelPanel.js';
/** @typedef {import('../types.js').PanelContext} PanelContext */
/**
* Wire the layout-mode selector and thread sliders. The sub-mode toggles + relief
* controls are wired by ReliefPanel.
* @param {PanelContext} ctx
* @returns {void}
*/
function wireLayoutControls(ctx) {
const layoutSelector = document.getElementById('layout-mode-selector');
const blanketControls = document.getElementById('blanket-controls');
const compareControls = document.getElementById('compare-controls');
const spacingSlider = document.getElementById('thread-spacing-slider');
const widthSlider = document.getElementById('thread-width-slider');
const thicknessSlider = document.getElementById('thread-thickness-slider');
const compSpacingSlider = document.getElementById('compare-spacing-slider');
if (layoutSelector) {
layoutSelector.onchange = (e) => {
STATE.layoutMode = e.target.value;
ctx.refresh.subModeUI?.();
updateAllThreadPositions();
updateThreadVisibility();
};
}
if (spacingSlider) {
spacingSlider.oninput = function() {
CONFIG.threadSpacing = parseFloat(this.value);
updateAllThreadPositions();
};
}
if (widthSlider) {
widthSlider.oninput = function() {
CONFIG.threadWidth = parseFloat(this.value);
updateAllThreadPositions();
};
}
if (thicknessSlider) {
thicknessSlider.oninput = function() {
CONFIG.threadThickness = parseFloat(this.value);
updateAllThreadPositions();
};
}
if (compSpacingSlider) {
compSpacingSlider.oninput = function() {
CONFIG.compareSpacing = parseFloat(this.value);
updateAllThreadPositions();
};
}
}
/**
* Build the Layout Settings card (composing the relief controls) and schedule the
* layout/model/relief wiring. Model is wired before relief so its refreshers are
* published before the relief panel may invoke them.
* @param {PanelContext} ctx
* @returns {HTMLElement}
*/
export function buildLayoutPanel(ctx) {
const layoutSection = document.createElement('div');
layoutSection.className = 'section-card';
layoutSection.innerHTML = `
<div class="section-title">Layout Settings</div>
<div style="display:flex; flex-direction:column; gap: var(--spacing-xs);">
<div style="font-size: var(--font-size-small); color: var(--color-neutral-400); margin-bottom: 4px;">Mode:</div>
<select id="layout-mode-selector" class="input-field" style="background: oklch(from var(--color-neutral-950) l c h / 0.5);">
<option value="COMPARE" ${STATE.layoutMode === 'COMPARE' ? 'selected' : ''}>Compare (Overlapping)</option>
<option value="BLANKET" ${STATE.layoutMode === 'BLANKET' ? 'selected' : ''}>3D Blanket (Parallel)</option>
</select>
<!-- Sub-mode: THREADS / RELIEF (Global) -->
<div style="display:flex; align-items:center; justify-content:space-between; margin-top: 6px; margin-bottom: 4px;">
<span style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">Sub-mode:</span>
<div style="display:flex; gap: 4px;">
<button id="blanket-sub-threads" class="btn btn-secondary" style="font-size: var(--font-size-small); min-height:22px; padding: 0 8px; ${STATE.blanketSubMode === 'THREADS' ? 'background: var(--color-brand-primary); color: var(--color-neutral-950);' : ''}">Threads</button>
<button id="blanket-sub-relief" class="btn btn-secondary" style="font-size: var(--font-size-small); min-height:22px; padding: 0 8px; ${STATE.blanketSubMode === 'RELIEF' ? 'background: var(--color-brand-secondary); color: var(--color-neutral-950);' : ''}">🏔️ Relief</button>
</div>
</div>
<!-- Standard THREADS controls (visible in THREADS sub-mode and BLANKET mode) -->
<div id="threads-submode-controls" style="display:${STATE.layoutMode === 'BLANKET' && STATE.blanketSubMode === 'THREADS' ? 'block' : 'none'}; margin-top: 8px;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);" title="Lateral spacing between text threads">Thread Spacing (X):</label>
<span id="thread-spacing-val" style="font-size: 10px; font-family: var(--font-mono); color: var(--color-brand-primary); font-weight:bold;">${CONFIG.threadSpacing}</span>
</div>
<input type="range" id="thread-spacing-slider" min="0.1" max="10.0" step="0.1" value="${CONFIG.threadSpacing}" title="Adjust lateral spacing between threads">
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:4px;">
<label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);" title="Longitudinal scale of each thread along the Z axis">Thread Width (Z Scale):</label>
<span id="thread-width-val" style="font-size: 10px; font-family: var(--font-mono); color: var(--color-brand-primary); font-weight:bold;">${CONFIG.threadWidth}</span>
</div>
<input type="range" id="thread-width-slider" min="0.1" max="5.0" step="0.1" value="${CONFIG.threadWidth}" title="Adjust thread length along the Z axis">
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:4px;">
<label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);" title="Visual thickness of 3D ribbon threads">Thread Thickness:</label>
<span id="thread-thickness-val" style="font-size: 10px; font-family: var(--font-mono); color: var(--color-brand-primary); font-weight:bold;">${CONFIG.threadThickness}</span>
</div>
<input type="range" id="thread-thickness-slider" min="1.0" max="5.0" step="0.1" value="${CONFIG.threadThickness}" title="Adjust visual thickness of threads">
</div>
${reliefVisualizerMarkup()}
<div id="compare-controls" style="display:${STATE.layoutMode === 'COMPARE' ? 'block' : 'none'}; margin-top: 8px;">
<div style="display:flex; flex-direction:column; gap: var(--spacing-xs);">
<div style="display:flex; justify-content:space-between; align-items:center;">
<label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);" title="Horizontal separation between overlapping models or documents">Linear Separation (X):</label>
<span id="compare-spacing-val" style="font-size: 10px; font-family: var(--font-mono); color: var(--color-brand-primary); font-weight:bold;">${CONFIG.compareSpacing}</span>
</div>
<input type="range" id="compare-spacing-slider" min="0.0" max="5.0" step="0.1" value="${CONFIG.compareSpacing}" title="Adjust linear distance in comparisons">
</div>
</div>
</div>
`;
// Defer wiring until the element is in the document (same tick as createSidebar's
// original setTimeout). Model is wired before relief so ctx.refresh.modelPanel is
// published before the relief panel can call it.
setTimeout(() => {
wireLayoutControls(ctx);
wireModelPanel(ctx);
wireReliefPanel(ctx);
}, 0);
return layoutSection;
}