|
11 | 11 | import { showTooltip, hideTooltip } from '$lib/components/Tooltip.svelte'; |
12 | 12 | import { paramInput } from '$lib/actions/paramInput'; |
13 | 13 | import { plotDataStore } from '$lib/plotting/processing/plotDataStore'; |
14 | | - import { NODE } from '$lib/constants/dimensions'; |
| 14 | + import { NODE, snapTo2G, getPortPositionCalc } from '$lib/constants/dimensions'; |
15 | 15 | import PlotPreview from './PlotPreview.svelte'; |
16 | 16 |
|
17 | 17 | interface Props { |
|
26 | 26 | const typeDef = $derived(nodeRegistry.get(data.type)); |
27 | 27 | const category = $derived(typeDef?.category || 'Algebraic'); |
28 | 28 |
|
| 29 | + // Get valid pinned params (filter out any that no longer exist in the type definition) |
| 30 | + // Defined early since it's needed for dimension calculations |
| 31 | + const validPinnedParams = $derived(() => { |
| 32 | + if (!data.pinnedParams?.length || !typeDef) return []; |
| 33 | + const paramNames = new Set(typeDef.params.map(p => p.name)); |
| 34 | + return data.pinnedParams.filter(name => paramNames.has(name)); |
| 35 | + }); |
| 36 | +
|
29 | 37 | // Recording node hover preview |
30 | 38 | const isRecordingNode = $derived(category === 'Recording'); |
31 | 39 | let isHovered = $state(false); |
|
127 | 135 |
|
128 | 136 | const maxPortsOnSide = $derived(Math.max(data.inputs.length, data.outputs.length)); |
129 | 137 |
|
130 | | - // For horizontal layout: height grows with ports; for vertical: width grows |
131 | | - const nodeHeight = $derived(isVertical ? NODE.baseHeight : Math.max(NODE.baseHeight, maxPortsOnSide * NODE.portSpacing + 10)); |
132 | | - const nodeWidth = $derived(isVertical ? Math.max(NODE.baseWidth, maxPortsOnSide * NODE.portSpacing + 20) : NODE.baseWidth); |
133 | | -
|
134 | | - // Calculate port positions using percentages for proper centering |
135 | | - function getPortPosition(index: number, total: number): string { |
136 | | - if (total === 1) return '50%'; |
137 | | - // Distribute evenly with padding from edges |
138 | | - const percent = ((index + 1) / (total + 1)) * 100; |
139 | | - return `${percent}%`; |
140 | | - } |
| 138 | + // Minimum node dimensions based on port count (grid-aligned to 2G) |
| 139 | + // Content can expand beyond these minimums |
| 140 | + const minPortDimension = $derived(Math.max(1, maxPortsOnSide) * NODE.portSpacing); |
| 141 | + const minNodeHeight = $derived( |
| 142 | + isVertical |
| 143 | + ? snapTo2G(NODE.baseHeight) |
| 144 | + : snapTo2G(Math.max(NODE.baseHeight, minPortDimension)) |
| 145 | + ); |
| 146 | + const minNodeWidth = $derived( |
| 147 | + isVertical |
| 148 | + ? snapTo2G(Math.max(NODE.baseWidth, minPortDimension)) |
| 149 | + : snapTo2G(NODE.baseWidth) |
| 150 | + ); |
141 | 151 |
|
142 | 152 | // Check if this is a Subsystem or Interface node (using shapes utility) |
143 | 153 | const isSubsystemNode = $derived(isSubsystem(data)); |
|
193 | 203 | // Custom node color (defaults to pathsim-blue) |
194 | 204 | const nodeColor = $derived(data.color || 'var(--accent)'); |
195 | 205 |
|
196 | | - // Get valid pinned params (filter out any that no longer exist in the type definition) |
197 | | - const validPinnedParams = $derived(() => { |
198 | | - if (!data.pinnedParams?.length || !typeDef) return []; |
199 | | - const paramNames = new Set(typeDef.params.map(p => p.name)); |
200 | | - return data.pinnedParams.filter(name => paramNames.has(name)); |
201 | | - }); |
202 | | -
|
203 | 206 | // Handle pinned param change |
204 | 207 | function handlePinnedParamChange(paramName: string, value: string) { |
205 | 208 | graphStore.updateNodeParams(id, { [paramName]: value }); |
|
282 | 285 | class:preview-hovered={showPreview} |
283 | 286 | class:subsystem-type={isSubsystemType} |
284 | 287 | data-rotation={rotation} |
285 | | - style="min-width: {nodeWidth}px; min-height: {nodeHeight}px; --node-color: {nodeColor};" |
| 288 | + style="min-width: {minNodeWidth}px; min-height: {minNodeHeight}px; --node-color: {nodeColor};" |
286 | 289 | ondblclick={handleDoubleClick} |
287 | 290 | onmouseenter={handleMouseEnter} |
288 | 291 | onmouseleave={handleMouseLeave} |
|
361 | 364 | type="target" |
362 | 365 | position={inputPosition()} |
363 | 366 | id={port.id} |
364 | | - style={isVertical ? `left: ${getPortPosition(i, data.inputs.length)};` : `top: ${getPortPosition(i, data.inputs.length)};`} |
| 367 | + style={isVertical ? `left: ${getPortPositionCalc(i, data.inputs.length)};` : `top: ${getPortPositionCalc(i, data.inputs.length)};`} |
365 | 368 | class="handle handle-input" |
366 | 369 | onmouseenter={(e) => handleInputMouseEnter(e, port)} |
367 | 370 | onmouseleave={() => handleInputMouseLeave(port)} |
|
376 | 379 | type="source" |
377 | 380 | position={outputPosition()} |
378 | 381 | id={port.id} |
379 | | - style={isVertical ? `left: ${getPortPosition(i, data.outputs.length)};` : `top: ${getPortPosition(i, data.outputs.length)};`} |
| 382 | + style={isVertical ? `left: ${getPortPositionCalc(i, data.outputs.length)};` : `top: ${getPortPositionCalc(i, data.outputs.length)};`} |
380 | 383 | class="handle handle-output" |
381 | 384 | onmouseenter={(e) => handleOutputMouseEnter(e, port)} |
382 | 385 | onmouseleave={() => handleOutputMouseLeave(port)} |
|
388 | 391 | <style> |
389 | 392 | .node { |
390 | 393 | position: relative; |
391 | | - min-width: 90px; |
392 | | - min-height: 36px; |
| 394 | + /* Center node on its position point (node center = local origin) */ |
| 395 | + transform: translate(-50%, -50%); |
| 396 | + /* Dimensions set via inline style using grid constants */ |
| 397 | + display: flex; |
| 398 | + flex-direction: column; |
393 | 399 | background: var(--surface-raised); |
394 | 400 | border: 1px solid var(--edge); |
395 | 401 | font-size: 10px; |
396 | | - transition: all 0.15s ease; |
397 | 402 | overflow: visible; |
398 | 403 | } |
399 | 404 |
|
|
412 | 417 |
|
413 | 418 | .shape-diamond { |
414 | 419 | border-radius: 4px; |
415 | | - transform: rotate(45deg); |
| 420 | + /* Compose with center transform */ |
| 421 | + transform: translate(-50%, -50%) rotate(45deg); |
416 | 422 | } |
417 | 423 |
|
418 | 424 | .shape-diamond .node-content { |
|
447 | 453 | z-index: 1000 !important; |
448 | 454 | } |
449 | 455 |
|
450 | | - /* Inner wrapper for proper border-radius clipping */ |
| 456 | + /* Inner wrapper for content */ |
451 | 457 | .node-inner { |
452 | 458 | border-radius: inherit; |
453 | 459 | overflow: hidden; |
|
0 commit comments