'use client'; import { useEffect } from 'react'; import Link from 'next/link'; import { UserButton } from '@clerk/nextjs'; import { Toolbar } from './Toolbar'; import { ArtboardNavigator } from '../navigator/ArtboardNavigator'; import { Canvas } from '../canvas/Canvas'; import { Inspector } from '../inspector/Inspector'; import { useHistory } from '@/store/history'; import { useCanvas, type Tool } from '@/store/canvas'; import { useViewport } from '@/store/viewport'; import { useTheme } from '@/store/theme'; import { useCanvasTheme } from '@/store/canvasTheme'; import { useWalkthrough } from '@/store/walkthrough'; interface AppChromeProps { workspaceId?: string; projectId?: string; workspaceName?: string; projectName?: string; } export function AppChrome({ workspaceId, projectId, workspaceName, projectName }: AppChromeProps) { const selectedArtboardId = useCanvas((s) => s.selectedArtboardId); const setContext = useCanvas((s) => s.setContext); const setActiveTool = useCanvas((s) => s.setActiveTool); const { mode: themeMode, toggle: toggleTheme } = useTheme(); const CT = useCanvasTheme(); const startTour = useWalkthrough((s) => s.start); // Push workspace/project IDs into the store so Canvas and Navigator can read them. useEffect(() => { if (workspaceId && projectId) setContext(workspaceId, projectId); }, [workspaceId, projectId, setContext]); // Per-workspace viewport persistence: restore saved pan/zoom on mount, // save current state back to localStorage on unmount. useEffect(() => { if (!workspaceId) return; const key = `originmain:viewport:${workspaceId}`; try { const raw = localStorage.getItem(key); if (raw) { const saved = JSON.parse(raw) as { panX: number; panY: number; zoom: number }; useViewport.getState().restore(saved); } } catch { /* ignore parse errors */ } return () => { const { panX, panY, zoom } = useViewport.getState(); localStorage.setItem(key, JSON.stringify({ panX, panY, zoom })); }; }, [workspaceId]); useEffect(() => { function onKeyDown(e: KeyboardEvent) { // Skip if user is typing in an input if ((e.target as HTMLElement).matches('input, textarea, [contenteditable]')) return; // Tool shortcuts (no modifier) if (!e.metaKey && !e.ctrlKey) { const toolKeys: Record = { v: 'select', h: 'pan', a: 'artboard', z: 'zone' }; const mapped = toolKeys[e.key.toLowerCase()]; if (mapped !== undefined) { e.preventDefault(); setActiveTool(mapped); return; } // Escape cancels active tool back to select if (e.key === 'Escape') { setActiveTool('select'); return; } } // Undo/Redo require a selected artboard if (!(e.metaKey || e.ctrlKey)) return; if (!selectedArtboardId) return; if (e.key === 'z' && !e.shiftKey) { e.preventDefault(); useHistory.getState().undo(selectedArtboardId); } else if ((e.key === 'z' && e.shiftKey) || e.key === 'y') { e.preventDefault(); useHistory.getState().redo(selectedArtboardId); } } window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, [selectedArtboardId, setActiveTool]); const showBreadcrumb = Boolean(workspaceId && projectId); return ( // Outer flex-column: breadcrumb on top (fixed height), inner grid fills the rest. // Breadcrumb lives OUTSIDE the grid so the Toolbar/panel gridRow placements // never shift regardless of whether the breadcrumb is visible.
{/* ── Breadcrumb bar ─────────────────────────────────── */} {showBreadcrumb && (
{/* Logo */} Originmain / Workspaces / {workspaceName ?? 'Workspace'} / {projectName ?? 'Canvas'} {/* Project settings link */} {workspaceId && projectId && ( (e.currentTarget.style.color = CT.itemHov)} onMouseLeave={e => (e.currentTarget.style.color = CT.dim)} > )}
{/* Tour trigger */} {/* Theme toggle */}
)} {/* ── Inner grid: Toolbar (row 1) + panels (row 2) ─── */} {/* Rows are always 44px + 1fr — breadcrumb never enters this grid. */}
); }