feat: artifacts
This commit is contained in:
@@ -76,12 +76,60 @@ const snapToGrid = (x: number, y: number): { x: number; y: number } => ({
|
||||
y: Math.round(y / SNAP_GRID[1]) * SNAP_GRID[1],
|
||||
})
|
||||
|
||||
function FlowFitViewOnLoad() {
|
||||
function FlowFitViewOnLoad({ disabled }: { disabled?: boolean }) {
|
||||
const nodesInitialized = useNodesInitialized()
|
||||
const { fitView } = useReactFlow()
|
||||
React.useEffect(() => {
|
||||
if (nodesInitialized) fitView?.({ duration: 200 })
|
||||
}, [nodesInitialized, fitView])
|
||||
if (disabled || !nodesInitialized) return
|
||||
fitView?.({ duration: 200 })
|
||||
}, [disabled, nodesInitialized, fitView])
|
||||
return null
|
||||
}
|
||||
|
||||
function FocusNodeOnLoad({ focusNodeId }: { focusNodeId?: string }) {
|
||||
const nodesInitialized = useNodesInitialized()
|
||||
const { getNodes, setCenter, screenToFlowPosition, project } = useReactFlow()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!focusNodeId || !nodesInitialized) return
|
||||
// Small delay so node DOM and layout are fully ready before centering/zooming.
|
||||
const timer = setTimeout(() => {
|
||||
const nodes = getNodes()
|
||||
const node = nodes.find((n) => n.id === focusNodeId)
|
||||
if (!node) return
|
||||
|
||||
const el = document.querySelector(
|
||||
`.react-flow__node[data-id="${focusNodeId}"]`
|
||||
) as HTMLElement | null
|
||||
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect()
|
||||
const screenCenter = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
|
||||
const toFlow = screenToFlowPosition ?? project
|
||||
if (toFlow) {
|
||||
const flowCenter = toFlow(screenCenter)
|
||||
setCenter(flowCenter.x, flowCenter.y, { duration: 400, zoom: 1.8 })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: center using node position + dimensions from React Flow state.
|
||||
const anyNode = node as Node & {
|
||||
positionAbsolute?: { x: number; y: number }
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
const basePos = anyNode.positionAbsolute ?? anyNode.position ?? { x: 0, y: 0 }
|
||||
const width = anyNode.width ?? 0
|
||||
const height = anyNode.height ?? 0
|
||||
const centerX = basePos.x + width / 2
|
||||
const centerY = basePos.y + height / 2
|
||||
setCenter(centerX, centerY, { duration: 400, zoom: 1.8 })
|
||||
}, 150)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [focusNodeId, nodesInitialized, getNodes, setCenter, screenToFlowPosition, project])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -152,9 +200,11 @@ function FullscreenNodeContent({ node }: { node: AppNode }) {
|
||||
export type CanvasPageProps = {
|
||||
/** Optional recollection id for per-recollection graph loading */
|
||||
recollectionId?: string
|
||||
/** Optional node id to focus when the canvas loads (e.g. from Katalogos artifacts). */
|
||||
focusNodeId?: string
|
||||
}
|
||||
|
||||
export function CanvasPage({ recollectionId }: CanvasPageProps) {
|
||||
export function CanvasPage({ recollectionId, focusNodeId }: CanvasPageProps) {
|
||||
const { theme } = useTheme()
|
||||
const { showMinimap } = usePlatform()
|
||||
const {
|
||||
@@ -688,7 +738,8 @@ export function CanvasPage({ recollectionId }: CanvasPageProps) {
|
||||
)}
|
||||
<ReactFlowProvider initialNodes={nodes} initialEdges={edges} fitView>
|
||||
<ViewportDisplayProvider>
|
||||
<FlowFitViewOnLoad />
|
||||
<FlowFitViewOnLoad disabled={Boolean(focusNodeId)} />
|
||||
<FocusNodeOnLoad focusNodeId={focusNodeId} />
|
||||
<FlowKeyboardShortcuts />
|
||||
<ReactFlow
|
||||
className={isPanning ? 'react-flow--panning' : isSelecting ? 'react-flow--selecting' : undefined}
|
||||
|
||||
Reference in New Issue
Block a user