feat(canvas): add frame name labels for top-level elements

This commit is contained in:
Fini 2026-02-19 01:21:18 +08:00
parent 2ab2a1d074
commit b9241db194
2 changed files with 66 additions and 0 deletions

View file

@ -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<HTMLCanvasElement>(null)
@ -18,6 +19,7 @@ export default function FabricCanvas() {
useCanvasSelection()
useCanvasSync()
useDimensionLabel(containerRef)
useFrameLabels()
return (
<div

View file

@ -0,0 +1,64 @@
import { useEffect } from 'react'
import { useCanvasStore } from '@/stores/canvas-store'
import { useDocumentStore } from '@/stores/document-store'
import type { FabricObjectWithPenId } from './canvas-object-factory'
const LABEL_FONT_SIZE = 12
const LABEL_OFFSET_Y = 6
const LABEL_COLOR = '#999999'
export function useFrameLabels() {
useEffect(() => {
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)
}, [])
}