Files
web-cad/mockup/assets/interactions.js
T

341 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// web-cad v6 Interactions (theme, tabs, drawers, mobile right-tab-bar)
(function() {
'use strict';
// ===================== THEME TOGGLE =====================
const themeBtn = document.getElementById('theme-toggle');
const html = document.documentElement;
if (themeBtn) {
themeBtn.addEventListener('click', () => {
const cur = html.getAttribute('data-theme');
html.setAttribute('data-theme', cur === 'dark' ? 'light' : 'dark');
});
}
// ===================== RIGHTBAR (desktop) TAB SWITCHING =====================
document.querySelectorAll('.rightbar-tab').forEach(tab => {
tab.addEventListener('click', () => {
const panel = tab.dataset.panel;
document.querySelectorAll('.rightbar-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
document.querySelectorAll('.rightbar-panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
const target = document.querySelector(`[data-panel-content="${panel}"]`);
if (target) target.classList.add('active');
// sync mobile drawer if open
const dTab = document.querySelector(`[data-drawer-tab="${panel}"]`);
if (dTab) dTab.click();
});
});
// ===================== DRAWER TAB SWITCHING (mobile) =====================
document.querySelectorAll('.drawer-tab').forEach(tab => {
tab.addEventListener('click', () => {
const panel = tab.dataset.drawerTab;
document.querySelectorAll('.drawer-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Re-render drawer body for this panel
renderDrawerRight(panel);
// sync desktop rightbar
const rTab = document.querySelector(`.rightbar-tab[data-panel="${panel}"]`);
if (rTab) {
document.querySelectorAll('.rightbar-tab').forEach(t => t.classList.remove('active'));
rTab.classList.add('active');
document.querySelectorAll('.rightbar-panel').forEach(p => p.classList.remove('active'));
const target = document.querySelector(`[data-panel-content="${panel}"]`);
if (target) target.classList.add('active');
}
});
});
// ===================== RIGHT-TAB-BAR (mobile vertical) =====================
document.querySelectorAll('.right-tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const which = btn.dataset.drawer;
document.querySelectorAll('.right-tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// activate drawer tab + open drawer
const dTab = document.querySelector(`[data-drawer-tab="${which}"]`);
if (dTab) dTab.click();
openDrawer('right');
});
});
// ===================== DRAWER OPEN/CLOSE =====================
const scrim = document.getElementById('scrim');
const drawerLeft = document.getElementById('drawer-left');
const drawerRight = document.getElementById('drawer-right');
function openDrawer(which) {
const drawerLeftBody = document.getElementById('drawer-left-body');
const drawerRightBody = document.getElementById('drawer-right-body');
if (which === 'left' && drawerLeft) {
drawerLeft.classList.add('open');
drawerLeft.setAttribute('aria-hidden', 'false');
// Use children.length instead of innerHTML.trim() because placeholder HTML comments would prevent re-render
if (drawerLeftBody && drawerLeftBody.children.length === 0) {
renderDrawerLeft();
}
}
if (which === 'right' && drawerRight) {
drawerRight.classList.add('open');
drawerRight.setAttribute('aria-hidden', 'false');
if (drawerRightBody && drawerRightBody.children.length === 0) {
const active = document.querySelector('.drawer-tab.active');
renderDrawerRight(active ? active.dataset.drawerTab : 'tool');
}
}
if (scrim) scrim.classList.add('show');
document.body.classList.add('drawer-open');
}
function closeDrawers() {
if (drawerLeft) { drawerLeft.classList.remove('open'); drawerLeft.setAttribute('aria-hidden', 'true'); }
if (drawerRight) { drawerRight.classList.remove('open'); drawerRight.setAttribute('aria-hidden', 'true'); }
if (scrim) scrim.classList.remove('show');
document.body.classList.remove('drawer-open');
}
// Hamburger opens left drawer
const hamburger = document.getElementById('hamburger-btn');
if (hamburger) {
hamburger.addEventListener('click', () => openDrawer('left'));
}
// Close buttons
document.querySelectorAll('[data-close-drawer]').forEach(b => {
b.addEventListener('click', closeDrawers);
});
// Scrim click closes
if (scrim) scrim.addEventListener('click', closeDrawers);
// ESC key closes
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeDrawers();
});
// ===================== RENDER DRAWER LEFT (clone leftbar) =====================
function renderDrawerLeft() {
const src = document.querySelector('.leftbar');
const dst = document.getElementById('drawer-left-body');
if (!src || !dst) return;
// Clone leftbar body (skip header which has toggle)
const clone = src.cloneNode(true);
// Remove the inner toggle button (drawer has its own close)
const innerToggle = clone.querySelector('.leftbar-toggle');
if (innerToggle) innerToggle.remove();
// Remove header (drawer has its own header)
const header = clone.querySelector('.leftbar-header');
if (header) header.remove();
// Remove footer (not needed in drawer)
const footer = clone.querySelector('.leftbar-footer');
if (footer) footer.remove();
// Mark as drawer version + force display (the original .leftbar is hidden on mobile)
clone.classList.add('drawer-content-left');
clone.removeAttribute('id');
clone.style.cssText = 'display: flex; flex-direction: column; flex: 1; min-height: 0; border: none; background: transparent;';
dst.innerHTML = '';
dst.appendChild(clone);
// Wire up tool buttons inside drawer
dst.querySelectorAll('.tool-btn').forEach(b => {
b.addEventListener('click', () => {
document.querySelectorAll('.tool-btn').forEach(x => x.classList.remove('active'));
b.classList.add('active');
// Close drawer on tool selection (mobile UX)
setTimeout(() => closeDrawers(), 200);
});
});
}
// ===================== RENDER DRAWER RIGHT (clone rightbar panel) =====================
function renderDrawerRight(panel) {
const src = document.querySelector(`[data-panel-content="${panel}"]`);
const dst = document.getElementById('drawer-right-body');
const titleEl = document.getElementById('drawer-right-title');
if (!src || !dst) return;
const labels = { tool: 'Werkzeug', layer: 'Layer', library: 'Bibliothek', ki: 'KI Copilot' };
if (titleEl) titleEl.textContent = labels[panel] || 'Panel';
dst.innerHTML = '';
const clone = src.cloneNode(true);
clone.classList.add('active');
clone.style.cssText = 'display: block; width: 100%;';
dst.appendChild(clone);
// Re-wire KI chips and library categories inside drawer
dst.querySelectorAll('.ki-chip').forEach(chip => {
chip.addEventListener('click', () => {
const input = dst.querySelector('.ki-input');
if (input) { input.value = chip.textContent.trim(); input.focus(); }
});
});
dst.querySelectorAll('.lib-cat').forEach(cat => {
cat.addEventListener('click', () => {
dst.querySelectorAll('.lib-cat').forEach(c => c.classList.remove('active'));
cat.classList.add('active');
});
});
}
// ===================== RIBBON TAB SWITCHING =====================
document.querySelectorAll('.ribbon-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.ribbon-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
});
});
// ===================== TOOL SELECTION (desktop) =====================
document.querySelectorAll('.leftbar .tool-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.leftbar .tool-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// ===================== LEFTBAR TOGGLE (desktop) =====================
const leftToggle = document.getElementById('leftbar-toggle');
if (leftToggle) {
leftToggle.addEventListener('click', () => {
const main = document.querySelector('.main');
if (main) main.classList.toggle('left-collapsed');
});
}
// ===================== CANVAS VIEW TABS =====================
document.querySelectorAll('.canvas-view-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.canvas-view-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
});
});
// ===================== STATUS BAR TOGGLES =====================
document.querySelectorAll('.status-item').forEach(item => {
item.addEventListener('click', () => item.classList.toggle('active'));
});
// ===================== CANVAS TOOLBAR (grid, ortho, snap) =====================
['btn-grid', 'btn-ortho', 'btn-snap'].forEach(id => {
const b = document.getElementById(id);
if (b) {
b.addEventListener('click', () => {
b.classList.toggle('active');
const pressed = b.classList.contains('active');
b.setAttribute('aria-pressed', pressed ? 'true' : 'false');
// Also toggle matching status bar item
const map = { 'btn-grid': 'GRID', 'btn-ortho': 'ORTHO', 'btn-snap': 'SNAP' };
const label = map[id];
if (label) {
document.querySelectorAll('.status-item').forEach(s => {
if (s.textContent.trim().startsWith(label)) s.classList.toggle('active', pressed);
});
}
});
}
});
// ===================== ZOOM =====================
let zoom = 100;
const zoomDisplay = document.getElementById('zoom-display');
document.getElementById('btn-zoom-in')?.addEventListener('click', () => {
zoom = Math.min(400, zoom + 25);
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
});
document.getElementById('btn-zoom-out')?.addEventListener('click', () => {
zoom = Math.max(10, zoom - 25);
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
});
document.getElementById('btn-zoom-fit')?.addEventListener('click', () => {
zoom = 100;
if (zoomDisplay) zoomDisplay.textContent = zoom + '%';
});
// ===================== LIBRARY CATEGORIES (desktop) =====================
document.querySelectorAll('.rightbar .lib-cat').forEach(cat => {
cat.addEventListener('click', () => {
document.querySelectorAll('.rightbar .lib-cat').forEach(c => c.classList.remove('active'));
cat.classList.add('active');
});
});
// ===================== KI CHIPS (desktop) =====================
document.querySelectorAll('.rightbar .ki-chip').forEach(chip => {
chip.addEventListener('click', () => {
const input = document.querySelector('.rightbar .ki-input');
if (input) { input.value = chip.textContent.trim(); input.focus(); }
});
});
// ===================== KEYBOARD SHORTCUTS =====================
const toolMap = { v: 'select', l: 'line', c: 'circle', m: 'move', p: 'pan', z: 'zoom-win', e: 'erase' };
document.addEventListener('keydown', (e) => {
if (e.target.matches('input, textarea, select')) return;
if (e.key === 'Escape') return; // handled by drawer
const k = e.key.toLowerCase();
if (toolMap[k]) {
const btn = document.querySelector(`.tool-btn[data-tool="${toolMap[k]}"]`);
if (btn) btn.click();
}
});
})();
// ===================== TREE COMPONENT TOGGLE =====================
(function initTrees() {
// Delegate click on [data-tree-toggle] rows (folder rows)
document.addEventListener('click', (e) => {
const toggleBtn = e.target.closest('.tree-toggle');
if (toggleBtn) {
const row = toggleBtn.closest('[data-tree-toggle]');
if (!row) return;
e.stopPropagation();
const node = row.parentElement;
toggleTreeNode(node);
return;
}
// Click anywhere on folder row also toggles (except the count which is passive)
const folderRow = e.target.closest('[data-tree-toggle]');
if (folderRow && !e.target.closest('.tree-count')) {
const node = folderRow.parentElement;
toggleTreeNode(node);
}
});
// Leaf selection (single-select within siblings)
document.addEventListener('click', (e) => {
const leaf = e.target.closest('[data-tree-select]');
if (!leaf) return;
e.stopPropagation();
// Remove .active from siblings within same parent tree
const tree = leaf.closest('.tree');
if (tree) {
tree.querySelectorAll('[data-tree-select].active').forEach(el => el.classList.remove('active'));
}
leaf.classList.add('active');
});
function toggleTreeNode(node) {
if (!node || !node.classList.contains('tree-node')) return;
const expanded = node.getAttribute('aria-expanded') === 'true';
node.setAttribute('aria-expanded', expanded ? 'false' : 'true');
const children = node.querySelector(':scope > .tree-children');
if (children) {
if (expanded) { children.setAttribute('hidden', ''); }
else { children.removeAttribute('hidden'); }
}
}
// Drag-and-drop for library leafs (visual only - mockup)
document.addEventListener('dragstart', (e) => {
const draggable = e.target.closest('.lib-draggable');
if (!draggable) return;
draggable.classList.add('dragging');
const name = draggable.querySelector('.tree-name')?.textContent || 'Block';
e.dataTransfer.setData('text/plain', name);
e.dataTransfer.effectAllowed = 'copy';
});
document.addEventListener('dragend', (e) => {
const draggable = e.target.closest('.lib-draggable');
if (draggable) draggable.classList.remove('dragging');
});
})();