|
1 | 1 | <script lang="ts"> |
2 | 2 | import '@xyflow/svelte/dist/style.css'; |
3 | | - import { Background, Controls, type Edge, MarkerType, MiniMap, type Node, SvelteFlow } from "@xyflow/svelte"; |
4 | | - import { writable } from "svelte/store"; |
| 3 | + import { |
| 4 | + Background, |
| 5 | + Controls, |
| 6 | + type Edge, |
| 7 | + MarkerType, |
| 8 | + MiniMap, |
| 9 | + type Node, |
| 10 | + SvelteFlow, |
| 11 | + useSvelteFlow, |
| 12 | + } from "@xyflow/svelte"; |
5 | 13 | import { DISPOSE_EDGE, edgeTypes, FLOW_NODE, nodeTypes } from "$lib/model/svelte_flow"; |
6 | 14 | import { getLayoutedElements } from "$lib/components/flow/flow_utils"; |
7 | 15 | import { |
|
17 | 25 | import type { NodeCreatedHandler } from "$lib/components/add"; |
18 | 26 | import type { WorkflowWithNodes } from "$lib/model/flows"; |
19 | 27 | import FlowMenus from "$lib/components/flow/FlowMenus.svelte"; |
20 | | - import { useSvelteFlow } from "@xyflow/svelte"; |
21 | 28 |
|
22 | 29 | type Props = { |
23 | 30 | flow: WorkflowWithNodes |
|
42 | 49 | onsettingschange: (newSettings: NodeSettings) => updateSettings(apiProps, node._id, newSettings), |
43 | 50 | ondelete: () => { |
44 | 51 | deleteNode(apiProps, node._id) |
45 | | - .then(() => nodesStore.update(nodes => nodes.filter(n => n.id !== node._id))); |
| 52 | + .then(() => { |
| 53 | + nodesStore = nodesStore.filter(n => n.id !== node._id); |
| 54 | + }); |
46 | 55 | }, |
47 | 56 | heights, |
48 | 57 | widths, |
|
59 | 68 | id: `${node._id}:${next}`, |
60 | 69 | source: node._id, |
61 | 70 | target: next, |
62 | | - animated: true, |
63 | 71 | data: { |
64 | 72 | ondisconnect: () => disconnectNodes(apiProps, node._id, next) |
65 | 73 | } |
66 | 74 | } as Edge)) |
67 | 75 | ); |
68 | 76 |
|
69 | | - const nodesStore = writable<Node[]>(initialNodes); |
70 | | - const edgesStore = writable<Edge[]>(initialEdges); |
71 | | - const { fitView } = useSvelteFlow(); |
| 77 | + let nodesStore = $state.raw<Node[]>(initialNodes); |
| 78 | + let edgesStore = $state.raw<Edge[]>(initialEdges); |
72 | 79 |
|
73 | | - // never-ending promise while the layout is being calculated |
74 | | - let promise: Promise<void> = $state(new Promise(() => {})); |
| 80 | + const { promise, resolve } = Promise.withResolvers() |
75 | 81 |
|
76 | 82 | $effect(() => { |
77 | 83 | if (Object.keys(widths).length === initialNodes.length && Object.keys(heights).length === initialNodes.length) { |
78 | | - updateLayout(); |
| 84 | + updateLayout() |
| 85 | + .then(() => resolve(null)); |
79 | 86 | } |
80 | 87 | }) |
81 | 88 |
|
82 | | - function updateLayout() { |
83 | | - promise = getLayoutedElements( |
| 89 | + const svelteFlow = useSvelteFlow() |
| 90 | + async function updateLayout() { |
| 91 | + const layoutedNodes = (await getLayoutedElements( |
84 | 92 | initialNodes, |
85 | 93 | initialEdges, |
86 | 94 | widths, |
87 | 95 | heights, |
88 | 96 | { 'elk.direction': "RIGHT" } |
89 | | - ).then(({ nodes: layoutedNodes }) => { |
90 | | - // most readable TS/JS code |
91 | | - nodesStore.update(_ => layoutedNodes.map(d => ({ ...d, data: { ...d.data, initializing: false }}))); |
92 | | - // scuffed way to wait for the nodes to be updated before fitting the view |
93 | | - // this is necessary because an immediate call to `fitView` would slightly offset the view |
94 | | - let unsubscriber: () => void; |
95 | | - unsubscriber = nodesStore.subscribe((_) => { |
96 | | - if (!unsubscriber) return |
97 | | - fitView(); |
98 | | - unsubscriber() |
99 | | - }); |
100 | | - }) |
| 97 | + )).nodes |
| 98 | +
|
| 99 | + nodesStore = layoutedNodes.map(d => ({ ...d, data: { ...d.data, initializing: false } })); |
| 100 | + for (let i = 0; i < 10; i++) { |
| 101 | + await svelteFlow.fitView(); |
| 102 | + } |
101 | 103 | } |
102 | 104 |
|
103 | 105 | const addNode: NodeCreatedHandler = async (node: StandaloneNode) => { |
104 | 106 | const newNode = createNodeFromNode(node); |
105 | 107 | newNode.width = Object.values(widths).reduce((acc, val) => acc + val, 0) / Object.keys(widths).length; |
106 | | - nodesStore.update(nodes => [...nodes, newNode]); |
| 108 | + // do not use `.push` because Svelte 5 is totally bugged |
| 109 | + nodesStore = [...nodesStore, newNode]; |
107 | 110 | } |
108 | 111 | </script> |
109 | 112 |
|
110 | 113 | <SvelteFlow proOptions={{hideAttribution: true}} |
111 | 114 | {nodeTypes} |
112 | 115 | {edgeTypes} |
113 | | - nodes={nodesStore} |
114 | | - edges={edgesStore} |
| 116 | + fitView |
| 117 | + bind:nodes={nodesStore} |
| 118 | + bind:edges={edgesStore} |
115 | 119 | defaultEdgeOptions={{ |
116 | 120 | type: DISPOSE_EDGE, |
117 | 121 | markerEnd: { |
118 | 122 | type: MarkerType.Arrow, |
119 | 123 | width: 25, |
120 | 124 | height: 25 |
121 | | - } |
| 125 | + }, |
| 126 | + animated: true, |
122 | 127 | }} |
123 | 128 | class="svelte-flow" |
124 | 129 | onconnect={(event) => connectNodes(apiProps, event.source, event.target)} |
|
0 commit comments