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 becde7dc..5168e763 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) @@ -1262,7 +1262,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 39c0fcac..ef48c1af 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 @@ -17,17 +17,11 @@ 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 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 e5b45cb0..487f3d7a 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 && (