diff --git a/src/main/frontend/app/routes/studio/canvas/edgetypes/frank-edge.tsx b/src/main/frontend/app/routes/studio/canvas/edgetypes/frank-edge.tsx index c17de12b..ffe82b57 100644 --- a/src/main/frontend/app/routes/studio/canvas/edgetypes/frank-edge.tsx +++ b/src/main/frontend/app/routes/studio/canvas/edgetypes/frank-edge.tsx @@ -1,5 +1,6 @@ -import { BaseEdge, EdgeLabelRenderer, getBezierPath, type Position } from '@xyflow/react' +import { BaseEdge, EdgeLabelRenderer, getBezierPath, type Position, useStore } from '@xyflow/react' import useFlowStore from '~/stores/flow-store' +import { FlowConfig, getCompactLabelScale } from '~/routes/studio/canvas/flow.config' export type FrankEdgeProperties = { id: string @@ -50,6 +51,9 @@ export default function FrankEdge({ return '' }) + const zoom = useStore((state) => state.transform[2]) + const isCompact = zoom < FlowConfig.ZOOM_THRESHOLD + const [edgePath, labelX, labelY] = getBezierPath({ sourceX, sourceY, @@ -72,20 +76,22 @@ export default function FrankEdge({ }} className="nodrag flex flex-col items-center" > -

- {sourceHandleType} - {selected && ( - - )} -

+
+

+ {sourceHandleType} + {selected && ( + + )} +

+
diff --git a/src/main/frontend/app/routes/studio/canvas/flow.config.ts b/src/main/frontend/app/routes/studio/canvas/flow.config.ts index 59302f44..6ee99b24 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.config.ts +++ b/src/main/frontend/app/routes/studio/canvas/flow.config.ts @@ -14,4 +14,17 @@ export const FlowConfig = { MAX_HISTORY: 20, // Adjust this value as needed to limit the number of undo steps LAYOUT_HORIZONTAL_OFFSET: 300, LAYOUT_VERTICAL_OFFSET: 200, + ZOOM_THRESHOLD: 0.6, +} + +/** + * Counter-scale factor for compact-mode labels so they stay readable when zoomed out. + * + * The React Flow viewport scales all node content by `zoom`, so a fixed font size shrinks on + * screen as you zoom out. Multiplying a label's font size by this factor keeps its on-screen + * size roughly constant. Only called in compact mode (zoom < {@link FlowConfig.ZOOM_THRESHOLD}), + * so the result is always >= 1, and the canvas `minZoom` bounds how large it can get. + */ +export function getCompactLabelScale(zoom: number): number { + return FlowConfig.ZOOM_THRESHOLD / zoom } diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index 87a6e76a..5973a081 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -481,7 +481,7 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { const mouseEvent = event as MouseEvent if (!connectionState.isValid) { const zoom = reactFlow.getZoom() - if (zoom < 0.4 && sourceInfoReference.current.handleType === 'source') { + if (zoom < FlowConfig.ZOOM_THRESHOLD && sourceInfoReference.current.handleType === 'source') { const { nodes } = useFlowStore.getState() const sourceNode = nodes.find((node) => node.id === sourceInfoReference.current.nodeId) if (sourceNode && isFrankNode(sourceNode)) { @@ -500,7 +500,7 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { (connection: Connection) => { const zoom = reactFlow.getZoom() - if (zoom < 0.4 && connection.source) { + if (zoom < FlowConfig.ZOOM_THRESHOLD && connection.source) { const { nodes } = useFlowStore.getState() const sourceNode = nodes.find((node) => node.id === connection.source) @@ -1265,7 +1265,7 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { if (sourceInfo?.nodeId && sourceInfo.handleType === 'source') { const sourceNode = flowStore.nodes.find((node) => node.id === sourceInfo.nodeId) - if (reactFlow.getZoom() < 0.4 && sourceNode && isFrankNode(sourceNode)) { + if (reactFlow.getZoom() < FlowConfig.ZOOM_THRESHOLD && sourceNode && isFrankNode(sourceNode)) { if (edgeDropHandleType) { const existingHandle = sourceNode.data.sourceHandles.find((handle) => handle.type === edgeDropHandleType) if (existingHandle) { diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/exit-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/exit-node.tsx index 39ddd73c..c80c7795 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/exit-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/exit-node.tsx @@ -26,7 +26,7 @@ export default function ExitNodeComponent(properties: NodeProps) { const minNodeHeight = FlowConfig.EXIT_DEFAULT_HEIGHT const gradientEnabled = useSettingsStore((state) => state.studio.gradient) const zoom = useStore((state) => state.transform[2]) - const isCompact = zoom < 0.4 + const isCompact = zoom < FlowConfig.ZOOM_THRESHOLD const updateNodeInternals = useUpdateNodeInternals() useEffect(() => { @@ -35,13 +35,7 @@ export default function ExitNodeComponent(properties: NodeProps) { if (isCompact) { return ( - + ) } diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx index 497301a0..273e9e22 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx @@ -78,7 +78,7 @@ export default function FrankNode(properties: NodeProps) { } = useNodeContextStore() const gradientEnabled = useSettingsStore((state) => state.studio.gradient) const zoom = useStore((state) => state.transform[2]) - const isCompact = zoom < 0.4 + const isCompact = zoom < FlowConfig.ZOOM_THRESHOLD const [isOverflowing, setIsOverflowing] = useState(false) const frankElement = useMemo(() => { @@ -402,8 +402,6 @@ export default function FrankNode(properties: NodeProps) { return ( colorVariable: string selected?: boolean showTargetHandle?: boolean @@ -23,13 +22,10 @@ function getAbbreviation(subtype: string): string { /** * Compact representation of a node shown when the canvas is zoomed out far enough that the full - * node would be unreadable. Renders an initials box with the subtype, name attributes and the - * handles aligned under it. + * node would be unreadable. Renders an initials box with the subtype aligned under it. */ export default function ZoomedOutNode({ subtype, - name, - attributes, colorVariable, selected, showTargetHandle = true, @@ -37,6 +33,8 @@ export default function ZoomedOutNode({ width = FlowConfig.NODE_DEFAULT_WIDTH, }: Readonly) { const abbr = getAbbreviation(subtype) + const zoom = useStore((state) => state.transform[2]) + const labelFontSize = `${COMPACT_LABEL_BASE_FONT_PX * getCompactLabelScale(zoom)}px` const compactXOffsetPx = (width - COMPACT_INITIALS_BOX_SIZE) / 2 - COMPACT_HANDLE_SIZE - COMPACT_HANDLE_GAP const compactHandleTop = @@ -65,15 +63,9 @@ export default function ZoomedOutNode({ - {subtype} - - {name && {name}} - {attributes && - Object.entries(attributes).map(([key, value]) => ( - - {value || key} - - ))} + + {subtype} + {showTargetHandle && (