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

Node search with Fuse.js.

Browse files
web/package-lock.json CHANGED
@@ -8,7 +8,8 @@
8
  "name": "vite-svelte-flow-template",
9
  "version": "0.0.0",
10
  "dependencies": {
11
- "@xyflow/svelte": "^0.0.40"
 
12
  },
13
  "devDependencies": {
14
  "@sveltejs/vite-plugin-svelte": "^3.0.2",
@@ -1386,6 +1387,14 @@
1386
  "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1387
  }
1388
  },
 
 
 
 
 
 
 
 
1389
  "node_modules/glob": {
1390
  "version": "7.2.3",
1391
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3048,6 +3057,11 @@
3048
  "dev": true,
3049
  "optional": true
3050
  },
 
 
 
 
 
3051
  "glob": {
3052
  "version": "7.2.3",
3053
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
 
8
  "name": "vite-svelte-flow-template",
9
  "version": "0.0.0",
10
  "dependencies": {
11
+ "@xyflow/svelte": "^0.0.40",
12
+ "fuse.js": "^7.0.0"
13
  },
14
  "devDependencies": {
15
  "@sveltejs/vite-plugin-svelte": "^3.0.2",
 
1387
  "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1388
  }
1389
  },
1390
+ "node_modules/fuse.js": {
1391
+ "version": "7.0.0",
1392
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
1393
+ "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
1394
+ "engines": {
1395
+ "node": ">=10"
1396
+ }
1397
+ },
1398
  "node_modules/glob": {
1399
  "version": "7.2.3",
1400
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
 
3057
  "dev": true,
3058
  "optional": true
3059
  },
3060
+ "fuse.js": {
3061
+ "version": "7.0.0",
3062
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
3063
+ "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q=="
3064
+ },
3065
  "glob": {
3066
  "version": "7.2.3",
3067
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
web/package.json CHANGED
@@ -19,6 +19,7 @@
19
  "vite": "^5.2.8"
20
  },
21
  "dependencies": {
22
- "@xyflow/svelte": "^0.0.40"
 
23
  }
24
  }
 
19
  "vite": "^5.2.8"
20
  },
21
  "dependencies": {
22
+ "@xyflow/svelte": "^0.0.40",
23
+ "fuse.js": "^7.0.0"
24
  }
25
  }
web/src/App.svelte CHANGED
@@ -47,17 +47,24 @@
47
  },
48
  ]);
49
 
50
- function onPaneContextMenu({ detail: { event } }) {
 
 
 
 
 
 
 
51
  event.preventDefault();
52
  const width = 500;
53
  const height = 200;
54
- showNodeSearch = {
55
  top: event.clientY < height - 200 ? event.clientY : undefined,
56
  left: event.clientX < width - 200 ? event.clientX : undefined,
57
  right: event.clientX >= width - 200 ? width - event.clientX : undefined,
58
  bottom: event.clientY >= height - 200 ? height - event.clientY : undefined
59
  };
60
- showNodeSearch = {
61
  top: event.clientY,
62
  left: event.clientX - 150,
63
  };
@@ -66,20 +73,27 @@
66
  nodes.update((n) => [...n, node]);
67
  }
68
 
69
- let showNodeSearch;
 
 
 
 
 
 
 
70
  </script>
71
 
72
  <div style:height="100vh">
73
  <SvelteFlow {nodes} {edges} {nodeTypes} fitView
74
- on:nodecontextmenu={onPaneContextMenu}
75
- on:panecontextmenu={onPaneContextMenu}
76
- on:paneclick={() => showNodeSearch = undefined}
77
- on:nodeclick={() => showNodeSearch = undefined}
78
  >
79
  <Background />
80
  <Controls />
81
  <Background />
82
  <MiniMap />
83
- {#if showNodeSearch}<NodeSearch on:add={addNode} pos={showNodeSearch} />{/if}
84
  </SvelteFlow>
85
  </div>
 
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
  };
 
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>
web/src/NodeSearch.svelte CHANGED
@@ -1,23 +1,72 @@
1
  <script lang="ts">
 
 
 
2
  export let pos;
3
- console.log({pos});
4
- function titleClicked() {
5
- expanded = !expanded;
 
 
 
 
 
 
 
 
 
6
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  </script>
8
 
9
  <div class="node-search"
10
  style="top: {pos.top}px; left: {pos.left}px; right: {pos.right}px; bottom: {pos.bottom}px;">
11
 
12
- Node Search!
 
 
 
 
 
 
 
 
13
  </div>
14
 
15
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  .node-search {
17
  position: absolute;
18
  width: 300px;
19
  z-index: 5;
20
- padding: 8px;
21
  border-radius: 4px;
22
  border: 1px solid #888;
23
  background-color: white;
 
1
  <script lang="ts">
2
+ import { createEventDispatcher, onMount } from 'svelte';
3
+ import Fuse from 'fuse.js'
4
+ const dispatch = createEventDispatcher();
5
  export let pos;
6
+ export let boxes;
7
+ let searchBox: HTMLInputElement;
8
+ let hits = [];
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') {
21
+ e.preventDefault();
22
+ selectedIndex = Math.min(selectedIndex + 1, hits.length - 1);
23
+ } else if (e.key === 'ArrowUp') {
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
+ }
31
+ }
32
+
33
  </script>
34
 
35
  <div class="node-search"
36
  style="top: {pos.top}px; left: {pos.left}px; right: {pos.right}px; bottom: {pos.bottom}px;">
37
 
38
+ <input
39
+ bind:this={searchBox}
40
+ on:input={onInput}
41
+ on:keydown={onKeyDown}
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
 
49
  <style>
50
+ input {
51
+ width: calc(100% - 26px);
52
+ font-size: 20px;
53
+ padding: 8px;
54
+ border-radius: 4px;
55
+ border: 1px solid #eee;
56
+ margin: 4px;
57
+ }
58
+ .search-result {
59
+ padding: 4px;
60
+ }
61
+ .search-result.selected {
62
+ background-color: #f80;
63
+ border-radius: 4px;
64
+ }
65
  .node-search {
66
  position: absolute;
67
  width: 300px;
68
  z-index: 5;
69
+ padding: 4px;
70
  border-radius: 4px;
71
  border: 1px solid #888;
72
  background-color: white;
web/src/app.css CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  :root {
2
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
  line-height: 1.5;
 
1
+ body {
2
+ margin: 0;
3
+ }
4
  :root {
5
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
6
  line-height: 1.5;