@elicdavis/node-flow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.github/workflows/deploy.yml +57 -0
  2. package/README.md +159 -0
  3. package/dev.tsconfig.json +18 -0
  4. package/docs/promotional-image.png +0 -0
  5. package/esbuild.ts +32 -0
  6. package/index.html +311 -0
  7. package/package.json +50 -0
  8. package/src/camera.ts +30 -0
  9. package/src/connection.ts +249 -0
  10. package/src/contextMenu.ts +367 -0
  11. package/src/draggable.ts +13 -0
  12. package/src/elements/base.ts +192 -0
  13. package/src/elements/container.ts +119 -0
  14. package/src/elements/interfaces.ts +50 -0
  15. package/src/elements/text.ts +86 -0
  16. package/src/graph.ts +535 -0
  17. package/src/graphSubsystem.ts +31 -0
  18. package/src/index.ts +28 -0
  19. package/src/input.ts +153 -0
  20. package/src/markdown/entry.ts +323 -0
  21. package/src/markdown/lexicalParser.ts +148 -0
  22. package/src/markdown/markdown.ts +14 -0
  23. package/src/markdown/syntaxParser.ts +344 -0
  24. package/src/markdown/token.ts +43 -0
  25. package/src/metadata.ts +3 -0
  26. package/src/node.ts +1130 -0
  27. package/src/nodes/factory.ts +87 -0
  28. package/src/nodes/publisher.ts +98 -0
  29. package/src/nodes/subsystem.ts +760 -0
  30. package/src/notes/note.ts +295 -0
  31. package/src/notes/subsystem.ts +262 -0
  32. package/src/organize.ts +181 -0
  33. package/src/pass/subsystem.ts +64 -0
  34. package/src/performance.ts +9 -0
  35. package/src/popup.ts +81 -0
  36. package/src/popups/form.ts +63 -0
  37. package/src/popups/string.ts +33 -0
  38. package/src/port.ts +223 -0
  39. package/src/quickMenu.ts +330 -0
  40. package/src/styles/box.ts +104 -0
  41. package/src/styles/canvasTextAlign.ts +8 -0
  42. package/src/styles/canvasTextBaseline.ts +9 -0
  43. package/src/styles/cursor.ts +18 -0
  44. package/src/styles/stroke.ts +39 -0
  45. package/src/styles/text.ts +90 -0
  46. package/src/styles/textBox.ts +82 -0
  47. package/src/theme.ts +78 -0
  48. package/src/types/box.ts +126 -0
  49. package/src/types/list.ts +41 -0
  50. package/src/types/pool.ts +66 -0
  51. package/src/types/text.ts +136 -0
  52. package/src/types/vector2.ts +34 -0
  53. package/src/utils/color.ts +136 -0
  54. package/src/utils/html.ts +21 -0
  55. package/src/utils/math.ts +8 -0
  56. package/src/utils/string.ts +98 -0
  57. package/src/widgets/button.ts +107 -0
  58. package/src/widgets/color.ts +151 -0
  59. package/src/widgets/factory.ts +46 -0
  60. package/src/widgets/image.ts +163 -0
  61. package/src/widgets/number.ts +153 -0
  62. package/src/widgets/slider.ts +216 -0
  63. package/src/widgets/string.ts +128 -0
  64. package/src/widgets/text.ts +92 -0
  65. package/src/widgets/toggle.ts +205 -0
  66. package/src/widgets/widget.ts +12 -0
  67. package/tsconfig.json +15 -0
  68. package/tsup.config.ts +10 -0
@@ -0,0 +1,295 @@
1
+ import { BuildMarkdown } from "../markdown/markdown";
2
+ import { MarkdownEntry } from "../markdown/entry";
3
+ import { Popup } from "../popup";
4
+ import { BoxStyle } from "../styles/box";
5
+ import { TextAlign } from "../styles/canvasTextAlign";
6
+ import { TextBaseline } from "../styles/canvasTextBaseline";
7
+ import { TextStyleConfig } from "../styles/text";
8
+ import { Theme } from "../theme";
9
+ import { Box } from "../types/box";
10
+ import { CopyVector2, Vector2, Zero } from "../types/vector2";
11
+ import { Camera } from "../camera";
12
+ import { Metadata } from "../metadata";
13
+
14
+ export type NoteContentChangeCallback = (node: FlowNote, newContents: string) => void
15
+ export type NoteWidthChangeCallback = (node: FlowNote, newWidth: number) => void
16
+
17
+ export interface FlowNoteConfig {
18
+ text?: string;
19
+ style?: TextStyleConfig;
20
+ metadata?: Metadata;
21
+ position?: Vector2;
22
+ width?: number;
23
+ locked?: boolean;
24
+ onWidthChange?: NoteWidthChangeCallback;
25
+ onContentChange?: NoteContentChangeCallback;
26
+ }
27
+
28
+ export enum DragHandle {
29
+ None,
30
+ Left,
31
+ Right
32
+ };
33
+
34
+ const BOUNDS_SPACING = 20;
35
+ const BOX_SIZE = 10;
36
+
37
+ export class FlowNote {
38
+
39
+ #originalText: string;
40
+
41
+ #document: Array<MarkdownEntry>;
42
+
43
+ #width: number;
44
+
45
+ #edittingStyle: BoxStyle;
46
+
47
+ // Runtime ================================================================
48
+
49
+ #data: Metadata;
50
+
51
+ #position: Vector2;
52
+
53
+ #handleSelected: DragHandle;
54
+
55
+ #edittingLayout: boolean;
56
+
57
+ #lastRenderedBox: Box;
58
+
59
+ #hovering: boolean;
60
+
61
+ // Callbacks ==============================================================
62
+
63
+ #widthChangeCallbacks: Array<NoteWidthChangeCallback>;
64
+
65
+ #contentChangeCallbacks: Array<NoteContentChangeCallback>;
66
+
67
+ // ========================================================================
68
+
69
+ constructor(config?: FlowNoteConfig) {
70
+ this.#widthChangeCallbacks = new Array<NoteWidthChangeCallback>();
71
+ this.#contentChangeCallbacks = new Array<NoteContentChangeCallback>();
72
+ this.#data = config?.metadata === undefined ? {} : config?.metadata;
73
+ this.#document = [];
74
+ this.#originalText = "";
75
+ this.#hovering = false;
76
+ this.#edittingLayout = config?.locked === undefined ? true : !config?.locked;
77
+ this.#width = config?.width === undefined ? 500 : config.width;
78
+ this.#position = config?.position === undefined ? { x: 0, y: 0 } : config.position;
79
+ this.setText(config?.text === undefined ? "" : config?.text);
80
+ this.#lastRenderedBox = { Position: { x: 0, y: 0 }, Size: { x: 0, y: 0 } };
81
+ this.#handleSelected = DragHandle.None;
82
+
83
+ this.#edittingStyle = new BoxStyle({
84
+ border: {
85
+ color: "white",
86
+ size: 1
87
+ },
88
+ })
89
+
90
+ if (config?.onWidthChange) {
91
+ this.#widthChangeCallbacks.push(config.onWidthChange);
92
+ }
93
+
94
+ if (config?.onContentChange) {
95
+ this.#contentChangeCallbacks.push(config.onContentChange);
96
+ }
97
+ }
98
+
99
+ setText(text: string): void {
100
+ this.#originalText = text;
101
+ this.#document = BuildMarkdown(this.#originalText);
102
+ for (let i = 0; i < this.#contentChangeCallbacks.length; i++) {
103
+ this.#contentChangeCallbacks[i](this, text);
104
+ }
105
+ }
106
+
107
+ public setMetadataProperty(name: string, value: any): void {
108
+ this.#data[name] = value;
109
+ }
110
+
111
+ public getMetadataProperty(name: string): any {
112
+ return this.#data[name];
113
+ }
114
+
115
+ text(): string {
116
+ return this.#originalText;
117
+ }
118
+
119
+ width(): number {
120
+ return this.#width;
121
+ }
122
+
123
+ position(): Vector2 {
124
+ return this.#position;
125
+ }
126
+
127
+ translate(delta: Vector2): void {
128
+ this.#position.x += delta.x;
129
+ this.#position.y += delta.y;
130
+ }
131
+
132
+ setPosition(position: Vector2): void {
133
+ CopyVector2(this.#position, position);
134
+ }
135
+
136
+ handleSelected(): DragHandle {
137
+ return this.#handleSelected;
138
+ }
139
+
140
+ selectHandle(handle: DragHandle): void {
141
+ this.#handleSelected = handle;
142
+ }
143
+
144
+ #tempPosition: Vector2 = Zero();
145
+
146
+ public setWidth(newWidth: number): void {
147
+ if (newWidth === undefined) {
148
+ console.error("Attempted to set note's width to undefined");
149
+ }
150
+ this.#width = newWidth;
151
+ for (let i = 0; i < this.#widthChangeCallbacks.length; i++) {
152
+ this.#widthChangeCallbacks[i](this, newWidth);
153
+ }
154
+ }
155
+
156
+ public addWidthChangeListener(callback: NoteWidthChangeCallback): void {
157
+ if (callback === null || callback === undefined) {
158
+ return;
159
+ }
160
+ this.#widthChangeCallbacks.push(callback);
161
+ }
162
+
163
+ public addContentChangeListener(callback: NoteContentChangeCallback): void {
164
+ if (callback === null || callback === undefined) {
165
+ return;
166
+ }
167
+ this.#contentChangeCallbacks.push(callback);
168
+ }
169
+
170
+ render(ctx: CanvasRenderingContext2D, camera: Camera, mousePosition: Vector2 | undefined): void {
171
+ if (this.#edittingLayout && (this.#hovering || this.#handleSelected !== DragHandle.None)) {
172
+
173
+ if (mousePosition) {
174
+ if (this.#handleSelected === DragHandle.Right) {
175
+
176
+ const leftPosition = (this.#position.x * camera.zoom) + camera.position.x;
177
+ this.setWidth(Math.max((mousePosition.x - leftPosition) / camera.zoom, 1));
178
+
179
+ } else if (this.#handleSelected === DragHandle.Left) {
180
+
181
+ const scaledWidth = this.#width * camera.zoom;
182
+ const rightPosition = (this.#position.x * camera.zoom) + camera.position.x + scaledWidth;
183
+ this.setWidth(Math.max((rightPosition - mousePosition.x) / camera.zoom, 1));
184
+ this.#position.x = rightPosition - (this.#width * camera.zoom) - camera.position.x;
185
+ this.#position.x /= camera.zoom;
186
+ }
187
+ }
188
+
189
+ this.#edittingStyle.Outline(ctx, this.leftResizeHandleBox(), camera.zoom, 2);
190
+ this.#edittingStyle.Outline(ctx, this.rightResizeHandleBox(), camera.zoom, 2);
191
+
192
+ ctx.beginPath();
193
+ const left = this.#lastRenderedBox.Position.x;
194
+ const right = this.#lastRenderedBox.Position.x + this.#lastRenderedBox.Size.x;
195
+ const bottom = this.#lastRenderedBox.Position.y;
196
+ const top = this.#lastRenderedBox.Position.y + this.#lastRenderedBox.Size.y;
197
+ ctx.moveTo(left, bottom + (this.#lastRenderedBox.Size.y / 2) - (BOX_SIZE));
198
+ ctx.lineTo(left, bottom);
199
+ ctx.lineTo(right, bottom);
200
+ ctx.lineTo(right, bottom + (this.#lastRenderedBox.Size.y / 2) - (BOX_SIZE));
201
+
202
+ ctx.moveTo(left, top - (this.#lastRenderedBox.Size.y / 2) + (BOX_SIZE));
203
+ ctx.lineTo(left, top);
204
+ ctx.lineTo(right, top);
205
+ ctx.lineTo(right, top - (this.#lastRenderedBox.Size.y / 2) + (BOX_SIZE));
206
+ ctx.stroke();
207
+ // this.#edittingStyle.Outline(ctx, bigBox, camera.zoom, 2);
208
+ }
209
+
210
+ camera.graphSpaceToScreenSpace(this.#position, this.#tempPosition);
211
+ CopyVector2(this.#lastRenderedBox.Position, this.#tempPosition)
212
+
213
+ const startY = this.#tempPosition.y;
214
+ const lineSpacing = Theme.Note.EntrySpacing * camera.zoom;
215
+
216
+ ctx.textAlign = TextAlign.Left;
217
+ ctx.textBaseline = TextBaseline.Alphabetic;
218
+ for (let i = 0; i < this.#document.length; i++) {
219
+ const text = this.#document[i];
220
+ this.#tempPosition.y += text.render(ctx, this.#tempPosition, camera.zoom, this.#width) + lineSpacing;
221
+ }
222
+
223
+ this.#lastRenderedBox.Position.x -= BOUNDS_SPACING
224
+ this.#lastRenderedBox.Position.y -= BOUNDS_SPACING
225
+ this.#lastRenderedBox.Size.x = camera.zoom * this.#width + (BOUNDS_SPACING * 2);
226
+ this.#lastRenderedBox.Size.y = this.#tempPosition.y - startY + (BOUNDS_SPACING * 2);
227
+ }
228
+
229
+ leftResizeHandleBox(): Box {
230
+ return {
231
+ Position: {
232
+ x: this.#lastRenderedBox.Position.x - (BOX_SIZE / 2),
233
+ y: this.#lastRenderedBox.Position.y + (this.#lastRenderedBox.Size.y / 2) - (BOX_SIZE / 2),
234
+ },
235
+ Size: { x: BOX_SIZE, y: BOX_SIZE, }
236
+ };
237
+ }
238
+
239
+ rightResizeHandleBox(): Box {
240
+ return {
241
+ Position: {
242
+ x: this.#lastRenderedBox.Position.x - (BOX_SIZE / 2) + this.#lastRenderedBox.Size.x,
243
+ y: this.#lastRenderedBox.Position.y + (this.#lastRenderedBox.Size.y / 2) - (BOX_SIZE / 2),
244
+ },
245
+ Size: { x: BOX_SIZE, y: BOX_SIZE, }
246
+ };
247
+ }
248
+
249
+ edittingLayout(): boolean {
250
+ return this.#edittingLayout;
251
+ }
252
+
253
+ setHovering(hovering: boolean): void {
254
+ this.#hovering = hovering;
255
+ }
256
+
257
+ editContent(): void {
258
+ let input: HTMLTextAreaElement | null = null;
259
+ let saveText = "Save";
260
+
261
+ const popup = new Popup({
262
+ title: "Edit Note",
263
+ options: [saveText, "Cancel"],
264
+ content: () => {
265
+ const container = document.createElement('div');
266
+ input = document.createElement('textarea')
267
+ input.rows = 8;
268
+ input.cols = 50;
269
+ input.value = this.#originalText;
270
+ container.append(input);
271
+ return container;
272
+ },
273
+ onClose: (button: string | null): void => {
274
+ if (button !== saveText || input === null) {
275
+ return;
276
+ }
277
+ this.setText(input.value);
278
+ },
279
+ });
280
+
281
+ popup.Show();
282
+ }
283
+
284
+ lock(): void {
285
+ this.#edittingLayout = false;
286
+ }
287
+
288
+ unlock(): void {
289
+ this.#edittingLayout = true;
290
+ }
291
+
292
+ bounds(): Box {
293
+ return this.#lastRenderedBox;
294
+ }
295
+ }
@@ -0,0 +1,262 @@
1
+ import { Camera } from "../camera";
2
+ import { ContextMenuConfig } from "../contextMenu";
3
+ import { RenderResults } from "../graphSubsystem";
4
+ import { InBox } from "../types/box";
5
+ import { Vector2 } from "../types/vector2";
6
+ import { DragHandle, FlowNote, FlowNoteConfig } from './note';
7
+
8
+
9
+ export type NoteAddedCallback = (addedNote: FlowNote) => void
10
+ export type NoteRemovedCallback = (noteRemoved: FlowNote) => void
11
+
12
+ export type NoteDragStartCallback = (nodeDragged: FlowNote) => void
13
+ export type NoteDragStopCallback = (nodeDragged: FlowNote) => void
14
+
15
+
16
+ export interface NoteSubsystemConfig {
17
+ onNoteAdded?: NoteAddedCallback;
18
+ onNoteRemoved?: NoteRemovedCallback;
19
+ onNoteDragStop?: NoteDragStopCallback
20
+ onNoteDragStart?: NoteDragStartCallback
21
+ notes?: Array<FlowNoteConfig>
22
+ }
23
+
24
+ export class NoteSubsystem {
25
+
26
+ #notes: Array<FlowNote>;
27
+
28
+ #noteHovering: FlowNote | null;
29
+
30
+ #noteSelected: FlowNote | null;
31
+
32
+ #hoveringHandle: DragHandle;
33
+
34
+ #onNoteAddedCallbacks: Array<NoteAddedCallback>;
35
+
36
+ #onNoteRemovedCallbacks: Array<NoteRemovedCallback>;
37
+
38
+ #onNoteDragStartCallbacks: Array<NoteDragStartCallback>;
39
+
40
+ #onNoteDragStopCallbacks: Array<NoteDragStopCallback>;
41
+
42
+ constructor(config?: NoteSubsystemConfig) {
43
+ this.#hoveringHandle = DragHandle.None;
44
+ this.#notes = [];
45
+ this.#noteHovering = null;
46
+ this.#noteSelected = null;
47
+
48
+ // Callbacks
49
+ this.#onNoteAddedCallbacks = new Array<NoteAddedCallback>();
50
+ this.#onNoteRemovedCallbacks = new Array<NoteRemovedCallback>();
51
+ this.#onNoteDragStartCallbacks = new Array<NoteDragStartCallback>();
52
+ this.#onNoteDragStopCallbacks = new Array<NoteDragStopCallback>();
53
+
54
+ if (config?.notes !== undefined) {
55
+ for (let i = 0; i < config?.notes.length; i++) {
56
+ this.addNote(new FlowNote(config?.notes[i]));
57
+ }
58
+ }
59
+
60
+ // Add callback *after* we added all the initial notes
61
+ if (config?.onNoteAdded !== undefined) {
62
+ this.#onNoteAddedCallbacks.push(config?.onNoteAdded);
63
+ }
64
+
65
+ if (config?.onNoteDragStop !== undefined) {
66
+ this.#onNoteDragStopCallbacks.push(config?.onNoteDragStop);
67
+ }
68
+
69
+ if (config?.onNoteDragStart !== undefined) {
70
+ this.#onNoteDragStartCallbacks.push(config?.onNoteDragStart);
71
+ }
72
+
73
+ if (config?.onNoteRemoved !== undefined) {
74
+ this.#onNoteRemovedCallbacks.push(config?.onNoteRemoved);
75
+ }
76
+ }
77
+
78
+ addNote(note: FlowNote): void {
79
+ if (note === null || note === undefined) {
80
+ return;
81
+ }
82
+
83
+ this.#notes.push(note);
84
+ for (let i = 0; i < this.#onNoteAddedCallbacks.length; i++) {
85
+ this.#onNoteAddedCallbacks[i](note);
86
+ }
87
+ }
88
+
89
+ public addNoteAddedListener(callback: NoteAddedCallback): void {
90
+ if (callback === null || callback === undefined) {
91
+ return;
92
+ }
93
+ this.#onNoteAddedCallbacks.push(callback);
94
+ }
95
+
96
+ public addNoteRemovedListener(callback: NoteRemovedCallback): void {
97
+ if (callback === null || callback === undefined) {
98
+ return;
99
+ }
100
+ this.#onNoteRemovedCallbacks.push(callback);
101
+ }
102
+
103
+ public addNoteDragStartListener(callback: NoteDragStartCallback): void {
104
+ if (callback === null || callback === undefined) {
105
+ return;
106
+ }
107
+ this.#onNoteDragStartCallbacks.push(callback);
108
+ }
109
+
110
+ public addNoteDragStopListener(callback: NoteDragStopCallback): void {
111
+ if (callback === null || callback === undefined) {
112
+ return;
113
+ }
114
+ this.#onNoteDragStopCallbacks.push(callback);
115
+ }
116
+
117
+ openContextMenu(ctx: CanvasRenderingContext2D, position: Vector2): ContextMenuConfig | null {
118
+ const group = "node-flow-graph-note-menu";
119
+
120
+ const result: ContextMenuConfig = {
121
+ items: [
122
+ {
123
+ name: "New Note",
124
+ group: group,
125
+ callback: () => {
126
+ this.addNote(new FlowNote({
127
+ text: "#Note\n\nRight-click this note and select \"edit note\" to put what you want here.",
128
+ width: 300,
129
+ position: position
130
+ }))
131
+ }
132
+ }
133
+ ],
134
+ subMenus: [],
135
+ }
136
+
137
+ if (this.#noteHovering !== null) {
138
+ const noteToReview = this.#noteHovering;
139
+ result.items?.push(
140
+ {
141
+ name: "Delete Note",
142
+ group: group,
143
+ callback: () => {
144
+ this.removeNote(noteToReview);
145
+ }
146
+ },
147
+ {
148
+ name: "Edit Note",
149
+ group: group,
150
+ callback: noteToReview.editContent.bind(noteToReview),
151
+ },
152
+ );
153
+
154
+ if (!noteToReview.edittingLayout()) {
155
+ result.items?.push({
156
+ name: "Unlock Note",
157
+ group: group,
158
+ callback: noteToReview.unlock.bind(noteToReview),
159
+ })
160
+ } else {
161
+ result.items?.push({
162
+ name: "Lock Note",
163
+ group: group,
164
+ callback: noteToReview.lock.bind(noteToReview),
165
+ })
166
+ }
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ clickStart(mousePosition: Vector2, camera: Camera, ctrlKey: boolean): boolean {
173
+ if (this.#noteHovering !== null && this.#noteHovering.edittingLayout()) {
174
+ this.#noteSelected = this.#noteHovering;
175
+ this.#noteSelected.selectHandle(this.#hoveringHandle);
176
+
177
+ for (let i = 0; i < this.#onNoteDragStartCallbacks.length; i++) {
178
+ this.#onNoteDragStartCallbacks[i](this.#noteSelected);
179
+ }
180
+ return true;
181
+ }
182
+ return false;
183
+ }
184
+
185
+ clickEnd(): void {
186
+ if (this.#noteSelected !== null) {
187
+ this.#noteSelected.selectHandle(DragHandle.None);
188
+ for (let i = 0; i < this.#onNoteDragStopCallbacks.length; i++) {
189
+ this.#onNoteDragStopCallbacks[i](this.#noteSelected);
190
+ }
191
+ }
192
+ this.#noteSelected = null;
193
+ }
194
+
195
+ mouseDragEvent(delta: Vector2, scale: number): boolean {
196
+ if (this.#noteSelected === null) {
197
+ return false;
198
+ }
199
+
200
+ // Return early so we don't translate since we're moving a drag handle
201
+ if (this.#noteSelected.handleSelected()) {
202
+ return true;
203
+ }
204
+
205
+ this.#noteSelected.translate({
206
+ x: delta.x * (1 / scale),
207
+ y: delta.y * (1 / scale)
208
+ });
209
+ return true;
210
+ }
211
+
212
+ fileDrop(file: File): boolean {
213
+ return false;
214
+ }
215
+
216
+ removeNote(note: FlowNote): void {
217
+ const index = this.#notes.indexOf(note);
218
+ if (index > -1) {
219
+ const noteRemoved = this.#notes[index];
220
+ this.#notes.splice(index, 1);
221
+
222
+ for (let i = 0; i < this.#onNoteRemovedCallbacks.length; i++) {
223
+ this.#onNoteRemovedCallbacks[i](noteRemoved)
224
+ }
225
+ } else {
226
+ console.error("no note found to remove");
227
+ }
228
+ }
229
+
230
+ render(ctx: CanvasRenderingContext2D, camera: Camera, mousePosition: Vector2 | undefined): RenderResults | undefined {
231
+ this.#noteHovering = null;
232
+ this.#hoveringHandle = DragHandle.None;
233
+
234
+ // Set hovering to false on everything
235
+ if (mousePosition) {
236
+ for (let i = 0; i < this.#notes.length; i++) {
237
+ this.#notes[i].setHovering(false);
238
+ if (InBox(this.#notes[i].bounds(), mousePosition)) {
239
+ this.#noteHovering = this.#notes[i];
240
+ }
241
+ }
242
+ }
243
+
244
+ // The note on top of everything is the one that get's to be in a
245
+ // hover state
246
+ if (this.#noteHovering != null) {
247
+ this.#noteHovering.setHovering(true);
248
+ if (mousePosition) {
249
+ if (InBox(this.#noteHovering.leftResizeHandleBox(), mousePosition)) {
250
+ this.#hoveringHandle = DragHandle.Left;
251
+ } else if (InBox(this.#noteHovering.rightResizeHandleBox(), mousePosition)) {
252
+ this.#hoveringHandle = DragHandle.Right;
253
+ }
254
+ }
255
+ }
256
+
257
+ for (let i = 0; i < this.#notes.length; i++) {
258
+ this.#notes[i].render(ctx, camera, mousePosition);
259
+ }
260
+ return;
261
+ }
262
+ }