From 2e16d32d5831a4d695821db31d2a5b85dc35ea4a Mon Sep 17 00:00:00 2001 From: Fini Date: Thu, 19 Feb 2026 01:21:18 +0800 Subject: [PATCH] feat(canvas): add frame name labels for top-level elements --- src/canvas/fabric-canvas.tsx | 2 ++ src/canvas/use-frame-labels.ts | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/canvas/use-frame-labels.ts diff --git a/src/canvas/fabric-canvas.tsx b/src/canvas/fabric-canvas.tsx index 39a31cfb1..46b2a9cdc 100644 --- a/src/canvas/fabric-canvas.tsx +++ b/src/canvas/fabric-canvas.tsx @@ -6,6 +6,7 @@ import { useCanvasViewport } from './use-canvas-viewport' import { useCanvasSelection } from './use-canvas-selection' import { useCanvasSync } from './use-canvas-sync' import { useDimensionLabel } from './use-dimension-label' +import { useFrameLabels } from './use-frame-labels' export default function FabricCanvas() { const canvasRef = useRef(null) @@ -18,6 +19,7 @@ export default function FabricCanvas() { useCanvasSelection() useCanvasSync() useDimensionLabel(containerRef) + useFrameLabels() return (
{ + const interval = setInterval(() => { + const canvas = useCanvasStore.getState().fabricCanvas + if (!canvas) return + clearInterval(interval) + + const onAfterRender = () => { + const el = canvas.lowerCanvasEl + if (!el) return + const ctx = el.getContext('2d') + if (!ctx) return + + const vpt = canvas.viewportTransform + if (!vpt) return + const zoom = vpt[0] + const dpr = el.width / el.offsetWidth + + const store = useDocumentStore.getState() + // Only top-level document children show labels + const topIds = new Set(store.document.children.map((c) => c.id)) + const objects = canvas.getObjects() as FabricObjectWithPenId[] + + ctx.save() + const fontSize = LABEL_FONT_SIZE * dpr + ctx.font = `500 ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` + ctx.fillStyle = LABEL_COLOR + + for (const obj of objects) { + if (!obj.penNodeId) continue + if (!topIds.has(obj.penNodeId)) continue + + const node = store.getNodeById(obj.penNodeId) + if (!node) continue + + const name = node.name ?? node.type + const x = ((obj.left ?? 0) * zoom + vpt[4]) * dpr + const y = ((obj.top ?? 0) * zoom + vpt[5]) * dpr + + ctx.fillText(name, x, y - LABEL_OFFSET_Y * dpr) + } + + ctx.restore() + } + + canvas.on('after:render', onAfterRender) + + return () => { + canvas.off('after:render', onAfterRender) + } + }, 100) + + return () => clearInterval(interval) + }, []) +}