darabos commited on
Commit
4237e78
·
1 Parent(s): 5427205

Create nodes at the search location.

Browse files
web/src/App.svelte CHANGED
@@ -1,99 +1,10 @@
1
  <script lang="ts">
2
- import { writable } from 'svelte/store';
3
  import {
4
- SvelteFlow,
5
- Controls,
6
- Background,
7
- MiniMap,
8
- MarkerType,
9
- Position,
10
- type Node,
11
- type Edge,
12
  } from '@xyflow/svelte';
13
- import LynxKiteNode from './LynxKiteNode.svelte';
14
- import NodeSearch from './NodeSearch.svelte';
15
- import '@xyflow/svelte/dist/style.css';
16
-
17
- const nodeTypes: NodeTypes = {
18
- basic: LynxKiteNode,
19
- };
20
-
21
- const nodes = writable<Node[]>([
22
- {
23
- id: '1',
24
- type: 'basic',
25
- data: { title: 'Compute PageRank', params: { damping: 0.85, iterations: 3 } },
26
- position: { x: 0, y: 0 },
27
- sourcePosition: Position.Right,
28
- targetPosition: Position.Left,
29
- },
30
- {
31
- id: '3',
32
- type: 'basic',
33
- data: { title: 'Import Parquet', params: { filename: '/tmp/x.parquet' } },
34
- position: { x: -300, y: 0 },
35
- sourcePosition: Position.Right,
36
- },
37
- ]);
38
-
39
- const edges = writable<Edge[]>([
40
- {
41
- id: '3-1',
42
- source: '3',
43
- target: '1',
44
- markerEnd: {
45
- type: MarkerType.ArrowClosed,
46
- },
47
- },
48
- ]);
49
-
50
- function closeNodeSearch() {
51
- nodeSearchPos = undefined;
52
- }
53
- function toggleNodeSearch({ detail: { event } }) {
54
- if (nodeSearchPos) {
55
- closeNodeSearch();
56
- return;
57
- }
58
- event.preventDefault();
59
- const width = 500;
60
- const height = 200;
61
- nodeSearchPos = {
62
- top: event.clientY < height - 200 ? event.clientY : undefined,
63
- left: event.clientX < width - 200 ? event.clientX : undefined,
64
- right: event.clientX >= width - 200 ? width - event.clientX : undefined,
65
- bottom: event.clientY >= height - 200 ? height - event.clientY : undefined
66
- };
67
- nodeSearchPos = {
68
- top: event.clientY,
69
- left: event.clientX - 150,
70
- };
71
- }
72
- function addNode(node: Node) {
73
- nodes.update((n) => [...n, node]);
74
- }
75
-
76
- const boxes = [
77
- { name: 'Compute PageRank' },
78
- { name: 'Import Parquet' },
79
- { name: 'Export Parquet' },
80
- { name: 'Export CSV' },
81
- ];
82
-
83
- let nodeSearchPos;
84
  </script>
85
 
86
- <div style:height="100vh">
87
- <SvelteFlow {nodes} {edges} {nodeTypes} fitView
88
- on:paneclick={toggleNodeSearch}
89
- proOptions={{ hideAttribution: true }}
90
- maxZoom={1.5}
91
- minZoom={0.3}
92
- >
93
- <Background />
94
- <Controls />
95
- <Background />
96
- <MiniMap />
97
- {#if nodeSearchPos}<NodeSearch boxes={boxes} on:cancel={closeNodeSearch} on:add={addNode} pos={nodeSearchPos} />{/if}
98
- </SvelteFlow>
99
- </div>
 
1
  <script lang="ts">
 
2
  import {
3
+ SvelteFlowProvider,
 
 
 
 
 
 
 
4
  } from '@xyflow/svelte';
5
+ import LynxKiteFlow from './LynxKiteFlow.svelte';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  </script>
7
 
8
+ <SvelteFlowProvider>
9
+ <LynxKiteFlow />
10
+ </SvelteFlowProvider>
 
 
 
 
 
 
 
 
 
 
 
web/src/LynxKiteFlow.svelte ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { writable } from 'svelte/store';
3
+ import {
4
+ SvelteFlow,
5
+ Controls,
6
+ Background,
7
+ MiniMap,
8
+ MarkerType,
9
+ Position,
10
+ useSvelteFlow,
11
+ type XYPosition,
12
+ type Node,
13
+ type Edge,
14
+ } from '@xyflow/svelte';
15
+ import LynxKiteNode from './LynxKiteNode.svelte';
16
+ import NodeSearch from './NodeSearch.svelte';
17
+ import '@xyflow/svelte/dist/style.css';
18
+
19
+ const { screenToFlowPosition } = useSvelteFlow();
20
+ const nodeTypes: NodeTypes = {
21
+ basic: LynxKiteNode,
22
+ };
23
+
24
+ const nodes = writable<Node[]>([
25
+ {
26
+ id: '1',
27
+ type: 'basic',
28
+ data: { title: 'Compute PageRank', params: { damping: 0.85, iterations: 3 } },
29
+ position: { x: 0, y: 0 },
30
+ sourcePosition: Position.Right,
31
+ targetPosition: Position.Left,
32
+ },
33
+ {
34
+ id: '3',
35
+ type: 'basic',
36
+ data: { title: 'Import Parquet', params: { filename: '/tmp/x.parquet' } },
37
+ position: { x: -300, y: 0 },
38
+ sourcePosition: Position.Right,
39
+ },
40
+ ]);
41
+
42
+ const edges = writable<Edge[]>([
43
+ {
44
+ id: '3-1',
45
+ source: '3',
46
+ target: '1',
47
+ markerEnd: {
48
+ type: MarkerType.ArrowClosed,
49
+ },
50
+ },
51
+ ]);
52
+
53
+ function closeNodeSearch() {
54
+ nodeSearchPos = undefined;
55
+ }
56
+ function toggleNodeSearch({ detail: { event } }) {
57
+ if (nodeSearchPos) {
58
+ closeNodeSearch();
59
+ return;
60
+ }
61
+ event.preventDefault();
62
+ const width = 500;
63
+ const height = 200;
64
+ nodeSearchPos = {
65
+ top: event.clientY < height - 200 ? event.clientY : undefined,
66
+ left: event.clientX < width - 200 ? event.clientX : undefined,
67
+ right: event.clientX >= width - 200 ? width - event.clientX : undefined,
68
+ bottom: event.clientY >= height - 200 ? height - event.clientY : undefined
69
+ };
70
+ nodeSearchPos = {
71
+ top: event.clientY,
72
+ left: event.clientX - 150,
73
+ };
74
+ }
75
+ function addNode(e) {
76
+ const node = {...e.detail};
77
+ nodes.update((n) => {
78
+ node.position = screenToFlowPosition({x: nodeSearchPos.left, y: nodeSearchPos.top});
79
+ const title = node.data.title;
80
+ let i = 1;
81
+ node.id = `${title} ${i}`;
82
+ while (n.find((x) => x.id === node.id)) {
83
+ i += 1;
84
+ node.id = `${title} ${i}`;
85
+ }
86
+ return [...n, node]
87
+ });
88
+ closeNodeSearch();
89
+ }
90
+
91
+ const boxes = [
92
+ {
93
+ type: 'basic',
94
+ data: { title: 'Import Parquet', params: { filename: '/tmp/x.parquet' } },
95
+ sourcePosition: Position.Right,
96
+ },
97
+ {
98
+ type: 'basic',
99
+ data: { title: 'Export Parquet', params: { filename: '/tmp/x.parquet' } },
100
+ sourcePosition: Position.Right,
101
+ },
102
+ {
103
+ type: 'basic',
104
+ data: { title: 'Export CSV', params: { filename: '/tmp/x.csv' } },
105
+ sourcePosition: Position.Right,
106
+ },
107
+ {
108
+ type: 'basic',
109
+ data: { title: 'Compute PageRank', params: { damping: 0.85, iterations: 3 } },
110
+ sourcePosition: Position.Right,
111
+ targetPosition: Position.Left,
112
+ },
113
+ ];
114
+
115
+ let nodeSearchPos: XYPosition | undefined = undefined
116
+ </script>
117
+
118
+ <div style:height="100vh">
119
+ <SvelteFlow {nodes} {edges} {nodeTypes} fitView
120
+ on:paneclick={toggleNodeSearch}
121
+ proOptions={{ hideAttribution: true }}
122
+ maxZoom={1.5}
123
+ minZoom={0.3}
124
+ >
125
+ <Background />
126
+ <Controls />
127
+ <Background />
128
+ <MiniMap />
129
+ {#if nodeSearchPos}<NodeSearch boxes={boxes} on:cancel={closeNodeSearch} on:add={addNode} pos={nodeSearchPos} />{/if}
130
+ </SvelteFlow>
131
+ </div>
web/src/NodeSearch.svelte CHANGED
@@ -9,12 +9,11 @@
9
  let selectedIndex = 0;
10
  onMount(() => searchBox.focus());
11
  const fuse = new Fuse(boxes, {
12
- keys: ['name']
13
  })
14
  function onInput() {
15
- console.log('input', searchBox.value, selectedIndex);
16
  hits = fuse.search(searchBox.value);
17
- selectedIndex = Math.min(selectedIndex, hits.length - 1);
18
  }
19
  function onKeyDown(e) {
20
  if (e.key === 'ArrowDown') {
@@ -24,7 +23,9 @@
24
  e.preventDefault();
25
  selectedIndex = Math.max(selectedIndex - 1, 0);
26
  } else if (e.key === 'Enter') {
27
- dispatch('add', {x: pos.left, y: pos.top, ...hits[selectedIndex].item});
 
 
28
  } else if (e.key === 'Escape') {
29
  dispatch('cancel');
30
  }
@@ -42,7 +43,7 @@ style="top: {pos.top}px; left: {pos.left}px; right: {pos.right}px; bottom: {pos.
42
  on:focusout={() => dispatch('cancel')}
43
  placeholder="Search for box">
44
  {#each hits as box, index}
45
- <div class="search-result" class:selected={index == selectedIndex}>{index} {box.item.name}</div>
46
  {/each}
47
  </div>
48
 
 
9
  let selectedIndex = 0;
10
  onMount(() => searchBox.focus());
11
  const fuse = new Fuse(boxes, {
12
+ keys: ['data.title']
13
  })
14
  function onInput() {
 
15
  hits = fuse.search(searchBox.value);
16
+ selectedIndex = Math.max(0, Math.min(selectedIndex, hits.length - 1));
17
  }
18
  function onKeyDown(e) {
19
  if (e.key === 'ArrowDown') {
 
23
  e.preventDefault();
24
  selectedIndex = Math.max(selectedIndex - 1, 0);
25
  } else if (e.key === 'Enter') {
26
+ const node = {...hits[selectedIndex].item};
27
+ node.position = {x: pos.left, y: pos.top};
28
+ dispatch('add', node);
29
  } else if (e.key === 'Escape') {
30
  dispatch('cancel');
31
  }
 
43
  on:focusout={() => dispatch('cancel')}
44
  placeholder="Search for box">
45
  {#each hits as box, index}
46
+ <div class="search-result" class:selected={index == selectedIndex}>{index} {box.item.data.title}</div>
47
  {/each}
48
  </div>
49