import { useCallback } from 'react'; import type { ProjectDocument, CanvasComponent, ComponentType, Page, } from '../types/project'; // ── Default sizes per component type ───────────────────────────────────────── export const DEFAULT_SIZES: Record = { Label: { width: 200, height: 32 }, Button: { width: 120, height: 40 }, TextInput: { width: 240, height: 40 }, JsonViewer: { width: 400, height: 200 }, Dropdown: { width: 260, height: 72 }, Table: { width: 520, height: 260 }, }; // ── Default label text per component type ───────────────────────────────────── const DEFAULT_LABELS: Record = { Label: 'Label', Button: 'Button', TextInput: '', JsonViewer: '', Dropdown: 'Dropdown', Table: 'Table', }; // ── ID counter (session-stable, not persistent) ─────────────────────────────── let _counter = 1; export function nextComponentId(type: ComponentType): string { return `${type.toLowerCase()}_${_counter++}`; } // ── Grid snap helper ────────────────────────────────────────────────────────── const GRID = 8; export function snap(v: number): number { return Math.round(v / GRID) * GRID; } // ── Hook ────────────────────────────────────────────────────────────────────── // This hook owns canvas mutation logic only. // The doc state is owned by ProjectContext and passed in via doc/setDoc. export type CanvasActions = { activePage: Page; addComponent: (type: ComponentType, x: number, y: number) => void; moveComponent: (id: string, dx: number, dy: number) => void; removeComponent: (id: string) => void; updateComponentProperty: (id: string, key: string, value: unknown) => void; updateComponentName: (id: string, name: string) => void; updateComponentSize: (id: string, width: number, height: number) => void; }; export function useCanvasActions( doc: ProjectDocument, setDoc: React.Dispatch>, ): CanvasActions { // Always operate on the first page for this MVP step const activePage = doc.project.pages[0]; const updatePage = useCallback( (updater: (p: Page) => Page) => { setDoc((prev) => ({ ...prev, project: { ...prev.project, pages: prev.project.pages.map((p, i) => (i === 0 ? updater(p) : p)), }, })); }, [setDoc], ); const addComponent = useCallback( (type: ComponentType, x: number, y: number) => { const id = nextComponentId(type); const size = DEFAULT_SIZES[type]; const label = DEFAULT_LABELS[type]; const properties: CanvasComponent['properties'] = { label, visible: true, disabled: false }; if (type === 'TextInput') properties.placeholder = 'Enter text…'; if (type === 'Dropdown') { properties.placeholder = 'Select an option'; properties.options = []; properties.value = ''; } if (type === 'Table') { properties.columns = []; properties.rows = []; } const component: CanvasComponent = { id, type, name: id, position: { x: snap(x), y: snap(y) }, size, properties, }; updatePage((p) => ({ ...p, components: [...p.components, component] })); }, [updatePage], ); const moveComponent = useCallback( (id: string, dx: number, dy: number) => { updatePage((p) => ({ ...p, components: p.components.map((c) => c.id === id ? { ...c, position: { x: snap(Math.max(0, c.position.x + dx)), y: snap(Math.max(0, c.position.y + dy)), }, } : c, ), })); }, [updatePage], ); const removeComponent = useCallback( (id: string) => { updatePage((p) => ({ ...p, components: p.components.filter((c) => c.id !== id), })); }, [updatePage], ); const updateComponentProperty = useCallback( (id: string, key: string, value: unknown) => { updatePage((p) => ({ ...p, components: p.components.map((c) => c.id === id ? { ...c, properties: { ...c.properties, [key]: value } } : c, ), })); }, [updatePage], ); const updateComponentName = useCallback( (id: string, name: string) => { updatePage((p) => ({ ...p, components: p.components.map((c) => c.id === id ? { ...c, name } : c, ), })); }, [updatePage], ); const updateComponentSize = useCallback( (id: string, width: number, height: number) => { updatePage((p) => ({ ...p, components: p.components.map((c) => c.id === id ? { ...c, size: { width, height } } : c, ), })); }, [updatePage], ); return { activePage, addComponent, moveComponent, removeComponent, updateComponentProperty, updateComponentName, updateComponentSize, }; }