|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onDestroy } from 'svelte'; |
3 | 3 | import { Handle, Position, useUpdateNodeInternals } from '@xyflow/svelte'; |
4 | | - import { nodeRegistry, type NodeInstance } from '$lib/nodes'; |
| 4 | + import { nodeRegistry, registryVersion, type NodeInstance } from '$lib/nodes'; |
5 | 5 | import { getShapeCssClass, isSubsystem } from '$lib/nodes/shapes/index'; |
6 | 6 | import { NODE_TYPES } from '$lib/constants/nodeTypes'; |
7 | 7 | import { openNodeDialog } from '$lib/stores/nodeDialog'; |
|
33 | 33 | // Get SvelteFlow hook to trigger re-measurement when node size changes |
34 | 34 | const updateNodeInternals = useUpdateNodeInternals(); |
35 | 35 |
|
36 | | - // Get type definition |
37 | | - const typeDef = $derived(nodeRegistry.get(data.type)); |
| 36 | + // Get type definition. The registry isn't a reactive store on its own, |
| 37 | + // so we tick on registryVersion bumps (toolbox install/uninstall) and |
| 38 | + // re-read here. Without this, blocks loaded before their toolbox finished |
| 39 | + // bootstrapping would stay stuck rendering as (missing). |
| 40 | + let registryTick = $state(0); |
| 41 | + const unsubRegistry = registryVersion.subscribe((v) => (registryTick = v)); |
| 42 | + const typeDef = $derived.by(() => { |
| 43 | + registryTick; // dependency: re-read whenever the registry version bumps |
| 44 | + return nodeRegistry.get(data.type); |
| 45 | + }); |
38 | 46 | const category = $derived(typeDef?.category || 'Algebraic'); |
39 | 47 |
|
40 | 48 | // Get valid pinned params (filter out any that no longer exist in the type definition) |
|
109 | 117 | }); |
110 | 118 |
|
111 | 119 | onDestroy(() => { |
| 120 | + unsubRegistry(); |
112 | 121 | unsubscribePinned(); |
113 | 122 | unsubscribePlotData(); |
114 | 123 | unsubscribePortLabels(); |
|
333 | 342 | const shapeClass = $derived(() => typeDef ? getShapeCssClass(typeDef) : 'shape-default'); |
334 | 343 |
|
335 | 344 | // Custom node color (defaults to pathsim-blue) |
336 | | - const nodeColor = $derived(data.color || 'var(--accent)'); |
| 345 | + // Missing blocks (type not registered) override any custom color so the |
| 346 | + // whole block — name, handles, hover state — picks up the error red. |
| 347 | + const nodeColor = $derived( |
| 348 | + !typeDef && data.type !== NODE_TYPES.SUBSYSTEM && data.type !== NODE_TYPES.INTERFACE |
| 349 | + ? 'var(--error)' |
| 350 | + : data.color || 'var(--accent)' |
| 351 | + ); |
337 | 352 |
|
338 | 353 | // Handle pinned param change |
339 | 354 | function handlePinnedParamChange(paramName: string, value: string) { |
|
697 | 712 | font-size: 8px; |
698 | 713 | color: var(--text-muted); |
699 | 714 | margin-top: 2px; |
| 715 | + white-space: nowrap; |
| 716 | + overflow: hidden; |
| 717 | + text-overflow: ellipsis; |
700 | 718 | } |
701 | 719 |
|
702 | 720 | .node-content.has-icon { |
|
727 | 745 | } |
728 | 746 |
|
729 | 747 | .node-type.missing { |
730 | | - color: var(--warning); |
| 748 | + color: var(--error); |
| 749 | + font-weight: 500; |
731 | 750 | } |
732 | 751 |
|
733 | 752 | /* Visual marker for nodes whose block type isn't registered (e.g. file |
734 | | - loaded with a toolbox dependency the user hasn't installed). */ |
| 753 | + * loaded with a toolbox dependency the user hasn't installed). Same |
| 754 | + * shape as a normal block, just dressed in error red so it's obvious |
| 755 | + * something is wrong. */ |
735 | 756 | .node.missing-type { |
736 | | - --node-color: var(--warning); |
737 | | - opacity: 0.85; |
| 757 | + --node-color: var(--error); |
| 758 | + border-color: var(--error); |
| 759 | + background: var(--error-bg); |
738 | 760 | } |
739 | 761 |
|
740 | | - .node.missing-type .node-content, |
741 | | - .node.missing-type :global(.node-shape) { |
742 | | - border-style: dashed; |
| 762 | + /* Port handles: paint the outer pentagon red so the missing block |
| 763 | + * carries its error state out to its connections. The inner cutout |
| 764 | + * picks up the red-tinted body so it visually merges with the block. */ |
| 765 | + :global(.node.missing-type .svelte-flow__handle::before) { |
| 766 | + background: var(--error); |
| 767 | + } |
| 768 | + :global(.node.missing-type .svelte-flow__handle::after) { |
| 769 | + background: color-mix(in srgb, var(--error-bg) 60%, var(--surface-raised)); |
743 | 770 | } |
744 | 771 |
|
745 | 772 | /* Pinned parameters - rectangular, clipped by node-clip's overflow:hidden */ |
|
0 commit comments