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 resizingRef = useRef<{ id: string; startX: number; startY: number; origW: number; origH: number } | null>(null) const rotatingRef = useRef<{ id: string; startX: number; startY: number; origDeg: 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 rot = rotatingRef.current if (rot) { const dx = e.clientX - rot.startX let next = rot.origDeg + dx if (!e.shiftKey) { next = Math.round(next / 15) * 15 } updateObject(rot.id, (o) => ({ ...o, rotate: next })) return } const r = resizingRef.current if (r) { const dx = e.clientX - r.startX const dy = e.clientY - r.startY const nw = Math.max(8, snap8(r.origW + dx)) const nh = Math.max(8, snap8(r.origH + dy)) updateObject(r.id, (o) => ({ ...o, width: nw, height: nh })) return } const d = draggingRef.current if (d) { 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 resizingRef.current = null rotatingRef.current = null }, []) const onMouseDownResize = useCallback((e: React.MouseEvent, o: WObject) => { e.stopPropagation() resizingRef.current = { id: o.id, startX: e.clientX, startY: e.clientY, origW: o.width, origH: o.height } }, []) const onMouseDownRotate = useCallback((e: React.MouseEvent, o: WObject) => { e.stopPropagation() rotatingRef.current = { id: o.id, startX: e.clientX, startY: e.clientY, origDeg: o.rotate } }, []) 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' }} > {selectedId === o.id && (
onMouseDownResize(e, o)} style={{ position: 'absolute', right: 0, bottom: 0, width: 10, height: 10, background: '#0ea5e9', cursor: 'nwse-resize' }} /> )} {selectedId === o.id && (
onMouseDownRotate(e, o)} style={{ position: 'absolute', left: '50%', top: -16, width: 10, height: 10, marginLeft: -5, background: '#0ea5e9', borderRadius: 5, cursor: 'grab' }} /> )}
)))}
) }