Spaces:
Sleeping
Sleeping
| // 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; | |
| } | |