/* ===== Panel Resizer (Universal) ===== */ window.NAIResizer = { instances: [], create(containerId, leftSel, rightSel, opts = {}) { const container = document.querySelector(containerId); if (!container) return null; const left = container.querySelector(leftSel); const right = container.querySelector(rightSel); if (!left || !right) return null; left.style.minHeight = '0'; right.style.minHeight = '0'; const minLeft = opts.minLeft ?? 200; const minRight = opts.minRight ?? 200; const storeKey = opts.storeKey || containerId; // Create resizer element const resizer = document.createElement('div'); resizer.className = 'panel-resizer'; resizer.style.flexShrink = '0'; // Insert between left and right right.parentNode.insertBefore(resizer, right); const state = { container, left, right, resizer, minLeft, minRight, storeKey, savedSize: null, dragging: false, }; // Detect orientation const isVertical = () => { const style = getComputedStyle(container); return style.flexDirection === 'column' || style.flexDirection === 'column-reverse'; }; // Apply orientation class const updateOrientation = () => { const vert = isVertical(); resizer.classList.toggle('vertical', vert); }; // Observe container for flex-direction changes (responsive breakpoints) let prevVertical = isVertical(); let resizeTimer; const resizeObserver = new ResizeObserver(() => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { const nowVertical = isVertical(); if (nowVertical !== prevVertical) { prevVertical = nowVertical; this._resetSizes(state); updateOrientation(); } }, 100); const nowVertical = isVertical(); if (nowVertical !== prevVertical) { prevVertical = nowVertical; this._resetSizes(state); updateOrientation(); } }); resizeObserver.observe(container); updateOrientation(); // ── Drag logic ── let startPos, startSize; const onDown = (e) => { e.preventDefault(); state.dragging = true; const vert = isVertical(); startPos = vert ? (e.touches ? e.touches[0].clientY : e.clientY) : (e.touches ? e.touches[0].clientX : e.clientX); startSize = vert ? left.getBoundingClientRect().height : left.getBoundingClientRect().width; resizer.classList.add('active'); document.body.style.cursor = vert ? 'row-resize' : 'col-resize'; document.body.style.userSelect = 'none'; }; const onMove = (e) => { if (!state.dragging) return; const vert = isVertical(); const clientPos = vert ? (e.touches ? e.touches[0].clientY : e.clientY) : (e.touches ? e.touches[0].clientX : e.clientX); const delta = clientPos - startPos; const resizerSize = vert ? resizer.getBoundingClientRect().height : resizer.getBoundingClientRect().width; const containerSize = vert ? container.getBoundingClientRect().height - resizerSize : container.getBoundingClientRect().width - resizerSize; const newSize = Math.max(minLeft, Math.min(containerSize - minRight, startSize + delta)); const prop = vert ? 'height' : 'width'; const minProp = vert ? 'minHeight' : 'minWidth'; const maxProp = vert ? 'maxHeight' : 'maxWidth'; left.style[prop] = newSize + 'px'; left.style[minProp] = newSize + 'px'; left.style[maxProp] = newSize + 'px'; left.style.flex = 'none'; right.style.flex = '1'; }; const onUp = () => { if (!state.dragging) return; state.dragging = false; resizer.classList.remove('active'); document.body.style.cursor = ''; document.body.style.userSelect = ''; }; resizer.addEventListener('mousedown', onDown); document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); resizer.addEventListener('touchstart', onDown, { passive: false }); document.addEventListener('touchmove', onMove, { passive: false }); document.addEventListener('touchend', onUp); // Double click to reset resizer.addEventListener('dblclick', () => { this._resetSizes(state); }); updateOrientation(); this.instances.push(state); return state; }, _resetSizes(state) { const props = ['width', 'minWidth', 'maxWidth', 'height', 'minHeight', 'maxHeight', 'flex', 'overflow', 'padding', 'border']; for (const p of props) { state.left.style[p] = ''; state.right.style[p] = ''; } }, init() { this.create('.gen-layout', '.gen-params', '.gen-result', { minLeft: 280, minRight: 200, storeKey: 'gen', }); this.create('.dt-layout', '.dt-left', '.dt-right', { minLeft: 250, minRight: 200, storeKey: 'dt', }); this.create('.pi-layout', '.pi-left', '.pi-right', { minLeft: 200, minRight: 200, storeKey: 'pi', }); this.create('.tagger-layout', '.tagger-left', '.tagger-right', { minLeft: 250, minRight: 200, storeKey: 'tagger', }); this.create('.history-layout', '.history-left', '.history-right', { minLeft: 200, minRight: 250, storeKey: 'history', }); // Cloud: between main and detail panel this.create('.cloud-layout', '.cloud-main', '.cloud-detail-panel', { minLeft: 200, minRight: 250, storeKey: 'cloud', }); }, };