Spaces:
Running
Running
File size: 5,659 Bytes
4237e78 f951d3a af53b62 4237e78 ba4663c 4237e78 abadddf c1a1d02 a6b7675 a18645a 3010d5b 801415b 4237e78 abadddf 4237e78 abadddf d4a220c abadddf af53b62 4237e78 c1a1d02 a6b7675 a18645a 3010d5b 801415b 4237e78 a18645a 05acf81 abadddf 05acf81 4237e78 3010d5b 4237e78 3010d5b 4237e78 3010d5b 4237e78 d4a220c 4237e78 d4a220c abb1488 d4a220c 3010d5b 4237e78 e7f244d 3010d5b e7f244d 3010d5b 4237e78 ca01fa3 4237e78 3010d5b e7f244d 3010d5b af53b62 9e91869 af53b62 abadddf af53b62 abadddf af53b62 9e91869 abadddf af53b62 ba4663c 05b0e84 ba4663c 3010d5b abb1488 3010d5b e7f244d 3010d5b ba4663c 4237e78 05acf81 4237e78 3010d5b 4237e78 abadddf 4237e78 ba4663c 767e953 4237e78 3010d5b 4237e78 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
<script lang="ts">
import { setContext } from 'svelte';
import { writable, derived } from 'svelte/store';
import {
SvelteFlow,
Controls,
Background,
MiniMap,
MarkerType,
useSvelteFlow,
type XYPosition,
type Node,
type Edge,
type Connection,
type NodeTypes,
} from '@xyflow/svelte';
import { useQuery, useMutation, useQueryClient } from '@sveltestack/svelte-query';
import NodeWithParams from './NodeWithParams.svelte';
import NodeWithVisualization from './NodeWithVisualization.svelte';
import NodeWithTableView from './NodeWithTableView.svelte';
import NodeWithSubFlow from './NodeWithSubFlow.svelte';
import NodeWithArea from './NodeWithArea.svelte';
import NodeSearch from './NodeSearch.svelte';
import '@xyflow/svelte/dist/style.css';
export let path = '';
const { screenToFlowPosition } = useSvelteFlow();
const queryClient = useQueryClient();
const backendWorkspace = useQuery(['workspace', path], async () => {
const res = await fetch(`/api/load?path=${path}`);
return res.json();
}, {staleTime: 10000, retry: false});
const mutation = useMutation(async(update) => {
const res = await fetch('/api/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(update),
});
return await res.json();
}, {
onSuccess: data => queryClient.setQueryData(['workspace', path], data),
})
const nodeTypes: NodeTypes = {
basic: NodeWithParams,
visualization: NodeWithVisualization,
table_view: NodeWithTableView,
sub_flow: NodeWithSubFlow,
area: NodeWithArea,
};
const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
let doNotSave = true;
$: if ($backendWorkspace.isSuccess) {
doNotSave = true; // Change is coming from the backend.
nodes.set(JSON.parse(JSON.stringify($backendWorkspace.data?.nodes || [])));
edges.set(JSON.parse(JSON.stringify($backendWorkspace.data?.edges || [])));
doNotSave = false;
}
function closeNodeSearch() {
nodeSearchSettings = undefined;
}
function toggleNodeSearch({ detail: { event } }) {
if (nodeSearchSettings) {
closeNodeSearch();
return;
}
event.preventDefault();
nodeSearchSettings = {
pos: { x: event.clientX, y: event.clientY },
boxes: $boxes,
};
}
function addNode(e) {
const meta = {...e.detail};
nodes.update((n) => {
const node = {
type: meta.type,
data: {
meta: meta,
title: meta.name,
params: Object.fromEntries(
Object.values(meta.params).map((p) => [p.name, p.default])),
},
};
node.position = screenToFlowPosition({x: nodeSearchSettings.pos.x, y: nodeSearchSettings.pos.y});
const title = node.data.title;
let i = 1;
node.id = `${title} ${i}`;
while (n.find((x) => x.id === node.id)) {
i += 1;
node.id = `${title} ${i}`;
}
node.parentId = nodeSearchSettings.parentId;
if (node.parentId) {
node.extent = 'parent';
const parent = n.find((x) => x.id === node.parentId);
node.position = { x: node.position.x - parent.position.x, y: node.position.y - parent.position.y };
}
return [...n, node]
});
closeNodeSearch();
}
const boxes = writable([]);
async function getBoxes() {
const res = await fetch('/api/catalog');
const j = await res.json();
boxes.set(j);
}
getBoxes();
let nodeSearchSettings: {
pos: XYPosition,
boxes: any[],
parentId: string,
};
const graph = derived([nodes, edges], ([nodes, edges]) => ({ nodes, edges }));
// Like JSON.stringify, but with keys sorted.
function orderedJSON(obj: any) {
const allKeys = new Set();
JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
return JSON.stringify(obj, Array.from(allKeys).sort());
}
graph.subscribe(async (g) => {
if (doNotSave) return;
const dragging = g.nodes.find((n) => n.dragging);
if (dragging) return;
g = JSON.parse(JSON.stringify(g));
for (const node of g.nodes) {
delete node.computed;
delete node.selected;
}
const ws = orderedJSON(g);
const bd = orderedJSON($backendWorkspace.data);
if (ws === bd) return;
$mutation.mutate({ path, ws: g });
});
function onconnect(connection: Connection) {
edges.update((edges) => {
// Only one source can connect to a given target.
return edges.filter((e) =>
e.source === connection.source
|| e.target !== connection.target
|| e.targetHandle !== connection.targetHandle);
});
}
function nodeClick(e) {
const node = e.detail.node;
const meta = node.data.meta;
if (!meta) return;
const sub_nodes = meta.sub_nodes;
if (!sub_nodes) return;
const event = e.detail.event;
if (event.target.classList.contains('title')) return;
nodeSearchSettings = {
pos: { x: event.clientX, y: event.clientY },
boxes: sub_nodes,
parentId: node.id,
};
}
</script>
<div style:height="100%">
<SvelteFlow {nodes} {edges} {nodeTypes} fitView
on:paneclick={toggleNodeSearch}
on:nodeclick={nodeClick}
proOptions={{ hideAttribution: true }}
maxZoom={3}
minZoom={0.3}
onconnect={onconnect}
defaultEdgeOptions={{ markerEnd: { type: MarkerType.Arrow } }}
>
<Controls />
<MiniMap />
{#if nodeSearchSettings}
<NodeSearch pos={nodeSearchSettings.pos} boxes={nodeSearchSettings.boxes} on:cancel={closeNodeSearch} on:add={addNode} />
{/if}
</SvelteFlow>
</div>
|