viteboard 0.1.1 → 0.1.3

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 (83) hide show
  1. package/bin/content-api-plugin.mjs +722 -0
  2. package/bin/viteboard.mjs +3 -7
  3. package/content/docs/index.md +1 -1
  4. package/content/docs/product-roadmap.board.json +4 -4
  5. package/content/machine/board-v1.schema.json +138 -0
  6. package/content/machine/example.board.json +217 -0
  7. package/content/machine/guide.md +270 -0
  8. package/content/machine/llms.txt +31 -0
  9. package/content/machine/viteboard.json +62 -0
  10. package/dist/assets/index-D2W76uu9.js +96 -0
  11. package/dist/assets/index-rMkUjGXP.css +1 -0
  12. package/dist/index.html +4 -2
  13. package/package.json +10 -12
  14. package/vite.config.ts +619 -38
  15. package/dist/assets/index-C30zRPsW.js +0 -60
  16. package/dist/assets/index-e5aqtN8W.css +0 -1
  17. package/index.html +0 -13
  18. package/src/app/AppController.ts +0 -962
  19. package/src/app/BoardState.ts +0 -130
  20. package/src/app/ClipboardService.ts +0 -159
  21. package/src/app/CommandManager.ts +0 -66
  22. package/src/app/ElementActions.ts +0 -152
  23. package/src/app/EmbedRegionSelector.ts +0 -103
  24. package/src/app/ExportPng.ts +0 -107
  25. package/src/app/ImageInsertService.ts +0 -141
  26. package/src/app/KeyboardShortcuts.ts +0 -124
  27. package/src/app/main.ts +0 -6
  28. package/src/board/BoardPreview.ts +0 -189
  29. package/src/board/BoardService.ts +0 -222
  30. package/src/canvas/CanvasRenderer.ts +0 -253
  31. package/src/canvas/CanvasSurface.ts +0 -70
  32. package/src/canvas/HitTester.ts +0 -110
  33. package/src/canvas/ImageCache.ts +0 -123
  34. package/src/canvas/PerformanceMonitor.ts +0 -31
  35. package/src/canvas/RenderScheduler.ts +0 -26
  36. package/src/canvas/Viewport.ts +0 -77
  37. package/src/content/ContentApi.ts +0 -258
  38. package/src/content/Markdown.ts +0 -431
  39. package/src/content/MarkdownView.ts +0 -70
  40. package/src/editor/DocEditor.ts +0 -799
  41. package/src/editor/htmlToMarkdown.ts +0 -333
  42. package/src/elements/renderElement.ts +0 -598
  43. package/src/elements/types.ts +0 -119
  44. package/src/shell/Shell.ts +0 -2948
  45. package/src/shell/Sidebar.ts +0 -1651
  46. package/src/storage/AssetStore.ts +0 -86
  47. package/src/storage/BoardSerializer.ts +0 -114
  48. package/src/storage/ImportExportService.ts +0 -153
  49. package/src/storage/IndexedDbStore.ts +0 -92
  50. package/src/storage/LocalBoardStore.ts +0 -104
  51. package/src/styles.css +0 -3332
  52. package/src/templates/handoffTemplate.ts +0 -34
  53. package/src/templates/helpers.ts +0 -132
  54. package/src/templates/index.ts +0 -79
  55. package/src/templates/journeyMapTemplate.ts +0 -52
  56. package/src/templates/opportunityTreeTemplate.ts +0 -45
  57. package/src/templates/personaTemplate.ts +0 -41
  58. package/src/templates/prioritizationMatrixTemplate.ts +0 -44
  59. package/src/templates/requirementsFlowTemplate.ts +0 -45
  60. package/src/templates/screenshotReviewTemplate.ts +0 -52
  61. package/src/templates/systemDiagramTemplate.ts +0 -41
  62. package/src/templates/workflowTemplate.ts +0 -35
  63. package/src/testing/StressTestGenerator.ts +0 -134
  64. package/src/testing/StressTestPanel.ts +0 -64
  65. package/src/tools/ArrowTool.ts +0 -97
  66. package/src/tools/ImageTool.ts +0 -39
  67. package/src/tools/PanTool.ts +0 -34
  68. package/src/tools/SelectTool.ts +0 -377
  69. package/src/tools/ShapeTool.ts +0 -106
  70. package/src/tools/StickyTool.ts +0 -69
  71. package/src/tools/TaskTool.ts +0 -52
  72. package/src/tools/TextTool.ts +0 -45
  73. package/src/tools/ToolContext.ts +0 -51
  74. package/src/tools/ToolController.ts +0 -178
  75. package/src/ui/Modal.ts +0 -58
  76. package/src/ui/PerformanceOverlay.ts +0 -75
  77. package/src/ui/StorageManager.ts +0 -94
  78. package/src/ui/TemplatePicker.ts +0 -67
  79. package/src/ui/TextEditorOverlay.ts +0 -262
  80. package/src/ui/Toolbar.ts +0 -189
  81. package/src/ui/TopControls.ts +0 -137
  82. package/src/ui/icons.ts +0 -56
  83. package/tsconfig.json +0 -19
@@ -1,134 +0,0 @@
1
- import type { BoardState } from "../app/BoardState";
2
- import { createElement, type BoardElement, type ElementType } from "../elements/types";
3
- import { STICKY_COLORS } from "../tools/StickyTool";
4
-
5
- const SHAPE_FILLS = [
6
- "#93c5fd", "#a5b4fc", "#86efac", "#fdba74", "#fca5a5",
7
- "#d8b4fe", "#f9a8d4", "#fef08a", "#ffffff", "#e5e7eb",
8
- ];
9
-
10
- const WORDS = [
11
- "Onboarding", "Checkout", "Retention", "Activation", "Churn risk",
12
- "API gateway", "User insight", "Edge case", "Quick win", "Tech debt",
13
- "Experiment", "Metric", "Persona", "Journey", "Feature flag",
14
- ];
15
-
16
- function rand(min: number, max: number): number {
17
- return min + Math.random() * (max - min);
18
- }
19
-
20
- function pick<T>(arr: T[]): T {
21
- return arr[Math.floor(Math.random() * arr.length)];
22
- }
23
-
24
- const STRESS_TAG = "stress";
25
-
26
- export class StressTestGenerator {
27
- private state: BoardState;
28
- lastGenerateMs = 0;
29
-
30
- constructor(state: BoardState) {
31
- this.state = state;
32
- }
33
-
34
- generate(count: number): void {
35
- const start = performance.now();
36
- const spread = Math.sqrt(count) * 280;
37
- const types: ElementType[] = ["sticky", "rectangle", "ellipse", "text", "arrow"];
38
- let z = this.state.maxZIndex();
39
-
40
- const batch: BoardElement[] = [];
41
- for (let i = 0; i < count; i++) {
42
- const type = pick(types);
43
- const x = rand(-spread / 2, spread / 2);
44
- const y = rand(-spread / 2, spread / 2);
45
- let el: BoardElement;
46
- if (type === "sticky") {
47
- el = createElement("sticky", {
48
- x, y, width: 220, height: 160, zIndex: ++z,
49
- style: { fill: pick(STICKY_COLORS), borderRadius: 12, shadow: true, fontSize: 14, textColor: "#1f2933" },
50
- data: { text: pick(WORDS), [STRESS_TAG]: true },
51
- });
52
- } else if (type === "rectangle" || type === "ellipse") {
53
- el = createElement(type, {
54
- x, y, width: rand(80, 300), height: rand(60, 200), zIndex: ++z,
55
- style: { fill: pick(SHAPE_FILLS), stroke: "#37415188", strokeWidth: 1.5, borderRadius: type === "rectangle" ? 8 : 0 },
56
- data: { [STRESS_TAG]: true },
57
- });
58
- } else if (type === "text") {
59
- el = createElement("text", {
60
- x, y, width: 200, height: 28, zIndex: ++z,
61
- style: { fontSize: 16, textColor: this.state.theme === "dark" ? "#f2f2f2" : "#1f2933" },
62
- data: { text: pick(WORDS), [STRESS_TAG]: true },
63
- });
64
- } else {
65
- el = createElement("arrow", {
66
- x, y, width: rand(-250, 250), height: rand(-250, 250), zIndex: ++z,
67
- style: { stroke: pick(SHAPE_FILLS), strokeWidth: 2, arrowEnd: true },
68
- data: { [STRESS_TAG]: true },
69
- });
70
- }
71
- batch.push(el);
72
- }
73
-
74
- for (const el of batch) this.state.addElement(el);
75
- this.lastGenerateMs = performance.now() - start;
76
- this.state.emitChange();
77
- }
78
-
79
- generateImagePlaceholders(count: number): void {
80
- const spread = Math.sqrt(count) * 420;
81
- let z = this.state.maxZIndex();
82
- for (let i = 0; i < count; i++) {
83
- const el = createElement("image", {
84
- x: rand(-spread / 2, spread / 2),
85
- y: rand(-spread / 2, spread / 2),
86
- width: 320,
87
- height: 220,
88
- zIndex: ++z,
89
- data: { assetId: "stress-placeholder-" + i, [STRESS_TAG]: true },
90
- });
91
- this.state.addElement(el);
92
- }
93
- this.state.emitChange();
94
- }
95
-
96
- generateMixedPmBoard(): void {
97
- this.generate(300);
98
- }
99
-
100
- randomizePositions(): void {
101
- const spread = Math.sqrt(this.state.elements.length) * 280;
102
- for (const el of this.state.elements) {
103
- if (!el.data[STRESS_TAG]) continue;
104
- el.x = rand(-spread / 2, spread / 2);
105
- el.y = rand(-spread / 2, spread / 2);
106
- }
107
- this.state.emitChange();
108
- }
109
-
110
- randomizeColors(): void {
111
- for (const el of this.state.elements) {
112
- if (!el.data[STRESS_TAG]) continue;
113
- if (el.type === "sticky") el.style.fill = pick(STICKY_COLORS);
114
- else if (el.type === "rectangle" || el.type === "ellipse") el.style.fill = pick(SHAPE_FILLS);
115
- else if (el.type === "arrow") el.style.stroke = pick(SHAPE_FILLS);
116
- }
117
- this.state.emitChange();
118
- }
119
-
120
- randomizeZIndex(): void {
121
- for (const el of this.state.elements) {
122
- if (!el.data[STRESS_TAG]) continue;
123
- el.zIndex = Math.floor(rand(0, this.state.elements.length));
124
- }
125
- this.state.invalidateSort();
126
- this.state.emitChange();
127
- }
128
-
129
- clearGenerated(): void {
130
- const toRemove = this.state.elements.filter((el) => el.data[STRESS_TAG]);
131
- for (const el of toRemove) this.state.removeElement(el.id);
132
- this.state.emitChange();
133
- }
134
- }
@@ -1,64 +0,0 @@
1
- import type { StressTestGenerator } from "./StressTestGenerator";
2
- import type { CanvasRenderer } from "../canvas/CanvasRenderer";
3
- import { showToast } from "../ui/Modal";
4
-
5
- export class StressTestPanel {
6
- element: HTMLDivElement;
7
- visible = false;
8
-
9
- constructor(parent: HTMLElement, generator: StressTestGenerator, renderer: CanvasRenderer) {
10
- this.element = document.createElement("div");
11
- this.element.className = "stress-panel";
12
-
13
- const addBtn = (text: string, onClick: () => void, danger = false) => {
14
- const btn = document.createElement("button");
15
- btn.className = "btn" + (danger ? " danger" : "");
16
- btn.textContent = text;
17
- btn.addEventListener("click", onClick);
18
- this.element.appendChild(btn);
19
- };
20
-
21
- const gen = (n: number) => () => {
22
- generator.generate(n);
23
- showToast(`Generated ${n.toLocaleString()} elements in ${generator.lastGenerateMs.toFixed(0)}ms`);
24
- };
25
-
26
- addBtn("+100", gen(100));
27
- addBtn("+1,000", gen(1000));
28
- addBtn("+5,000", gen(5000));
29
- addBtn("+10,000", gen(10000));
30
- addBtn("+20,000", gen(20000));
31
- addBtn("+100 images", () => {
32
- generator.generateImagePlaceholders(100);
33
- showToast("Generated 100 image placeholders");
34
- });
35
- addBtn("Mixed PM board", () => {
36
- generator.generateMixedPmBoard();
37
- showToast("Generated mixed PM board");
38
- });
39
- addBtn("Shuffle positions", () => generator.randomizePositions());
40
- addBtn("Shuffle colors", () => generator.randomizeColors());
41
- addBtn("Shuffle z-index", () => generator.randomizeZIndex());
42
- addBtn("Toggle culling", () => {
43
- renderer.cullingEnabled = !renderer.cullingEnabled;
44
- showToast(`Viewport culling ${renderer.cullingEnabled ? "enabled" : "disabled"}`);
45
- renderer.state.requestRender();
46
- });
47
- addBtn("Toggle LOD", () => {
48
- renderer.lodEnabled = !renderer.lodEnabled;
49
- showToast(`Simplified rendering ${renderer.lodEnabled ? "enabled" : "disabled"}`);
50
- renderer.state.requestRender();
51
- });
52
- addBtn("Clear generated", () => {
53
- generator.clearGenerated();
54
- showToast("Cleared generated elements");
55
- }, true);
56
-
57
- parent.appendChild(this.element);
58
- }
59
-
60
- toggle(): void {
61
- this.visible = !this.visible;
62
- this.element.classList.toggle("visible", this.visible);
63
- }
64
- }
@@ -1,97 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
- import { createElement, type BoardElement } from "../elements/types";
3
- import { screenToWorld } from "../canvas/Viewport";
4
-
5
- type ArrowToolName = Extract<Tool["name"], "arrow" | "dashedArrow" | "twoWayArrow" | "labeledArrow" | "sidedLabelArrow">;
6
-
7
- export class ArrowTool implements Tool {
8
- name: ArrowToolName;
9
- cursor = "crosshair";
10
-
11
- private drawing = false;
12
- private startWorld = { x: 0, y: 0 };
13
- private preview: BoardElement | null = null;
14
-
15
- constructor(name: ArrowToolName = "arrow") {
16
- this.name = name;
17
- }
18
-
19
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
20
- const { state } = ctx;
21
- this.drawing = true;
22
- this.startWorld = screenToWorld(e.clientX, e.clientY, state.viewport);
23
- this.preview = createElement("arrow", {
24
- x: this.startWorld.x,
25
- y: this.startWorld.y,
26
- width: 0,
27
- height: 0,
28
- zIndex: state.maxZIndex() + 1,
29
- style: {
30
- stroke: state.theme === "dark" ? "#d1d5db" : "#374151",
31
- strokeWidth: 2,
32
- arrowEnd: true,
33
- arrowStart: this.name === "twoWayArrow",
34
- strokeDash: this.name === "dashedArrow" || e.altKey ? [8, 6] : undefined,
35
- },
36
- data: { text: "", labelPosition: this.name === "sidedLabelArrow" ? "sides" : "center" },
37
- });
38
- ctx.renderer.overlay.preview = this.preview;
39
- }
40
-
41
- onPointerMove(e: PointerEvent, ctx: ToolContext): void {
42
- if (!this.drawing || !this.preview) return;
43
- const world = screenToWorld(e.clientX, e.clientY, ctx.state.viewport);
44
- let dx = world.x - this.startWorld.x;
45
- let dy = world.y - this.startWorld.y;
46
- if (e.shiftKey) {
47
- // snap to 45-degree increments
48
- const angle = Math.atan2(dy, dx);
49
- const snapped = Math.round(angle / (Math.PI / 4)) * (Math.PI / 4);
50
- const len = Math.hypot(dx, dy);
51
- dx = Math.cos(snapped) * len;
52
- dy = Math.sin(snapped) * len;
53
- }
54
- this.preview.width = dx;
55
- this.preview.height = dy;
56
- ctx.state.requestRender();
57
- }
58
-
59
- onPointerUp(_e: PointerEvent, ctx: ToolContext): void {
60
- const { state, commands } = ctx;
61
- if (!this.drawing || !this.preview) return;
62
- this.drawing = false;
63
- ctx.renderer.overlay.preview = null;
64
-
65
- const el = this.preview;
66
- this.preview = null;
67
-
68
- if (Math.hypot(el.width, el.height) < 8) {
69
- state.requestRender();
70
- return;
71
- }
72
-
73
- commands.execute(
74
- "create-arrow",
75
- () => {
76
- if (!state.elementsById.has(el.id)) state.addElement(el);
77
- state.emitChange();
78
- },
79
- () => {
80
- state.removeElement(el.id);
81
- state.emitChange();
82
- }
83
- );
84
-
85
- state.clearSelection();
86
- state.selectedIds.add(el.id);
87
- ctx.setTool("select");
88
- if (this.name === "labeledArrow" || this.name === "sidedLabelArrow") ctx.openTextEditor(el);
89
- }
90
-
91
- onCancel(ctx: ToolContext): void {
92
- this.drawing = false;
93
- this.preview = null;
94
- ctx.renderer.overlay.preview = null;
95
- ctx.state.requestRender();
96
- }
97
- }
@@ -1,39 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
-
3
- /**
4
- * The image "tool" opens a file picker; insertion logic lives in
5
- * ImageInsertService which is shared with paste and drag-and-drop.
6
- */
7
- export class ImageTool implements Tool {
8
- name = "image" as const;
9
- cursor = "default";
10
-
11
- private onInsert: (files: FileList, x: number, y: number) => void;
12
-
13
- constructor(onInsert: (files: FileList, x: number, y: number) => void) {
14
- this.onInsert = onInsert;
15
- }
16
-
17
- onActivate(ctx: ToolContext): void {
18
- const input = document.createElement("input");
19
- input.type = "file";
20
- input.accept = "image/png,image/jpeg,image/webp,image/gif";
21
- input.multiple = true;
22
- input.onchange = () => {
23
- if (input.files && input.files.length > 0) {
24
- this.onInsert(
25
- input.files,
26
- window.innerWidth / 2,
27
- window.innerHeight / 2
28
- );
29
- }
30
- ctx.setTool("select");
31
- };
32
- input.oncancel = () => ctx.setTool("select");
33
- input.click();
34
- }
35
-
36
- onPointerDown(_e: PointerEvent, _ctx: ToolContext): void {}
37
- onPointerMove(_e: PointerEvent, _ctx: ToolContext): void {}
38
- onPointerUp(_e: PointerEvent, _ctx: ToolContext): void {}
39
- }
@@ -1,34 +0,0 @@
1
- import type { Tool, ToolContext } from "./ToolContext";
2
-
3
- export class PanTool implements Tool {
4
- name = "hand" as const;
5
- cursor = "grab";
6
-
7
- private panning = false;
8
- private last = { x: 0, y: 0 };
9
-
10
- onPointerDown(e: PointerEvent, ctx: ToolContext): void {
11
- this.panning = true;
12
- this.last = { x: e.clientX, y: e.clientY };
13
- ctx.setCursor("grabbing");
14
- }
15
-
16
- onPointerMove(e: PointerEvent, ctx: ToolContext): void {
17
- if (!this.panning) return;
18
- const vp = ctx.state.viewport;
19
- vp.offsetX += e.clientX - this.last.x;
20
- vp.offsetY += e.clientY - this.last.y;
21
- this.last = { x: e.clientX, y: e.clientY };
22
- ctx.state.requestRender();
23
- }
24
-
25
- onPointerUp(_e: PointerEvent, ctx: ToolContext): void {
26
- this.panning = false;
27
- ctx.setCursor("grab");
28
- }
29
-
30
- onCancel(ctx: ToolContext): void {
31
- this.panning = false;
32
- ctx.setCursor("grab");
33
- }
34
- }