T24: Use shared types, add 300ms debounce on Yjs updates in PropertiesPanel

This commit is contained in:
2026-06-22 23:51:00 +00:00
parent dc185135f0
commit 723869d65a
@@ -1,39 +1,8 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { CADLayer, CADProperties, CADElement } from '../../types/cad.types';
import './PropertiesPanel.css'; import './PropertiesPanel.css';
// ─── Types ─────────────────────────────────────────────────────────────────── // ─── Types (local-only, not duplicated from cad.types.ts) ─────────────────────
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;
}
interface YjsDocumentLike { interface YjsDocumentLike {
layers: { layers: {
@@ -112,6 +81,18 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
const [fabricObject, setFabricObject] = useState<FabricObjectLike | null>(null); const [fabricObject, setFabricObject] = useState<FabricObjectLike | null>(null);
const fabricCanvasRef = useRef<FabricCanvasLike | null>(null); const fabricCanvasRef = useRef<FabricCanvasLike | null>(null);
// Debounce timer for Yjs updates (300ms) — no new dependency needed
const yjsDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Clear debounce timer on unmount
useEffect(() => {
return () => {
if (yjsDebounceRef.current) {
clearTimeout(yjsDebounceRef.current);
}
};
}, []);
// ── Sync layers from YjsDocument ────────────────────────────────────────── // ── Sync layers from YjsDocument ──────────────────────────────────────────
useEffect(() => { useEffect(() => {
const yjsDoc = yjsDocRef?.current; const yjsDoc = yjsDocRef?.current;
@@ -215,19 +196,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
}; };
}, [canvasRef, yjsDocRef]); }, [canvasRef, yjsDocRef]);
// ── Update handlers (Panel → Canvas + YjsDocument) ───────────────────────── // ── Update handlers (Panel → Canvas + YjsDocument) ─────────────────────────
const updateFabricAndYjs = useCallback( const updateFabricAndYjs = useCallback(
(updates: Partial<CADElement> & { properties?: Partial<CADProperties> }) => { (updates: Partial<CADElement> & { properties?: Partial<CADProperties> }) => {
if (!selectedElement) return; if (!selectedElement) return;
// Update YjsDocument // Debounce Yjs write (300ms) — only write after user pauses typing
const yjsDoc = yjsDocRef?.current; if (yjsDebounceRef.current) {
if (yjsDoc) { clearTimeout(yjsDebounceRef.current);
yjsDoc.updateElement(selectedElement.id, updates);
} }
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 fabricObj = fabricObject;
const fabricCanvas = fabricCanvasRef.current; const fabricCanvas = fabricCanvasRef.current;
if (fabricObj && fabricCanvas) { if (fabricObj && fabricCanvas) {
@@ -251,7 +239,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
fabricCanvas.renderAll(); fabricCanvas.renderAll();
} }
// Update local state // Update local state (immediate, no debounce)
setSelectedElement((prev) => { setSelectedElement((prev) => {
if (!prev) return prev; if (!prev) return prev;
return { return {
@@ -264,7 +252,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
[selectedElement, fabricObject, yjsDocRef] [selectedElement, fabricObject, yjsDocRef]
); );
// ── Field change handlers ───────────────────────────────────────────────── // ── Field change handlers ─────────────────────────────────────────────────
const handleNumberChange = (field: 'x' | 'y' | 'width' | 'height', value: string) => { const handleNumberChange = (field: 'x' | 'y' | 'width' | 'height', value: string) => {
const n = clampNumber(value, -100000, 100000); const n = clampNumber(value, -100000, 100000);
@@ -297,7 +285,7 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
updateFabricAndYjs({ layerId: value }); updateFabricAndYjs({ layerId: value });
}; };
// ── Render ──────────────────────────────────────────────────────────────── // ── Render ──────────────────────────────────────────────────────────────────
if (!selectedElement) { if (!selectedElement) {
return ( return (