import React, { useCallback, useMemo, useRef, useState } from 'react' import type { Frame, WObject } from '@/lib/wysiwyg/schema' function snap8(v: number) { return Math.round(v / 8) * 8 } export type CanvasProps = { frame: Frame onChange?: (next: Frame) => void } export function Canvas({ frame, onChange }: CanvasProps) { const [model, setModel] = useState(frame) const [selectedId, setSelectedId] = useState(model.layers[0]?.objects[0]?.id ?? null) const draggingRef = useRef<{ id: string; startX: number; startY: number; origX: number; origY: number } | null>(null) const selected = useMemo(() => { for (const layer of model.layers) { const obj = layer.objects.find((o) => o.id === selectedId) if (obj) return obj } return null }, [model, selectedId]) const updateObject = useCallback((id: string, updater: (o: WObject) => WObject) => { setModel((prev) => { const next: Frame = { ...prev, layers: prev.layers.map((ly) => ({ ...ly, objects: ly.objects.map((o) => (o.id === id ? updater(o) : o)) })) } onChange?.(next) return next }) }, [onChange]) const onKeyDown = useCallback((e: React.KeyboardEvent) => { if (!selected) return let dx = 0, dy = 0 if (e.key === 'ArrowLeft') dx = -1 else if (e.key === 'ArrowRight') dx = 1 else if (e.key === 'ArrowUp') dy = -1 else if (e.key === 'ArrowDown') dy = 1 else return e.preventDefault() updateObject(selected.id, (o) => { const nx = snap8(o.x + dx) const ny = snap8(o.y + dy) return { ...o, x: nx, y: ny } }) }, [selected, updateObject]) const onMouseDownObj = useCallback((e: React.MouseEvent, o: WObject) => { setSelectedId(o.id) draggingRef.current = { id: o.id, startX: e.clientX, startY: e.clientY, origX: o.x, origY: o.y } }, []) const onMouseMoveCanvas = useCallback((e: React.MouseEvent) => { const d = draggingRef.current if (!d) return const dx = e.clientX - d.startX const dy = e.clientY - d.startY const nx = snap8(d.origX + dx) const ny = snap8(d.origY + dy) updateObject(d.id, (o) => ({ ...o, x: nx, y: ny })) }, [updateObject]) const onMouseUpCanvas = useCallback(() => { draggingRef.current = null }, []) return (
{model.layers.map((layer) => layer.visible !== false && layer.objects.map((o) => (
onMouseDownObj(e, o)} style={{ position: 'absolute', left: o.x, top: o.y, width: o.width, height: o.height, transform: `rotate(${o.rotate}deg)`, outline: selectedId===o.id? '2px solid #0ea5e9':'none' }} /> )))}
) }