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 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<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
const [fabricObject, setFabricObject] = useState<FabricObjectLike | 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 ──────────────────────────────────────────
useEffect(() => {
const yjsDoc = yjsDocRef?.current;
@@ -215,19 +196,26 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
};
}, [canvasRef, yjsDocRef]);
// ── Update handlers (Panel → Canvas + YjsDocument) ─────────────────────────
// ── Update handlers (Panel → Canvas + YjsDocument) ─────────────────────────
const updateFabricAndYjs = useCallback(
(updates: Partial<CADElement> & { properties?: Partial<CADProperties> }) => {
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<PropertiesPanelProps> = ({ 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<PropertiesPanelProps> = ({ 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<PropertiesPanelProps> = ({ canvasRef, yjsDocRef
updateFabricAndYjs({ layerId: value });
};
// ── Render ────────────────────────────────────────────────────────────────
// ── Render ──────────────────────────────────────────────────────────────────
if (!selectedElement) {
return (