Spaces:
Sleeping
Sleeping
| /* ===== 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', | |
| }); | |
| }, | |
| }; |