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 && (