viteboard 0.1.1 → 0.1.2

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 (81) hide show
  1. package/content/docs/index.md +1 -1
  2. package/content/docs/product-roadmap.board.json +4 -4
  3. package/content/machine/board-v1.schema.json +138 -0
  4. package/content/machine/example.board.json +217 -0
  5. package/content/machine/guide.md +270 -0
  6. package/content/machine/llms.txt +31 -0
  7. package/content/machine/viteboard.json +62 -0
  8. package/dist/assets/index-D2W76uu9.js +96 -0
  9. package/dist/assets/index-rMkUjGXP.css +1 -0
  10. package/dist/index.html +4 -2
  11. package/package.json +10 -12
  12. package/vite.config.ts +619 -38
  13. package/dist/assets/index-C30zRPsW.js +0 -60
  14. package/dist/assets/index-e5aqtN8W.css +0 -1
  15. package/index.html +0 -13
  16. package/src/app/AppController.ts +0 -962
  17. package/src/app/BoardState.ts +0 -130
  18. package/src/app/ClipboardService.ts +0 -159
  19. package/src/app/CommandManager.ts +0 -66
  20. package/src/app/ElementActions.ts +0 -152
  21. package/src/app/EmbedRegionSelector.ts +0 -103
  22. package/src/app/ExportPng.ts +0 -107
  23. package/src/app/ImageInsertService.ts +0 -141
  24. package/src/app/KeyboardShortcuts.ts +0 -124
  25. package/src/app/main.ts +0 -6
  26. package/src/board/BoardPreview.ts +0 -189
  27. package/src/board/BoardService.ts +0 -222
  28. package/src/canvas/CanvasRenderer.ts +0 -253
  29. package/src/canvas/CanvasSurface.ts +0 -70
  30. package/src/canvas/HitTester.ts +0 -110
  31. package/src/canvas/ImageCache.ts +0 -123
  32. package/src/canvas/PerformanceMonitor.ts +0 -31
  33. package/src/canvas/RenderScheduler.ts +0 -26
  34. package/src/canvas/Viewport.ts +0 -77
  35. package/src/content/ContentApi.ts +0 -258
  36. package/src/content/Markdown.ts +0 -431
  37. package/src/content/MarkdownView.ts +0 -70
  38. package/src/editor/DocEditor.ts +0 -799
  39. package/src/editor/htmlToMarkdown.ts +0 -333
  40. package/src/elements/renderElement.ts +0 -598
  41. package/src/elements/types.ts +0 -119
  42. package/src/shell/Shell.ts +0 -2948
  43. package/src/shell/Sidebar.ts +0 -1651
  44. package/src/storage/AssetStore.ts +0 -86
  45. package/src/storage/BoardSerializer.ts +0 -114
  46. package/src/storage/ImportExportService.ts +0 -153
  47. package/src/storage/IndexedDbStore.ts +0 -92
  48. package/src/storage/LocalBoardStore.ts +0 -104
  49. package/src/styles.css +0 -3332
  50. package/src/templates/handoffTemplate.ts +0 -34
  51. package/src/templates/helpers.ts +0 -132
  52. package/src/templates/index.ts +0 -79
  53. package/src/templates/journeyMapTemplate.ts +0 -52
  54. package/src/templates/opportunityTreeTemplate.ts +0 -45
  55. package/src/templates/personaTemplate.ts +0 -41
  56. package/src/templates/prioritizationMatrixTemplate.ts +0 -44
  57. package/src/templates/requirementsFlowTemplate.ts +0 -45
  58. package/src/templates/screenshotReviewTemplate.ts +0 -52
  59. package/src/templates/systemDiagramTemplate.ts +0 -41
  60. package/src/templates/workflowTemplate.ts +0 -35
  61. package/src/testing/StressTestGenerator.ts +0 -134
  62. package/src/testing/StressTestPanel.ts +0 -64
  63. package/src/tools/ArrowTool.ts +0 -97
  64. package/src/tools/ImageTool.ts +0 -39
  65. package/src/tools/PanTool.ts +0 -34
  66. package/src/tools/SelectTool.ts +0 -377
  67. package/src/tools/ShapeTool.ts +0 -106
  68. package/src/tools/StickyTool.ts +0 -69
  69. package/src/tools/TaskTool.ts +0 -52
  70. package/src/tools/TextTool.ts +0 -45
  71. package/src/tools/ToolContext.ts +0 -51
  72. package/src/tools/ToolController.ts +0 -178
  73. package/src/ui/Modal.ts +0 -58
  74. package/src/ui/PerformanceOverlay.ts +0 -75
  75. package/src/ui/StorageManager.ts +0 -94
  76. package/src/ui/TemplatePicker.ts +0 -67
  77. package/src/ui/TextEditorOverlay.ts +0 -262
  78. package/src/ui/Toolbar.ts +0 -189
  79. package/src/ui/TopControls.ts +0 -137
  80. package/src/ui/icons.ts +0 -56
  81. package/tsconfig.json +0 -19
@@ -1,377 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import type { BoardElement } from "../elements/types";
3
- import { screenToWorld } from "../canvas/Viewport";
4
- import { handlePoints, type HandleId } from "../canvas/CanvasRenderer";
5
- import { invalidateTextCache } from "../elements/renderElement";
6
-
7
- type DragMode = "none" | "move" | "marquee" | "resize" | "endpoint";
8
-
9
- type Snapshot = { id: string; x: number; y: number; width: number; height: number };
10
-
11
- const HANDLE_HIT_RADIUS = 7;
12
-
13
- const HANDLE_CURSORS: Record<HandleId, string> = {
14
- nw: "nwse-resize",
15
- n: "ns-resize",
16
- ne: "nesw-resize",
17
- e: "ew-resize",
18
- se: "nwse-resize",
19
- s: "ns-resize",
20
- sw: "nesw-resize",
21
- w: "ew-resize",
22
- };
23
-
24
- export class SelectTool implements Tool {
25
- name = "select" as const;
26
- cursor = "default";
27
-
28
- private mode: DragMode = "none";
29
- private startScreen = { x: 0, y: 0 };
30
- private startWorld = { x: 0, y: 0 };
31
- private snapshots: Snapshot[] = [];
32
- private activeHandle: HandleId | null = null;
33
- private endpointIsStart = false;
34
- private moved = false;
35
- private pointerDownHitId: string | null = null;
36
-
37
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
38
- const { state, hitTester, renderer } = ctx;
39
- this.startScreen = { x: e.clientX, y: e.clientY };
40
- this.startWorld = screenToWorld(e.clientX, e.clientY, state.viewport);
41
- this.moved = false;
42
-
43
- // 1. resize handle on current selection?
44
- const handle = this.hitHandle(e, ctx);
45
- if (handle) {
46
- this.mode = "resize";
47
- this.activeHandle = handle;
48
- this.takeSnapshots(state.getSelected());
49
- return;
50
- }
51
-
52
- // 2. arrow/line endpoint?
53
- const selected = state.getSelected();
54
- if (selected.length === 1 && this.isLinear(selected[0])) {
55
- const ep = this.hitEndpoint(e, selected[0], ctx);
56
- if (ep !== null) {
57
- this.mode = "endpoint";
58
- this.endpointIsStart = ep;
59
- this.takeSnapshots(selected);
60
- return;
61
- }
62
- }
63
-
64
- // 3. element under cursor?
65
- const hit = hitTester.hitTest(this.startWorld.x, this.startWorld.y, state.viewport.scale);
66
- if (hit) {
67
- this.pointerDownHitId = hit.id;
68
- if (e.shiftKey) {
69
- if (state.selectedIds.has(hit.id)) state.selectedIds.delete(hit.id);
70
- else state.selectedIds.add(hit.id);
71
- } else if (!state.selectedIds.has(hit.id)) {
72
- state.clearSelection();
73
- state.selectedIds.add(hit.id);
74
- }
75
- this.mode = "move";
76
- this.takeSnapshots(state.getSelected());
77
- state.requestRender();
78
- return;
79
- }
80
-
81
- // 4. empty canvas: marquee
82
- if (!e.shiftKey) state.clearSelection();
83
- this.mode = "marquee";
84
- renderer.overlay.marquee = { x: e.clientX, y: e.clientY, w: 0, h: 0 };
85
- state.requestRender();
86
- }
87
-
88
- onPointerMove(e: PointerEvent, ctx: ToolContext): void {
89
- const { state, renderer, hitTester } = ctx;
90
-
91
- if (this.mode === "none") {
92
- // hover feedback + handle cursors
93
- const handle = this.hitHandle(e, ctx);
94
- if (handle) {
95
- ctx.setCursor(HANDLE_CURSORS[handle]);
96
- return;
97
- }
98
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
99
- const hit = hitTester.hitTest(world.x, world.y, state.viewport.scale);
100
- const newHover = hit ? hit.id : null;
101
- ctx.setCursor(hit ? "move" : "default");
102
- if (newHover !== state.hoveredId) {
103
- state.hoveredId = newHover;
104
- state.requestRender();
105
- }
106
- return;
107
- }
108
-
109
- const dxScreen = e.clientX - this.startScreen.x;
110
- const dyScreen = e.clientY - this.startScreen.y;
111
- if (Math.abs(dxScreen) + Math.abs(dyScreen) > 2) this.moved = true;
112
-
113
- const dx = dxScreen / state.viewport.scale;
114
- const dy = dyScreen / state.viewport.scale;
115
-
116
- if (this.mode === "move") {
117
- for (const snap of this.snapshots) {
118
- const el = state.elementsById.get(snap.id);
119
- if (!el || el.locked) continue;
120
- el.x = snap.x + dx;
121
- el.y = snap.y + dy;
122
- }
123
- state.requestRender();
124
- } else if (this.mode === "marquee") {
125
- renderer.overlay.marquee = {
126
- x: this.startScreen.x,
127
- y: this.startScreen.y,
128
- w: dxScreen,
129
- h: dyScreen,
130
- };
131
- state.requestRender();
132
- } else if (this.mode === "resize" && this.activeHandle) {
133
- this.applyResize(dx, dy, e.shiftKey, ctx);
134
- state.requestRender();
135
- } else if (this.mode === "endpoint") {
136
- const snap = this.snapshots[0];
137
- const el = state.elementsById.get(snap.id);
138
- if (el) {
139
- if (this.endpointIsStart) {
140
- el.x = snap.x + dx;
141
- el.y = snap.y + dy;
142
- el.width = snap.width - dx;
143
- el.height = snap.height - dy;
144
- } else {
145
- el.width = snap.width + dx;
146
- el.height = snap.height + dy;
147
- }
148
- }
149
- state.requestRender();
150
- }
151
- }
152
-
153
- onPointerUp(e: PointerEvent, ctx: ToolContext): void {
154
- const { state, renderer, hitTester, commands } = ctx;
155
-
156
- if (this.mode === "marquee") {
157
- const m = renderer.overlay.marquee;
158
- renderer.overlay.marquee = null;
159
- if (m && (Math.abs(m.w) > 3 || Math.abs(m.h) > 3)) {
160
- const a = screenToWorld(m.x, m.y, state.viewport);
161
- const b = screenToWorld(m.x + m.w, m.y + m.h, state.viewport);
162
- const hits = hitTester.elementsInRect(a.x, a.y, b.x - a.x, b.y - a.y);
163
- for (const el of hits) state.selectedIds.add(el.id);
164
- }
165
- state.requestRender();
166
- } else if (
167
- (this.mode === "move" || this.mode === "resize" || this.mode === "endpoint") &&
168
- this.moved
169
- ) {
170
- // push undo command with before/after snapshots
171
- const before = this.snapshots;
172
- const after: Snapshot[] = before.map((s) => {
173
- const el = state.elementsById.get(s.id)!;
174
- return { id: s.id, x: el.x, y: el.y, width: el.width, height: el.height };
175
- });
176
- const apply = (snaps: Snapshot[]) => {
177
- for (const s of snaps) {
178
- const el = state.elementsById.get(s.id);
179
- if (!el) continue;
180
- el.x = s.x;
181
- el.y = s.y;
182
- el.width = s.width;
183
- el.height = s.height;
184
- el.updatedAt = Date.now();
185
- invalidateTextCache(el.id);
186
- }
187
- state.emitChange();
188
- };
189
- for (const s of after) {
190
- const el = state.elementsById.get(s.id);
191
- if (el) {
192
- el.updatedAt = Date.now();
193
- invalidateTextCache(el.id);
194
- }
195
- }
196
- commands.push(
197
- this.mode,
198
- () => apply(after),
199
- () => apply(before)
200
- );
201
- state.emitChange();
202
- } else if (this.mode === "move" && !this.moved) {
203
- this.toggleTaskIfCheckboxClick(e, ctx);
204
- }
205
-
206
- this.mode = "none";
207
- this.activeHandle = null;
208
- this.snapshots = [];
209
- this.pointerDownHitId = null;
210
- }
211
-
212
- onDoubleClick(e: PointerEvent, ctx: ToolContext): void {
213
- const { state, hitTester } = ctx;
214
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
215
- const hit = hitTester.hitTest(world.x, world.y, state.viewport.scale);
216
- if (hit && (hit.type === "sticky" || hit.type === "task" || hit.type === "text" || hit.type === "rectangle" || hit.type === "ellipse" || hit.type === "frame" || hit.type === "arrow" || hit.type === "line" || hit.type === "connector")) {
217
- ctx.openTextEditor(hit);
218
- }
219
- }
220
-
221
- onCancel(ctx: ToolContext): void {
222
- // restore snapshots if mid-drag
223
- if (this.mode !== "none" && this.snapshots.length > 0) {
224
- for (const s of this.snapshots) {
225
- const el = ctx.state.elementsById.get(s.id);
226
- if (!el) continue;
227
- el.x = s.x;
228
- el.y = s.y;
229
- el.width = s.width;
230
- el.height = s.height;
231
- }
232
- }
233
- ctx.renderer.overlay.marquee = null;
234
- this.mode = "none";
235
- ctx.state.clearSelection();
236
- ctx.state.requestRender();
237
- }
238
-
239
- // ---------- helpers ----------
240
-
241
- private isLinear(el: BoardElement): boolean {
242
- return el.type === "line" || el.type === "arrow" || el.type === "connector";
243
- }
244
-
245
- private takeSnapshots(elements: BoardElement[]): void {
246
- this.snapshots = elements.map((el) => ({
247
- id: el.id,
248
- x: el.x,
249
- y: el.y,
250
- width: el.width,
251
- height: el.height,
252
- }));
253
- }
254
-
255
- private toggleTaskIfCheckboxClick(e: PointerEvent, ctx: ToolContext): void {
256
- const { state, hitTester, commands } = ctx;
257
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
258
- const hit = hitTester.hitTest(world.x, world.y, state.viewport.scale);
259
- if (!hit || hit.id !== this.pointerDownHitId || hit.type !== "task") return;
260
- if (!this.hitTaskCheckbox(world.x, world.y, hit)) return;
261
- const before = hit.data.checked === true;
262
- const after = !before;
263
- const id = hit.id;
264
- const apply = (checked: boolean) => {
265
- const el = state.elementsById.get(id);
266
- if (!el) return;
267
- el.data.checked = checked;
268
- el.updatedAt = Date.now();
269
- state.emitChange();
270
- };
271
- commands.execute("toggle-task", () => apply(after), () => apply(before));
272
- }
273
-
274
- private hitTaskCheckbox(wx: number, wy: number, el: BoardElement): boolean {
275
- const boxSize = Math.min(22, Math.max(16, el.height * 0.38));
276
- const boxX = el.x + 16;
277
- const boxY = el.y + el.height / 2 - boxSize / 2;
278
- const pad = 8;
279
- return wx >= boxX - pad && wx <= boxX + boxSize + pad && wy >= boxY - pad && wy <= boxY + boxSize + pad;
280
- }
281
-
282
- private hitHandle(e: PointerEvent, ctx: ToolContext): HandleId | null {
283
- const { state, renderer } = ctx;
284
- const selected = state.getSelected();
285
- if (selected.length === 0) return null;
286
- if (selected.length === 1 && this.isLinear(selected[0])) return null;
287
-
288
- let bbox;
289
- if (selected.length === 1) {
290
- bbox = renderer.elementScreenBBox(selected[0]);
291
- } else {
292
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
293
- for (const el of selected) {
294
- const b = renderer.elementScreenBBox(el);
295
- minX = Math.min(minX, b.x);
296
- minY = Math.min(minY, b.y);
297
- maxX = Math.max(maxX, b.x + b.w);
298
- maxY = Math.max(maxY, b.y + b.h);
299
- }
300
- bbox = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
301
- }
302
-
303
- for (const p of handlePoints(bbox)) {
304
- if (
305
- Math.abs(e.clientX - p.x) <= HANDLE_HIT_RADIUS &&
306
- Math.abs(e.clientY - p.y) <= HANDLE_HIT_RADIUS
307
- ) {
308
- return p.id;
309
- }
310
- }
311
- return null;
312
- }
313
-
314
- private hitEndpoint(e: PointerEvent, el: BoardElement, ctx: ToolContext): boolean | null {
315
- const vp = ctx.state.viewport;
316
- const sx1 = el.x * vp.scale + vp.offsetX;
317
- const sy1 = el.y * vp.scale + vp.offsetY;
318
- const sx2 = (el.x + el.width) * vp.scale + vp.offsetX;
319
- const sy2 = (el.y + el.height) * vp.scale + vp.offsetY;
320
- if (Math.hypot(e.clientX - sx1, e.clientY - sy1) <= HANDLE_HIT_RADIUS + 2) return true;
321
- if (Math.hypot(e.clientX - sx2, e.clientY - sy2) <= HANDLE_HIT_RADIUS + 2) return false;
322
- return null;
323
- }
324
-
325
- private applyResize(dx: number, dy: number, preserveAspect: boolean, ctx: ToolContext): void {
326
- const { state } = ctx;
327
- const h = this.activeHandle!;
328
-
329
- // group bbox in world coords from snapshots
330
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
331
- for (const s of this.snapshots) {
332
- minX = Math.min(minX, Math.min(s.x, s.x + s.width));
333
- minY = Math.min(minY, Math.min(s.y, s.y + s.height));
334
- maxX = Math.max(maxX, Math.max(s.x, s.x + s.width));
335
- maxY = Math.max(maxY, Math.max(s.y, s.y + s.height));
336
- }
337
- const ow = maxX - minX;
338
- const oh = maxY - minY;
339
- if (ow === 0 || oh === 0) return;
340
-
341
- let nx = minX, ny = minY, nw = ow, nh = oh;
342
- if (h.includes("e")) nw = ow + dx;
343
- if (h.includes("w")) { nx = minX + dx; nw = ow - dx; }
344
- if (h.includes("s")) nh = oh + dy;
345
- if (h.includes("n")) { ny = minY + dy; nh = oh - dy; }
346
-
347
- if (preserveAspect && (h === "nw" || h === "ne" || h === "se" || h === "sw")) {
348
- const ratio = ow / oh;
349
- if (Math.abs(nw / ow) > Math.abs(nh / oh)) {
350
- const newH = nw / ratio;
351
- if (h.includes("n")) ny = ny + (nh - newH);
352
- nh = newH;
353
- } else {
354
- const newW = nh * ratio;
355
- if (h.includes("w")) nx = nx + (nw - newW);
356
- nw = newW;
357
- }
358
- }
359
-
360
- const MIN = 4;
361
- if (Math.abs(nw) < MIN || Math.abs(nh) < MIN) return;
362
-
363
- const scaleX = nw / ow;
364
- const scaleY = nh / oh;
365
-
366
- for (const s of this.snapshots) {
367
- const el = state.elementsById.get(s.id);
368
- if (!el || el.locked) continue;
369
- el.x = nx + (s.x - minX) * scaleX;
370
- el.y = ny + (s.y - minY) * scaleY;
371
- el.width = s.width * scaleX;
372
- el.height = s.height * scaleY;
373
- el.updatedAt = Date.now();
374
- invalidateTextCache(el.id);
375
- }
376
- }
377
- }
@@ -1,106 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import type { ToolName } from "./ToolContext";
3
- import { createElement, type BoardElement, type ElementType } from "../elements/types";
4
- import { screenToWorld } from "../canvas/Viewport";
5
-
6
- type ShapeToolName = Extract<ToolName, "rectangle" | "ellipse" | "diamond" | "triangle" | "pill" | "chevron" | "parallelogram" | "hexagon" | "cylinder">;
7
-
8
- export class ShapeTool implements Tool {
9
- name: ShapeToolName;
10
- cursor = "crosshair";
11
-
12
- private shapeType: ElementType;
13
- private shapeKind: ShapeToolName;
14
- private drawing = false;
15
- private startWorld = { x: 0, y: 0 };
16
- private preview: BoardElement | null = null;
17
-
18
- constructor(name: ShapeToolName) {
19
- this.name = name;
20
- this.shapeKind = name;
21
- this.shapeType = name === "ellipse" ? "ellipse" : "rectangle";
22
- }
23
-
24
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
25
- const { state } = ctx;
26
- this.drawing = true;
27
- this.startWorld = screenToWorld(e.clientX, e.clientY, state.viewport);
28
- const isDark = state.theme === "dark";
29
- const isGraphicShape = !["rectangle", "ellipse", "pill", "cylinder"].includes(this.shapeKind);
30
- this.preview = createElement(this.shapeType, {
31
- x: this.startWorld.x,
32
- y: this.startWorld.y,
33
- width: 0,
34
- height: 0,
35
- zIndex: state.maxZIndex() + 1,
36
- style: {
37
- fill: isGraphicShape ? "#0078d4" : isDark ? "#2a2d33" : "#ffffff",
38
- stroke: isGraphicShape ? "#495057" : isDark ? "#9ca3af" : "#374151",
39
- strokeWidth: isGraphicShape ? 0 : 1.5,
40
- borderRadius: this.shapeKind === "pill" ? 999 : this.shapeKind === "rectangle" ? 8 : 0,
41
- fontSize: 14,
42
- textColor: isGraphicShape ? "#ffffff" : isDark ? "#f2f2f2" : "#1f2933",
43
- },
44
- data: { text: "", shapeKind: this.shapeKind },
45
- });
46
- ctx.renderer.overlay.preview = this.preview;
47
- }
48
-
49
- onPointerMove(e: PointerEvent, ctx: ToolContext): void {
50
- if (!this.drawing || !this.preview) return;
51
- const world = screenToWorld(e.clientX, e.clientY, ctx.state.viewport);
52
- let w = world.x - this.startWorld.x;
53
- let h = world.y - this.startWorld.y;
54
- if (e.shiftKey) {
55
- const size = Math.max(Math.abs(w), Math.abs(h));
56
- w = Math.sign(w || 1) * size;
57
- h = Math.sign(h || 1) * size;
58
- }
59
- this.preview.x = Math.min(this.startWorld.x, this.startWorld.x + w);
60
- this.preview.y = Math.min(this.startWorld.y, this.startWorld.y + h);
61
- this.preview.width = Math.abs(w);
62
- this.preview.height = Math.abs(h);
63
- ctx.state.requestRender();
64
- }
65
-
66
- onPointerUp(_e: PointerEvent, ctx: ToolContext): void {
67
- const { state, commands } = ctx;
68
- if (!this.drawing || !this.preview) return;
69
- this.drawing = false;
70
- ctx.renderer.overlay.preview = null;
71
-
72
- const el = this.preview;
73
- this.preview = null;
74
-
75
- if (el.width < 4 || el.height < 4) {
76
- // tiny click: create a default-size shape
77
- el.width = this.shapeKind === "chevron" ? 210 : 160;
78
- el.height = this.shapeKind === "chevron" ? 76 : 100;
79
- el.x -= el.width / 2;
80
- el.y -= el.height / 2;
81
- }
82
-
83
- commands.execute(
84
- "create-shape",
85
- () => {
86
- if (!state.elementsById.has(el.id)) state.addElement(el);
87
- state.emitChange();
88
- },
89
- () => {
90
- state.removeElement(el.id);
91
- state.emitChange();
92
- }
93
- );
94
-
95
- state.clearSelection();
96
- state.selectedIds.add(el.id);
97
- ctx.setTool("select");
98
- }
99
-
100
- onCancel(ctx: ToolContext): void {
101
- this.drawing = false;
102
- this.preview = null;
103
- ctx.renderer.overlay.preview = null;
104
- ctx.state.requestRender();
105
- }
106
- }
@@ -1,69 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import { createElement } from "../elements/types";
3
- import { screenToWorld } from "../canvas/Viewport";
4
-
5
- export const STICKY_COLORS = [
6
- "#fef08a",
7
- "#fdba74",
8
- "#fca5a5",
9
- "#f9a8d4",
10
- "#d8b4fe",
11
- "#a5b4fc",
12
- "#93c5fd",
13
- "#86efac",
14
- ];
15
-
16
- let lastStickyColor = STICKY_COLORS[0];
17
-
18
- export function getLastStickyColor(): string {
19
- return lastStickyColor;
20
- }
21
-
22
- export function setLastStickyColor(color: string): void {
23
- lastStickyColor = color;
24
- }
25
-
26
- export class StickyTool implements Tool {
27
- name = "sticky" as const;
28
- cursor = "crosshair";
29
-
30
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
31
- const { state, commands } = ctx;
32
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
33
- const el = createElement("sticky", {
34
- x: world.x - 110,
35
- y: world.y - 80,
36
- width: 220,
37
- height: 160,
38
- zIndex: state.maxZIndex() + 1,
39
- style: {
40
- fill: getLastStickyColor(),
41
- borderRadius: 12,
42
- shadow: true,
43
- fontSize: 15,
44
- textColor: "#1f2933",
45
- },
46
- data: { text: "" },
47
- });
48
-
49
- commands.execute(
50
- "create-sticky",
51
- () => {
52
- if (!state.elementsById.has(el.id)) state.addElement(el);
53
- state.emitChange();
54
- },
55
- () => {
56
- state.removeElement(el.id);
57
- state.emitChange();
58
- }
59
- );
60
-
61
- state.clearSelection();
62
- state.selectedIds.add(el.id);
63
- ctx.setTool("select");
64
- ctx.openTextEditor(el);
65
- }
66
-
67
- onPointerMove(_e: PointerEvent, _ctx: ToolContext): void {}
68
- onPointerUp(_e: PointerEvent, _ctx: ToolContext): void {}
69
- }
@@ -1,52 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import { createElement } from "../elements/types";
3
- import { screenToWorld } from "../canvas/Viewport";
4
-
5
- export class TaskTool implements Tool {
6
- name = "task" as const;
7
- cursor = "crosshair";
8
-
9
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
10
- const { state, commands } = ctx;
11
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
12
- const isDark = state.theme === "dark";
13
- const el = createElement("task", {
14
- x: world.x - 140,
15
- y: world.y - 28,
16
- width: 280,
17
- height: 56,
18
- zIndex: state.maxZIndex() + 1,
19
- style: {
20
- fill: isDark ? "#17191b" : "#ffffff",
21
- stroke: isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.09)",
22
- strokeWidth: 1,
23
- borderRadius: 14,
24
- shadow: true,
25
- fontSize: 15,
26
- fontWeight: "650",
27
- textColor: isDark ? "#f6f6f3" : "#252829",
28
- },
29
- data: { text: "Task title", checked: false },
30
- });
31
-
32
- commands.execute(
33
- "create-task",
34
- () => {
35
- if (!state.elementsById.has(el.id)) state.addElement(el);
36
- state.emitChange();
37
- },
38
- () => {
39
- state.removeElement(el.id);
40
- state.emitChange();
41
- }
42
- );
43
-
44
- state.clearSelection();
45
- state.selectedIds.add(el.id);
46
- ctx.setTool("select");
47
- ctx.openTextEditor(el);
48
- }
49
-
50
- onPointerMove(_e: PointerEvent, _ctx: ToolContext): void {}
51
- onPointerUp(_e: PointerEvent, _ctx: ToolContext): void {}
52
- }
@@ -1,45 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import { createElement } from "../elements/types";
3
- import { screenToWorld } from "../canvas/Viewport";
4
-
5
- export class TextTool implements Tool {
6
- name = "text" as const;
7
- cursor = "text";
8
-
9
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
10
- const { state, commands } = ctx;
11
- const world = screenToWorld(e.clientX, e.clientY, state.viewport);
12
- const el = createElement("text", {
13
- x: world.x,
14
- y: world.y - 12,
15
- width: 240,
16
- height: 28,
17
- zIndex: state.maxZIndex() + 1,
18
- style: {
19
- fontSize: 16,
20
- textColor: state.theme === "dark" ? "#f2f2f2" : "#1f2933",
21
- },
22
- data: { text: "" },
23
- });
24
-
25
- commands.execute(
26
- "create-text",
27
- () => {
28
- if (!state.elementsById.has(el.id)) state.addElement(el);
29
- state.emitChange();
30
- },
31
- () => {
32
- state.removeElement(el.id);
33
- state.emitChange();
34
- }
35
- );
36
-
37
- state.clearSelection();
38
- state.selectedIds.add(el.id);
39
- ctx.setTool("select");
40
- ctx.openTextEditor(el);
41
- }
42
-
43
- onPointerMove(_e: PointerEvent, _ctx: ToolContext): void {}
44
- onPointerUp(_e: PointerEvent, _ctx: ToolContext): void {}
45
- }
@@ -1,51 +0,0 @@
1
- import type { BoardState } from "../app/BoardState";
2
- import type { CommandManager } from "../app/CommandManager";
3
- import type { CanvasRenderer } from "../canvas/CanvasRenderer";
4
- import type { CanvasSurface } from "../canvas/CanvasSurface";
5
- import type { HitTester } from "../canvas/HitTester";
6
- import type { BoardElement } from "../elements/types";
7
-
8
- export type ToolName =
9
- | "select"
10
- | "hand"
11
- | "sticky"
12
- | "task"
13
- | "rectangle"
14
- | "ellipse"
15
- | "diamond"
16
- | "triangle"
17
- | "pill"
18
- | "chevron"
19
- | "parallelogram"
20
- | "hexagon"
21
- | "cylinder"
22
- | "text"
23
- | "arrow"
24
- | "dashedArrow"
25
- | "twoWayArrow"
26
- | "labeledArrow"
27
- | "sidedLabelArrow"
28
- | "image";
29
-
30
- export type ToolContext = {
31
- state: BoardState;
32
- renderer: CanvasRenderer;
33
- surface: CanvasSurface;
34
- hitTester: HitTester;
35
- commands: CommandManager;
36
- setTool: (tool: ToolName) => void;
37
- setCursor: (cursor: string) => void;
38
- openTextEditor: (el: BoardElement) => void;
39
- };
40
-
41
- export interface Tool {
42
- name: ToolName;
43
- cursor: string;
44
- onPointerDown(e: PointerEvent, ctx: ToolContext): void;
45
- onPointerMove(e: PointerEvent, ctx: ToolContext): void;
46
- onPointerUp(e: PointerEvent, ctx: ToolContext): void;
47
- onDoubleClick?(e: PointerEvent, ctx: ToolContext): void;
48
- onCancel?(ctx: ToolContext): void;
49
- onActivate?(ctx: ToolContext): void;
50
- onDeactivate?(ctx: ToolContext): void;
51
- }