From 47786acdf572b52d588cbf5722b88b3089d33cec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:15:08 +0000 Subject: [PATCH 1/2] chore: update submodules to latest versions --- frontend/src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/bindings b/frontend/src/bindings index 6285c0c..568e9bb 160000 --- a/frontend/src/bindings +++ b/frontend/src/bindings @@ -1 +1 @@ -Subproject commit 6285c0c88e6f515d519fe01860f3f6770b2274e4 +Subproject commit 568e9bbe5a3c5961e103557d286a100a06805fd2 From 9c193d82f5b960c59fefc3517c4eb1867fb33614 Mon Sep 17 00:00:00 2001 From: wizjany Date: Fri, 5 Jun 2026 17:46:20 -0400 Subject: [PATCH 2/2] Placeable enhancements. * Consider placeable interaction outputs as well as inputs. * Better routing/layout for placeable graph. --- .../src/components/shared/PlaceableGraph.tsx | 194 +++++++++++++----- frontend/src/lib/placeables.ts | 5 + 2 files changed, 147 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/shared/PlaceableGraph.tsx b/frontend/src/components/shared/PlaceableGraph.tsx index 783bb87..83e7c33 100644 --- a/frontend/src/components/shared/PlaceableGraph.tsx +++ b/frontend/src/components/shared/PlaceableGraph.tsx @@ -242,9 +242,57 @@ function detourPath( return {d, mid}; } +/** + * Same-column edge: rectangular path that routes between nodes vertically. + * Path: exits right from source → goes vertical to target level → + * goes left back to column → enters from left into target. + * + * This creates an upside-down U or right-side U that curves around + * intermediate nodes in the same column without doubling back. + */ +function sameColumnPath( + srcX: number, srcY: number, + tgtX: number, tgtY: number, +): { d: string; mid: { x: number; y: number } } { + const hw = NODE_W / 2; + const pad = 25; // How far right to swing + + // Exit and entry points + const exitX = srcX + hw; + const entryX = tgtX - hw; // = srcX - hw (same column) + const rightX = srcX + hw + pad; + const leftX = srcX - hw - pad; + const flipY = tgtY - (tgtY > srcY ? 1 : -1) * (NODE_H / 2); + + // Path: right → vertical → left + const r = 7; + const v = tgtY > srcY ? r : -r; + const d = [ + `M ${exitX} ${srcY}`, + `L ${rightX - r} ${srcY}`, + `Q ${rightX} ${srcY}, ${rightX} ${srcY + v}`, + `L ${rightX} ${flipY - v}`, + `Q ${rightX} ${flipY}, ${rightX - r} ${flipY}`, + `L ${leftX + r} ${flipY}`, + `Q ${leftX} ${flipY}, ${leftX} ${flipY + v}`, + `L ${leftX} ${tgtY - v}`, + `Q ${leftX} ${tgtY}, ${leftX + r} ${tgtY}`, + `L ${entryX} ${tgtY}` + ].join(" "); + + // Label positioned on the right side at the midpoint height + const mid = { + x: rightX, + y: flipY, + }; + + return {d, mid}; +} + /** * Compute all routed edges. Edges spanning multiple columns with intermediate * nodes get detour paths; others get direct quadratic bezier curves. + * Same-column edges get an S-curve to the right. * Self-loops get an arc path above the node. */ function computeRoutedEdges( @@ -310,7 +358,11 @@ function computeRoutedEdges( } } - if (!hasIntermediate) { + if (srcCol === tgtCol) { + // Same-column edge: rectangular path avoiding backward cut + const {d, mid} = sameColumnPath(src.x, src.y, tgt.x, tgt.y); + result.push({index: i, d, mid, label: e.label, tooltip: e.tooltip, edgeType: e.edgeType}); + } else if (!hasIntermediate) { // Direct edge with pair offset const offset = pairOffsets[i]; const d = directPath(src.x, src.y, tgt.x, tgt.y, offset); @@ -344,6 +396,30 @@ function computeRoutedEdges( } } + // Post-process: spread apart label midpoints that would visually overlap. + // This handles crossing edges (e.g. Na→N+1b and Nb→N+1a) whose midpoints + // coincide at the center of the quadrilateral formed by the four nodes. + const LABEL_COL_W = 52; // approximate label box half-width threshold + const LABEL_COL_H = 22; // approximate label box height + margin + for (let i = 0; i < result.length; i++) { + if (!result[i].label || result[i].isSelfLoop) continue; + for (let j = i + 1; j < result.length; j++) { + if (!result[j].label || result[j].isSelfLoop) continue; + const dx = Math.abs(result[i].mid.x - result[j].mid.x); + const dy = Math.abs(result[i].mid.y - result[j].mid.y); + if (dx < LABEL_COL_W && dy < LABEL_COL_H) { + const push = (LABEL_COL_H - dy) / 2 + 2; + if (result[i].mid.y <= result[j].mid.y) { + result[i] = {...result[i], mid: {...result[i].mid, y: result[i].mid.y - push}}; + result[j] = {...result[j], mid: {...result[j].mid, y: result[j].mid.y + push}}; + } else { + result[i] = {...result[i], mid: {...result[i].mid, y: result[i].mid.y + push}}; + result[j] = {...result[j], mid: {...result[j].mid, y: result[j].mid.y - push}}; + } + } + } + } + return result; } @@ -796,6 +872,30 @@ export function PlaceableGraph(props: PlaceableGraphProps) { onCleanup(() => svg.on(".zoom", null)); }); + const drawEdge= (edge: RoutedEdge, isHov: boolean) => { + const bw = () => Math.max(60, edge.label.length * 6.2 + 16); + const bh = 18; + return ( + + + + + {edge.label} + + + + ); + } + return (
- {/* Edge labels */} - - {(edge) => { - const bw = () => Math.max(60, edge.label.length * 6.2 + 16); - const bh = 18; - return ( - - - - - {edge.label} - - - - ); - }} - - - {/* Interaction hover tooltips */} + {/* Edge labels - non-hovered */} - {(edge) => { - const isHov = () => hoveredEdge() === edge.index; - return ( - - - - - {edge.tooltip} - - - - ); - }} + {(edge) => ( + + {drawEdge(edge, false)} + + )} {/* Nodes — foreignObject with GameIcon */} @@ -999,7 +1055,8 @@ export function PlaceableGraph(props: PlaceableGraphProps) { const desc = () => cargoIndex()?.get(node.gameId); return ( - {(d) => } + {/* hardcoded to use the same size as square icons */} + {(d) => } ); } else { @@ -1123,6 +1180,36 @@ export function PlaceableGraph(props: PlaceableGraphProps) { ); }} + + {/* Edge label - hovered */} + + {drawEdge(routedEdges()[hoveredEdge()!], true)} + + {/* Interaction hover tooltips */} + + {(edge) => { + const isHov = () => hoveredEdge() === edge.index; + return ( + + + + + {edge.tooltip} + + + + ); + }} +
@@ -1258,6 +1345,9 @@ export function buildPlaceableGraph(placementId: number): PlaceableGraphData { // Interactions const interactions = interactionsByPlaceable.get(plcId) ?? []; for (const ia of interactions) { + // only where this is the input + if (ia.placeableId !== plcId) continue; + // Tooltip: consumed items const tooltip = ia.consumedItemStacks.length ? ia.consumedItemStacks.map(s => { diff --git a/frontend/src/lib/placeables.ts b/frontend/src/lib/placeables.ts index b0d066b..00c4220 100644 --- a/frontend/src/lib/placeables.ts +++ b/frontend/src/lib/placeables.ts @@ -96,6 +96,11 @@ export function useInteractionsByPlaceable() { const arr = map.get(ia.placeableId); if (arr) arr.push(ia); else map.set(ia.placeableId, [ia]); + if (ia.onDestroySpawnedPlaceableId) { + const outArr = map.get(ia.onDestroySpawnedPlaceableId); + if (outArr) outArr.push(ia); + else map.set(ia.onDestroySpawnedPlaceableId, [ia]); + } } return map; });