Browse Source

refactor client to multiple modules

master
Milan Pässler 2 months ago
parent
commit
5ab913763d
7 changed files with 269 additions and 221 deletions
  1. 4
    0
      .gitignore
  2. 1
    0
      client/index.html
  3. 173
    0
      client/src/canvas.js
  4. 18
    0
      client/src/force.js
  5. 52
    220
      client/src/index.js
  6. 19
    0
      client/src/state.js
  7. 2
    1
      client/style.css

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
1
+client/main.js
2
+client/main.min.js
3
+client/node_modules
4
+server/node_modules

+ 1
- 0
client/index.html View File

@@ -22,6 +22,7 @@
22 22
 				<input type="text" id="name">
23 23
 				<input type="submit" id="go" value="Go">
24 24
 			</form>
25
+			<div id="details"></div>
25 26
 			<div class="pusher"></div>
26 27
 			<a id="source-link" href="https://git.pbb.lc/petabyteboy/pkgvis/">
27 28
 				<h2>Fork me on Gitea</h2>

+ 173
- 0
client/src/canvas.js View File

@@ -0,0 +1,173 @@
1
+import { graph, selectedNode, setSelectedNode } from "./state";
2
+
3
+const dpr = window.devicePixelRatio || 1;
4
+const canvas = document.getElementById("canvas");
5
+const ctx = canvas.getContext("2d");
6
+const view = {
7
+  offsetX: 0,
8
+  offsetY: 0,
9
+  scale: 1,
10
+  dragging: false,
11
+  clicking: false,
12
+  pinching: false,
13
+  dragStartMouseX: 0,
14
+  dragStartMouseY: 0,
15
+  dragStartOffsetX: 0,
16
+  dragStartOffsetY: 0,
17
+  pinchStartDistance: 0,
18
+  pinchStartScale: 0,
19
+};
20
+
21
+const x = (p) => {
22
+  return Math.floor((p + view.offsetX) * view.scale * dpr + canvas.width / 2);
23
+};
24
+
25
+const y = (p) => {
26
+  return Math.floor((p + view.offsetY) * view.scale * dpr + canvas.height / 2);
27
+};
28
+
29
+const drawGraph = () => {
30
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
31
+  for (let link of graph.links) {
32
+    if (link.source === selectedNode) {
33
+      ctx.strokeStyle = "rgba(255, 50, 0, .8)";
34
+      ctx.lineWidth = 5;
35
+    } else if (link.target === selectedNode) {
36
+      ctx.strokeStyle = "rgba(0, 50, 255, .8)";
37
+      ctx.lineWidth = 5;
38
+    } else {
39
+      ctx.strokeStyle = "rgba(255, 255, 255, .2)";
40
+      ctx.lineWidth = 2;
41
+    }
42
+    ctx.beginPath();
43
+    ctx.moveTo(x(link.source.x), y(link.source.y));
44
+    ctx.lineTo(x(link.target.x), y(link.target.y));
45
+    ctx.stroke();
46
+  }
47
+  for (let node of nodesAsArray()) {
48
+    ctx.fillStyle = node === selectedNode ? "yellow" : "red";
49
+    ctx.fillRect(x(node.x), y(node.y), node.weight * view.scale * dpr, node.weight * view.scale * dpr);
50
+    ctx.fillStyle = "white";
51
+    ctx.font = `${node.weight * Math.sqrt(view.scale) / 10 * (1 + dpr) / 2}rem Sans`;
52
+    ctx.fillText(node.name, x(node.x), y(node.y));
53
+  }
54
+  window.requestAnimationFrame(drawGraph);
55
+};
56
+
57
+const nodesAsArray = () => {
58
+  return Object.keys(graph.nodes).map((key) => graph.nodes[key]);
59
+};
60
+
61
+const handleResize = () => {
62
+  canvas.width = window.innerWidth * dpr;
63
+  canvas.height = window.innerHeight * dpr;
64
+};
65
+
66
+const nodeAt = (xPos, yPos) => {
67
+  return nodesAsArray().filter(n => x(n.x) <= xPos && y(n.y) <= yPos
68
+    && x(n.x + n.weight) >= xPos && y(n.y + n.weight) >= yPos)[0];
69
+};
70
+
71
+const handleClick = (xPos, yPos) => {
72
+  const node = nodeAt(xPos, yPos);
73
+  if (!node) {
74
+    setSelectedNode(null);
75
+  } else {
76
+    setSelectedNode(node);
77
+  }
78
+};
79
+
80
+const handleWheel = (evt) => {
81
+  if (view.scale < evt.deltaY / 60) {
82
+    return;
83
+  }
84
+
85
+  view.scale -= evt.deltaY / 60;
86
+};
87
+
88
+const pinchDistance = (touchEvent) => {
89
+  return Math.abs(touchEvent.touches[0].pageX - touchEvent.touches[1].pageX) + Math.abs(touchEvent.touches[0].pageY - touchEvent.touches[1].pageY);
90
+};
91
+
92
+const mouseDownHandler = (x, y, touchEvent) => {
93
+  if (touchEvent && touchEvent.touches.length >= 2) {
94
+    view.pinching = true;
95
+    view.pinchStartDistance = pinchDistance(touchEvent);
96
+    view.pinchStartScale = view.scale;
97
+  }
98
+
99
+  view.dragging = true;
100
+  view.clicking = true;
101
+  view.dragStartMouseX = x;
102
+  view.dragStartMouseY = y;
103
+  view.draggedNode = nodeAt(x, y);
104
+
105
+  if (view.draggedNode) {
106
+    view.dragStartOffsetX = view.draggedNode.x;
107
+    view.dragStartOffsetY = view.draggedNode.y;
108
+    simulation.restart();
109
+  } else {
110
+    view.dragStartOffsetX = view.offsetX;
111
+    view.dragStartOffsetY = view.offsetY;
112
+  }
113
+};
114
+
115
+const mouseMoveHandler = (x, y, touchEvent) => {
116
+  if (touchEvent && view.pinching) {
117
+    view.scale = view.pinchStartScale * (pinchDistance(touchEvent) / view.pinchStartDistance);
118
+  }
119
+
120
+  if (view.dragging) {
121
+    if (Math.abs(view.dragStartMouseX - x) + Math.abs(view.dragStartMouseY - y) > 10) {
122
+      view.clicking = false;
123
+    }
124
+
125
+    if (view.draggedNode) {
126
+      view.draggedNode.fx = view.dragStartOffsetX - (view.dragStartMouseX - x) / view.scale;
127
+      view.draggedNode.fy = view.dragStartOffsetY - (view.dragStartMouseY - y) / view.scale;
128
+      simulation.alpha(0.5);
129
+    } else {
130
+      view.offsetX = view.dragStartOffsetX - (view.dragStartMouseX - x) / view.scale;
131
+      view.offsetY = view.dragStartOffsetY - (view.dragStartMouseY - y) / view.scale;
132
+    }
133
+  }
134
+};
135
+
136
+const mouseUpHandler = (x, y, touchEvent) => {
137
+  if (touchEvent) {
138
+    view.dragging = touchEvent.touches.length >= 1;
139
+    view.pinching = touchEvent.touches.length >= 2;
140
+    if (view.dragging) {
141
+      return;
142
+    }
143
+  }
144
+
145
+  view.dragging = false;
146
+  if (view.draggedNode) {
147
+    view.draggedNode.fx = undefined;
148
+    view.draggedNode.fy = undefined;
149
+    view.draggedNode = undefined;
150
+  }
151
+
152
+  if (view.clicking) {
153
+    handleClick(x, y);
154
+  }
155
+};
156
+
157
+/* initialization */
158
+
159
+if ("ontouchstart" in document.documentElement) {
160
+  canvas.addEventListener("touchstart", (evt) => mouseDownHandler(evt.touches[0].pageX, evt.touches[0].pageY, evt));
161
+  window.addEventListener("touchmove", (evt) => mouseMoveHandler(evt.touches[0].pageX, evt.touches[0].pageY, evt));
162
+  window.addEventListener("touchend", (evt) => mouseUpHandler(evt.changedTouches[0].pageX * dpr, evt.changedTouches[0].pageY * dpr, evt));
163
+} else {
164
+  canvas.addEventListener("mousedown", (evt) => mouseDownHandler(evt.pageX, evt.pageY));
165
+  window.addEventListener("mousemove", (evt) => mouseMoveHandler(evt.pageX, evt.pageY));
166
+  window.addEventListener("mouseup", (evt) => mouseUpHandler(evt.pageX, evt.pageY));
167
+}
168
+
169
+window.addEventListener("wheel", handleWheel);
170
+window.addEventListener("resize", handleResize);
171
+
172
+handleResize();
173
+drawGraph();

+ 18
- 0
client/src/force.js View File

@@ -0,0 +1,18 @@
1
+import { graph, nodesAsArray } from "./state";
2
+import * as d3Force from "d3-force";
3
+
4
+let simulation = d3Force.forceSimulation();
5
+let linkForce = d3Force.forceLink([]).id(d => String(d.id));
6
+
7
+export const updateGraph = () => {
8
+  simulation.nodes(nodesAsArray());
9
+  linkForce.links(graph.links);
10
+  simulation.alpha(0.5);
11
+  simulation.restart();
12
+};
13
+
14
+simulation
15
+  .force("link", linkForce)
16
+  .force("charge", d3Force.forceManyBody())
17
+  .force("center", d3Force.forceCenter());
18
+

+ 52
- 220
client/src/index.js View File

@@ -1,79 +1,58 @@
1
-import * as d3Force from 'd3-force';
1
+import { graph, selectedNode, setGraph, setSelectedNode } from "./state";
2
+import { updateGraph } from "./force";
3
+import "./canvas";
2 4
 
3 5
 const apiUri = "/api";
6
+const backendSelect = document.getElementById("backend");
7
+let currentRequestNumber = 0;
4 8
 
5
-const dpr = window.devicePixelRatio || 1;
6
-
7
-const view = {
8
-  offsetX: 0,
9
-  offsetY: 0,
10
-  scale: 1,
11
-  dragging: false,
12
-  clicking: false,
13
-  pinching: false,
14
-  dragStartMouseX: 0,
15
-  dragStartMouseY: 0,
16
-  dragStartOffsetX: 0,
17
-  dragStartOffsetY: 0,
18
-  pinchStartDistance: 0,
19
-  pinchStartScale: 0,
20
-};
21
-
22
-let graph = {
23
-  nodes: {},
24
-  links: []
25
-};
9
+const go = async (backend, name) => {
10
+  document.querySelector('#backend [value="' + backend + '"]').selected = true;
26 11
 
27
-let currentRequestNumber = 0;
12
+  document.getElementById("name").blur();
13
+  document.getElementById("name").value = name;
28 14
 
29
-let simulation = d3Force.forceSimulation();
30
-let linkForce = d3Force.forceLink([]).id(d => String(d.id));
31
-const canvas = document.getElementById('canvas');
32
-const ctx = canvas.getContext('2d');
33
-
34
-function x(p) {
35
-  return Math.floor((p + view.offsetX) * view.scale * dpr + canvas.width / 2);
36
-}
37
-
38
-function y(p) {
39
-  return Math.floor((p + view.offsetY) * view.scale * dpr + canvas.height / 2);
40
-}
41
-
42
-function drawGraph() {
43
-  ctx.clearRect(0, 0, canvas.width, canvas.height);
44
-  for (let link of graph.links) {
45
-    if (link.source === view.selectedNode) {
46
-      ctx.strokeStyle = "rgba(255, 50, 0, .8)";
47
-      ctx.lineWidth = 5;
48
-    } else if (link.target === view.selectedNode) {
49
-      ctx.strokeStyle = "rgba(0, 50, 255, .8)";
50
-      ctx.lineWidth = 5;
51
-    } else {
52
-      ctx.strokeStyle = "rgba(255, 255, 255, .2)";
53
-      ctx.lineWidth = 2;
54
-    }
55
-    ctx.beginPath();
56
-    ctx.moveTo(x(link.source.x), y(link.source.y));
57
-    ctx.lineTo(x(link.target.x), y(link.target.y));
58
-    ctx.stroke();
15
+  document.getElementById("loading").classList.remove("hidden");
16
+  currentRequestNumber++;
17
+  const thisRequest = currentRequestNumber;
18
+  let newGraph;
19
+  try {
20
+    newGraph = await fetch(`${apiUri}/${backend}/${name}`)
21
+      .then(resp => resp.json());
22
+  } catch(err) {
23
+    console.log(err)
24
+    document.getElementById("loading").classList.add("hidden");
25
+    return;
59 26
   }
60
-  for (let node of nodesAsArray()) {
61
-    ctx.fillStyle = node === view.selectedNode ? "yellow" : "red";
62
-    ctx.fillRect(x(node.x), y(node.y), node.weight * view.scale * dpr, node.weight * view.scale * dpr);
63
-    ctx.fillStyle = "white";
64
-    ctx.font = `${node.weight * Math.sqrt(view.scale) / 10 * (1 + dpr) / 2}rem Sans`;
65
-    ctx.fillText(node.name, x(node.x), y(node.y));
27
+  if (currentRequestNumber !== thisRequest) return;
28
+  setGraph(newGraph);
29
+  document.getElementById("loading").classList.add("hidden");
30
+
31
+  for (let id of Object.keys(graph.nodes)) {
32
+    graph.nodes[id].weight = Math.min(50, 5 + (graph.nodes[id].size / 5000000) || 0);
66 33
   }
67
-  window.requestAnimationFrame(drawGraph);
68
-}
69 34
 
70
-function nodesAsArray() {
71
-  return Object.keys(graph.nodes).map((key) => graph.nodes[key]);
72
-}
35
+  updateGraph();
36
+  setSelectedNode(nodesAsArray().filter(n => n.name === name)[0]);
37
+};
38
+
39
+const handleSubmit = (evt) => {
40
+  evt.preventDefault();
41
+  const backend = backendSelect[backendSelect.selectedIndex].value;
42
+  const name = document.getElementById("name").value;
43
+  window.location.hash = `#${backend}:${name}`;
44
+  return true;
45
+};
73 46
 
74
-async function init() {
47
+const handleHashChange = () => {
48
+  const hash = window.location.hash.substr(1).split(":");
49
+  if (hash.length < 2) return;
50
+  const backend = hash[0];
51
+  const name = hash[1];
52
+  go(backend, name);
53
+};
75 54
 
76
-  const backendSelect = document.getElementById("backend");
55
+const fetchBackendList = async () => {
77 56
   const backends = await fetch(`${apiUri}/backends`)
78 57
     .then(resp => resp.json());
79 58
 
@@ -85,167 +64,20 @@ async function init() {
85 64
   }
86 65
 
87 66
   document.querySelector("option:first-child").selected = true;
67
+};
88 68
 
89
-  simulation
90
-    .force("link", linkForce)
91
-    .force("charge", d3Force.forceManyBody())
92
-    .force("center", d3Force.forceCenter());
93
-
94
-  async function go(backend, name) {
95
-    document.querySelector('#backend [value="' + backend + '"]').selected = true;
96
-    document.getElementById("name").blur();
97
-    document.getElementById("name").value = name;
98
-
99
-    graph = {
100
-      nodes: {},
101
-      links: [],
102
-    };
103
-    simulation.nodes(nodesAsArray());
104
-    linkForce.links(graph.links);
105
-
106
-    document.getElementById("loading").classList.remove("hidden");
107
-    currentRequestNumber++;
108
-    const thisRequest = currentRequestNumber;
109
-    const newGraph = await fetch(`${apiUri}/${backend}/${name}`)
110
-      .then(resp => resp.json());
111
-    if (currentRequestNumber !== thisRequest) return;
112
-    graph = newGraph;
113
-    document.getElementById("loading").classList.add("hidden");
114
-
115
-    for (let id of Object.keys(graph.nodes)) {
116
-      graph.nodes[id].weight = Math.min(50, 5 + (graph.nodes[id].size / 5000000) || 0);
117
-    }
118
-
119
-    simulation.nodes(nodesAsArray());
120
-    linkForce.links(graph.links);
121
-    simulation.alpha(0.5);
122
-    simulation.restart();
123
-
124
-    view.selectedNode = nodesAsArray().filter(n => n.name === name);
125
-  }
69
+/* initialization */
126 70
 
127
-  document.getElementById("inputs").addEventListener("submit", function(event) {
128
-    event.preventDefault();
129
-    const backendSelect = document.getElementById("backend");
130
-    const backend = backendSelect[backendSelect.selectedIndex].value;
131
-    const name = document.getElementById("name").value;
132
-    window.location.hash = `#${backend}:${name}`;
133
-    return true;
134
-  });
135
-
136
-  drawGraph();
137
-
138
-  function goLocationHash() {
139
-    const hash = window.location.hash.substr(1).split(":");
140
-    if (hash.length < 2) return;
141
-    const backend = hash[0];
142
-    const name = hash[1];
143
-    go(backend, name);
144
-  }
71
+fetchBackendList().then(() => {
72
+  document.getElementById("loading").classList.add("hidden");
145 73
 
146 74
   if (window.location.hash.length) {
147
-    goLocationHash();
75
+    handleHashChange();
148 76
   } else {
149 77
     document.getElementById("name").focus();
150 78
   }
151
-
152
-  window.addEventListener("hashchange", goLocationHash);
153
-}
154
-
155
-function handleResize() {
156
-  canvas.width = window.innerWidth * dpr;
157
-  canvas.height = window.innerHeight * dpr;
158
-}
159
-
160
-function nodeAt(xPos, yPos) {
161
-  return nodesAsArray().filter(n => x(n.x) <= xPos && y(n.y) <= yPos
162
-    && x(n.x + n.weight) >= xPos && y(n.y + n.weight) >= yPos)[0];
163
-}
164
-
165
-function handleClick(xPos, yPos) {
166
-  const node = nodeAt(xPos, yPos);
167
-  if (!node)
168
-    view.selectedNode = undefined;
169
-  else
170
-    view.selectedNode = node;
171
-}
172
-
173
-window.addEventListener('wheel', function (evt) {
174
-  if (view.scale < evt.deltaY / 60)
175
-    return;
176
-  view.scale -= evt.deltaY / 60;
177 79
 });
178 80
 
179
-function pinchDistance(touchEvent) {
180
-  return Math.abs(touchEvent.touches[0].pageX - touchEvent.touches[1].pageX) + Math.abs(touchEvent.touches[0].pageY - touchEvent.touches[1].pageY);
181
-}
81
+document.getElementById("inputs").addEventListener("submit", handleSubmit);
82
+window.addEventListener("hashchange", handleHashChange);
182 83
 
183
-function mouseDownHandler(x, y, touchEvent) {
184
-  if (touchEvent && touchEvent.touches.length >= 2) {
185
-    view.pinching = true;
186
-    view.pinchStartDistance = pinchDistance(touchEvent);
187
-    view.pinchStartScale = view.scale;
188
-  }
189
-  view.dragging = true;
190
-  view.clicking = true;
191
-  view.dragStartMouseX = x;
192
-  view.dragStartMouseY = y;
193
-  view.draggedNode = nodeAt(x, y);
194
-  if (view.draggedNode) {
195
-    view.dragStartOffsetX = view.draggedNode.x;
196
-    view.dragStartOffsetY = view.draggedNode.y;
197
-    simulation.restart();
198
-  } else {
199
-    view.dragStartOffsetX = view.offsetX;
200
-    view.dragStartOffsetY = view.offsetY;
201
-  }
202
-}
203
-
204
-function mouseMoveHandler(x, y, touchEvent) {
205
-  if (touchEvent && view.pinching) {
206
-    view.scale = view.pinchStartScale * (pinchDistance(touchEvent) / view.pinchStartDistance);
207
-  }
208
-  if (view.dragging) {
209
-    if (Math.abs(view.dragStartMouseX - x) + Math.abs(view.dragStartMouseY - y) > 10)
210
-      view.clicking = false;
211
-    if (view.draggedNode) {
212
-      view.draggedNode.fx = view.dragStartOffsetX - (view.dragStartMouseX - x) / view.scale;
213
-      view.draggedNode.fy = view.dragStartOffsetY - (view.dragStartMouseY - y) / view.scale;
214
-      simulation.alpha(0.5);
215
-    } else {
216
-      view.offsetX = view.dragStartOffsetX - (view.dragStartMouseX - x) / view.scale;
217
-      view.offsetY = view.dragStartOffsetY - (view.dragStartMouseY - y) / view.scale;
218
-    }
219
-  }
220
-}
221
-
222
-function mouseUpHandler(x, y, touchEvent) {
223
-  if (touchEvent) {
224
-    view.dragging = touchEvent.touches.length >= 1;
225
-    view.pinching = touchEvent.touches.length >= 2;
226
-    if (view.dragging)
227
-      return;
228
-  }
229
-  view.dragging = false;
230
-  if (view.draggedNode) {
231
-    view.draggedNode.fx = undefined;
232
-    view.draggedNode.fy = undefined;
233
-    view.draggedNode = undefined;
234
-  }
235
-  if (view.clicking)
236
-    handleClick(x, y);
237
-}
238
-
239
-if ('ontouchstart' in document.documentElement) {
240
-  canvas.addEventListener("touchstart", (evt) => mouseDownHandler(evt.touches[0].pageX, evt.touches[0].pageY, evt));
241
-  window.addEventListener("touchmove", (evt) => mouseMoveHandler(evt.touches[0].pageX, evt.touches[0].pageY, evt));
242
-  window.addEventListener("touchend", (evt) => mouseUpHandler(evt.changedTouches[0].pageX * dpr, evt.changedTouches[0].pageY * dpr, evt));
243
-} else {
244
-  canvas.addEventListener("mousedown", (evt) => mouseDownHandler(evt.pageX, evt.pageY));
245
-  window.addEventListener("mousemove", (evt) => mouseMoveHandler(evt.pageX, evt.pageY));
246
-  window.addEventListener("mouseup", (evt) => mouseUpHandler(evt.pageX, evt.pageY));
247
-}
248
-
249
-window.addEventListener("resize", handleResize);
250
-handleResize();
251
-init();

+ 19
- 0
client/src/state.js View File

@@ -0,0 +1,19 @@
1
+export let graph = {
2
+  nodes: {},
3
+  links: []
4
+};
5
+
6
+export let selectedNode;
7
+
8
+export const setGraph = (val) => {
9
+  graph = val;
10
+};
11
+
12
+export const setSelectedNode = (val) => {
13
+  selectedNode = val;
14
+};
15
+
16
+export const nodesAsArray = () => {
17
+  return Object.keys(graph.nodes).map((key) => graph.nodes[key]);
18
+};
19
+

+ 2
- 1
client/style.css View File

@@ -38,7 +38,7 @@ canvas {
38 38
 	position: fixed;
39 39
 	top: 0;
40 40
 	left: 0;
41
-	background-color: rgba(255, 255, 255, 0.2);
41
+	background-color: rgba(255, 255, 255, 0.4);
42 42
 	height: 100vh;
43 43
 	padding: 15px;
44 44
 	padding-top: 75px;
@@ -100,6 +100,7 @@ canvas {
100 100
 	display: flex;
101 101
 	align-items: center;
102 102
 	justify-content: center;
103
+	background-color: black;
103 104
 }
104 105
 
105 106
 @keyframes spin {

Loading…
Cancel
Save