Skip to content

Commit 3495ef7

Browse files
committed
Extract preview-bounds geometry into shared utility, drop magic 42px cap
1 parent 9da963b commit 3495ef7

3 files changed

Lines changed: 102 additions & 23 deletions

File tree

src/lib/components/FlowUpdater.svelte

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
import { importFile } from '$lib/schema/fileOps';
1515
import { ALL_COMPONENT_EXTENSIONS } from '$lib/types/component';
1616
import { GRID_SIZE } from '$lib/constants/grid';
17+
import { pinnedPreviewsStore } from '$lib/stores/pinnedPreviews';
18+
import { plotDataStore } from '$lib/plotting/processing/plotDataStore';
19+
import { previewSideForRotation, extendBoundsForPreview } from '$lib/utils/previewBounds';
20+
import type { NodeInstance } from '$lib/types/nodes';
1721
1822
interface Props {
1923
pendingUpdates: string[];
@@ -33,6 +37,9 @@
3337
return;
3438
}
3539
40+
const previewsPinned = get(pinnedPreviewsStore);
41+
const plotState = get(plotDataStore);
42+
3643
// Calculate bounding box of all nodes, accounting for origin
3744
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
3845
for (const node of nodes) {
@@ -42,10 +49,19 @@
4249
const origin = (node.origin as [number, number]) ?? [0.5, 0.5];
4350
const left = node.position.x - width * origin[0];
4451
const top = node.position.y - height * origin[1];
45-
minX = Math.min(minX, left);
46-
minY = Math.min(minY, top);
47-
maxX = Math.max(maxX, left + width);
48-
maxY = Math.max(maxY, top + height);
52+
let bounds = { left, top, right: left + width, bottom: top + height };
53+
54+
// Extend bounds for pinned plot previews on recording blocks (Scope/Spectrum)
55+
if (previewsPinned && node.type === 'pathview' && plotState.plots.has(node.id)) {
56+
const data = node.data as NodeInstance;
57+
const rotation = (data.params?.['_rotation'] as number) || 0;
58+
bounds = extendBoundsForPreview(bounds, previewSideForRotation(rotation));
59+
}
60+
61+
minX = Math.min(minX, bounds.left);
62+
minY = Math.min(minY, bounds.top);
63+
maxX = Math.max(maxX, bounds.right);
64+
maxY = Math.max(maxY, bounds.bottom);
4965
}
5066
5167
// Add some padding around the nodes themselves

src/lib/components/nodes/BaseNode.svelte

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import { portLabelsStore } from '$lib/stores/portLabels';
1212
import { iconModeStore } from '$lib/stores/iconMode';
1313
import BlockIcon, { hasBlockIcon } from '$lib/components/icons/BlockIcon.svelte';
14+
import { PREVIEW_GAP, previewSideForRotation } from '$lib/utils/previewBounds';
1415
import { hoveredHandle, selectedNodeHighlight } from '$lib/stores/hoveredHandle';
1516
import { showTooltip, hideTooltip } from '$lib/components/Tooltip.svelte';
1617
import { paramInput } from '$lib/actions/paramInput';
@@ -215,19 +216,8 @@
215216
// Port is horizontal (left/right) or vertical (top/bottom)
216217
const isVertical = $derived(rotation === 1 || rotation === 3);
217218
218-
// Preview position: opposite side of inputs
219-
// rotation 0: inputs left → preview right
220-
// rotation 1: inputs top → preview bottom
221-
// rotation 2: inputs right → preview left
222-
// rotation 3: inputs bottom → preview top
223-
const previewPosition = $derived(() => {
224-
switch (rotation) {
225-
case 1: return 'bottom';
226-
case 2: return 'left';
227-
case 3: return 'top';
228-
default: return 'right';
229-
}
230-
});
219+
// Preview position: opposite side of inputs (rotation → side mapping is in utils)
220+
const previewPosition = $derived(() => previewSideForRotation(rotation));
231221
232222
const maxPortsOnSide = $derived(Math.max(data.inputs.length, data.outputs.length));
233223
const pinnedCount = $derived(validPinnedParams().length);
@@ -485,7 +475,7 @@
485475
class:show-labels={showPortLabels}
486476
class:missing-type={!typeDef && data.type !== NODE_TYPES.SUBSYSTEM && data.type !== NODE_TYPES.INTERFACE}
487477
data-rotation={rotation}
488-
style="width: {nodeDimensions.width}px; height: {nodeDimensions.height}px; --node-color: {nodeColor};"
478+
style="width: {nodeDimensions.width}px; height: {nodeDimensions.height}px; --node-color: {nodeColor}; --preview-gap: {PREVIEW_GAP}px;"
489479
ondblclick={handleDoubleClick}
490480
onmouseenter={handleMouseEnter}
491481
onmouseleave={handleMouseLeave}
@@ -799,7 +789,6 @@
799789
.node-icon {
800790
flex: 1;
801791
min-height: 0;
802-
max-height: 42px;
803792
margin-top: 1px;
804793
display: flex;
805794
align-items: center;
@@ -1057,28 +1046,28 @@
10571046
10581047
/* Preview position: right (default, inputs on left) */
10591048
.plot-preview-popup.preview-right {
1060-
left: calc(100% + 12px);
1049+
left: calc(100% + var(--preview-gap));
10611050
top: 50%;
10621051
transform: translateY(-50%);
10631052
}
10641053
10651054
/* Preview position: left (inputs on right) */
10661055
.plot-preview-popup.preview-left {
1067-
right: calc(100% + 12px);
1056+
right: calc(100% + var(--preview-gap));
10681057
top: 50%;
10691058
transform: translateY(-50%);
10701059
}
10711060
10721061
/* Preview position: top (inputs on bottom) */
10731062
.plot-preview-popup.preview-top {
1074-
bottom: calc(100% + 12px);
1063+
bottom: calc(100% + var(--preview-gap));
10751064
left: 50%;
10761065
transform: translateX(-50%);
10771066
}
10781067
10791068
/* Preview position: bottom (inputs on top) */
10801069
.plot-preview-popup.preview-bottom {
1081-
top: calc(100% + 12px);
1070+
top: calc(100% + var(--preview-gap));
10821071
left: 50%;
10831072
transform: translateX(-50%);
10841073
}

src/lib/utils/previewBounds.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Plot-preview popup geometry (Scope / Spectrum).
3+
*
4+
* Single source of truth for the gap between a recording block and its
5+
* pinned plot-preview popup, the side it appears on for each rotation,
6+
* and how those bounds extend the block's bounding box (used for fit-view).
7+
*/
8+
9+
import { PREVIEW_WIDTH, PREVIEW_HEIGHT } from '$lib/plotting/core/constants';
10+
11+
/** Pixel gap between block edge and preview popup. CSS reads this via the
12+
* `--preview-gap` custom property set on the block. */
13+
export const PREVIEW_GAP = 12;
14+
15+
export type PreviewSide = 'top' | 'right' | 'bottom' | 'left';
16+
17+
/** Side the preview appears on for a given block rotation (matches handle layout). */
18+
export function previewSideForRotation(rotation: number): PreviewSide {
19+
switch (rotation) {
20+
case 1:
21+
return 'bottom';
22+
case 2:
23+
return 'left';
24+
case 3:
25+
return 'top';
26+
default:
27+
return 'right';
28+
}
29+
}
30+
31+
export interface BlockBounds {
32+
left: number;
33+
top: number;
34+
right: number;
35+
bottom: number;
36+
}
37+
38+
/** Extend a block's bounding box by the preview popup placed on the given side. */
39+
export function extendBoundsForPreview(bounds: BlockBounds, side: PreviewSide): BlockBounds {
40+
const cx = (bounds.left + bounds.right) / 2;
41+
const cy = (bounds.top + bounds.bottom) / 2;
42+
const halfW = PREVIEW_WIDTH / 2;
43+
const halfH = PREVIEW_HEIGHT / 2;
44+
switch (side) {
45+
case 'right':
46+
return {
47+
left: bounds.left,
48+
top: Math.min(bounds.top, cy - halfH),
49+
right: bounds.right + PREVIEW_GAP + PREVIEW_WIDTH,
50+
bottom: Math.max(bounds.bottom, cy + halfH)
51+
};
52+
case 'left':
53+
return {
54+
left: bounds.left - PREVIEW_GAP - PREVIEW_WIDTH,
55+
top: Math.min(bounds.top, cy - halfH),
56+
right: bounds.right,
57+
bottom: Math.max(bounds.bottom, cy + halfH)
58+
};
59+
case 'bottom':
60+
return {
61+
left: Math.min(bounds.left, cx - halfW),
62+
top: bounds.top,
63+
right: Math.max(bounds.right, cx + halfW),
64+
bottom: bounds.bottom + PREVIEW_GAP + PREVIEW_HEIGHT
65+
};
66+
case 'top':
67+
return {
68+
left: Math.min(bounds.left, cx - halfW),
69+
top: bounds.top - PREVIEW_GAP - PREVIEW_HEIGHT,
70+
right: Math.max(bounds.right, cx + halfW),
71+
bottom: bounds.bottom
72+
};
73+
}
74+
}

0 commit comments

Comments
 (0)