diff --git a/frontend/src/components/PropertiesPanel/PropertiesPanel.tsx b/frontend/src/components/PropertiesPanel/PropertiesPanel.tsx index 5c7a5ca..f563c8d 100644 --- a/frontend/src/components/PropertiesPanel/PropertiesPanel.tsx +++ b/frontend/src/components/PropertiesPanel/PropertiesPanel.tsx @@ -1,39 +1,8 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; +import type { CADLayer, CADProperties, CADElement } from '../../types/cad.types'; import './PropertiesPanel.css'; -// ─── Types ─────────────────────────────────────────────────────────────────── - -interface CADLayer { - id: string; - name: string; - visible: boolean; - locked: boolean; - color?: string; - lineType?: string; - transparency?: number; - sortOrder?: number; -} - -interface CADProperties { - fill?: string; - stroke?: string; - strokeWidth?: number; - rotation?: number; - lineType?: 'solid' | 'dashed' | 'dotted'; - radius?: number; - [key: string]: unknown; -} - -interface CADElement { - id: string; - type: string; - layerId: string; - x: number; - y: number; - width: number; - height: number; - properties: CADProperties; -} +// ─── Types (local-only, not duplicated from cad.types.ts) ───────────────────── interface YjsDocumentLike { layers: { @@ -112,6 +81,18 @@ const PropertiesPanel: React.FC = ({ canvasRef, yjsDocRef const [fabricObject, setFabricObject] = useState(null); const fabricCanvasRef = useRef(null); + // Debounce timer for Yjs updates (300ms) — no new dependency needed + const yjsDebounceRef = useRef | null>(null); + + // Clear debounce timer on unmount + useEffect(() => { + return () => { + if (yjsDebounceRef.current) { + clearTimeout(yjsDebounceRef.current); + } + }; + }, []); + // ── Sync layers from YjsDocument ────────────────────────────────────────── useEffect(() => { const yjsDoc = yjsDocRef?.current; @@ -215,19 +196,26 @@ const PropertiesPanel: React.FC = ({ canvasRef, yjsDocRef }; }, [canvasRef, yjsDocRef]); - // ── Update handlers (Panel → Canvas + YjsDocument) ───────────────────────── + // ── Update handlers (Panel → Canvas + YjsDocument) ────────────────────────── const updateFabricAndYjs = useCallback( (updates: Partial & { properties?: Partial }) => { if (!selectedElement) return; - // Update YjsDocument - const yjsDoc = yjsDocRef?.current; - if (yjsDoc) { - yjsDoc.updateElement(selectedElement.id, updates); + // Debounce Yjs write (300ms) — only write after user pauses typing + if (yjsDebounceRef.current) { + clearTimeout(yjsDebounceRef.current); } + const elId = selectedElement.id; + yjsDebounceRef.current = setTimeout(() => { + const yjsDoc = yjsDocRef?.current; + if (yjsDoc) { + yjsDoc.updateElement(elId, updates); + } + yjsDebounceRef.current = null; + }, 300); - // Update fabric object directly + // Update fabric object directly (immediate, no debounce) const fabricObj = fabricObject; const fabricCanvas = fabricCanvasRef.current; if (fabricObj && fabricCanvas) { @@ -251,7 +239,7 @@ const PropertiesPanel: React.FC = ({ canvasRef, yjsDocRef fabricCanvas.renderAll(); } - // Update local state + // Update local state (immediate, no debounce) setSelectedElement((prev) => { if (!prev) return prev; return { @@ -264,7 +252,7 @@ const PropertiesPanel: React.FC = ({ canvasRef, yjsDocRef [selectedElement, fabricObject, yjsDocRef] ); - // ── Field change handlers ───────────────────────────────────────────────── + // ── Field change handlers ────────────────────────────────────────────────── const handleNumberChange = (field: 'x' | 'y' | 'width' | 'height', value: string) => { const n = clampNumber(value, -100000, 100000); @@ -297,7 +285,7 @@ const PropertiesPanel: React.FC = ({ canvasRef, yjsDocRef updateFabricAndYjs({ layerId: value }); }; - // ── Render ──────────────────────────────────────────────────────────────── + // ── Render ────────────────────────────────────────────────────────────────── if (!selectedElement) { return (