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.
- package/content/docs/index.md +1 -1
- package/content/docs/product-roadmap.board.json +4 -4
- package/content/machine/board-v1.schema.json +138 -0
- package/content/machine/example.board.json +217 -0
- package/content/machine/guide.md +270 -0
- package/content/machine/llms.txt +31 -0
- package/content/machine/viteboard.json +62 -0
- package/dist/assets/index-D2W76uu9.js +96 -0
- package/dist/assets/index-rMkUjGXP.css +1 -0
- package/dist/index.html +4 -2
- package/package.json +10 -12
- package/vite.config.ts +619 -38
- package/dist/assets/index-C30zRPsW.js +0 -60
- package/dist/assets/index-e5aqtN8W.css +0 -1
- package/index.html +0 -13
- package/src/app/AppController.ts +0 -962
- package/src/app/BoardState.ts +0 -130
- package/src/app/ClipboardService.ts +0 -159
- package/src/app/CommandManager.ts +0 -66
- package/src/app/ElementActions.ts +0 -152
- package/src/app/EmbedRegionSelector.ts +0 -103
- package/src/app/ExportPng.ts +0 -107
- package/src/app/ImageInsertService.ts +0 -141
- package/src/app/KeyboardShortcuts.ts +0 -124
- package/src/app/main.ts +0 -6
- package/src/board/BoardPreview.ts +0 -189
- package/src/board/BoardService.ts +0 -222
- package/src/canvas/CanvasRenderer.ts +0 -253
- package/src/canvas/CanvasSurface.ts +0 -70
- package/src/canvas/HitTester.ts +0 -110
- package/src/canvas/ImageCache.ts +0 -123
- package/src/canvas/PerformanceMonitor.ts +0 -31
- package/src/canvas/RenderScheduler.ts +0 -26
- package/src/canvas/Viewport.ts +0 -77
- package/src/content/ContentApi.ts +0 -258
- package/src/content/Markdown.ts +0 -431
- package/src/content/MarkdownView.ts +0 -70
- package/src/editor/DocEditor.ts +0 -799
- package/src/editor/htmlToMarkdown.ts +0 -333
- package/src/elements/renderElement.ts +0 -598
- package/src/elements/types.ts +0 -119
- package/src/shell/Shell.ts +0 -2948
- package/src/shell/Sidebar.ts +0 -1651
- package/src/storage/AssetStore.ts +0 -86
- package/src/storage/BoardSerializer.ts +0 -114
- package/src/storage/ImportExportService.ts +0 -153
- package/src/storage/IndexedDbStore.ts +0 -92
- package/src/storage/LocalBoardStore.ts +0 -104
- package/src/styles.css +0 -3332
- package/src/templates/handoffTemplate.ts +0 -34
- package/src/templates/helpers.ts +0 -132
- package/src/templates/index.ts +0 -79
- package/src/templates/journeyMapTemplate.ts +0 -52
- package/src/templates/opportunityTreeTemplate.ts +0 -45
- package/src/templates/personaTemplate.ts +0 -41
- package/src/templates/prioritizationMatrixTemplate.ts +0 -44
- package/src/templates/requirementsFlowTemplate.ts +0 -45
- package/src/templates/screenshotReviewTemplate.ts +0 -52
- package/src/templates/systemDiagramTemplate.ts +0 -41
- package/src/templates/workflowTemplate.ts +0 -35
- package/src/testing/StressTestGenerator.ts +0 -134
- package/src/testing/StressTestPanel.ts +0 -64
- package/src/tools/ArrowTool.ts +0 -97
- package/src/tools/ImageTool.ts +0 -39
- package/src/tools/PanTool.ts +0 -34
- package/src/tools/SelectTool.ts +0 -377
- package/src/tools/ShapeTool.ts +0 -106
- package/src/tools/StickyTool.ts +0 -69
- package/src/tools/TaskTool.ts +0 -52
- package/src/tools/TextTool.ts +0 -45
- package/src/tools/ToolContext.ts +0 -51
- package/src/tools/ToolController.ts +0 -178
- package/src/ui/Modal.ts +0 -58
- package/src/ui/PerformanceOverlay.ts +0 -75
- package/src/ui/StorageManager.ts +0 -94
- package/src/ui/TemplatePicker.ts +0 -67
- package/src/ui/TextEditorOverlay.ts +0 -262
- package/src/ui/Toolbar.ts +0 -189
- package/src/ui/TopControls.ts +0 -137
- package/src/ui/icons.ts +0 -56
- package/tsconfig.json +0 -19
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import type { Tool, ToolContext, ToolName } from "./ToolContext";
|
|
2
|
-
import { zoomAt } from "../canvas/Viewport";
|
|
3
|
-
|
|
4
|
-
const LINE_HEIGHT_PX = 16;
|
|
5
|
-
const PAGE_HEIGHT_PX = 720;
|
|
6
|
-
const MAX_WHEEL_DELTA = 120;
|
|
7
|
-
const ZOOM_SENSITIVITY = 0.0032;
|
|
8
|
-
|
|
9
|
-
export class ToolController {
|
|
10
|
-
private tools = new Map<ToolName, Tool>();
|
|
11
|
-
private active: Tool;
|
|
12
|
-
private ctx: ToolContext;
|
|
13
|
-
private target: HTMLElement;
|
|
14
|
-
|
|
15
|
-
/** spacebar / middle-mouse temporary pan */
|
|
16
|
-
private spaceDown = false;
|
|
17
|
-
private tempPanning = false;
|
|
18
|
-
private panLast = { x: 0, y: 0 };
|
|
19
|
-
|
|
20
|
-
private toolChangeListeners: ((tool: ToolName) => void)[] = [];
|
|
21
|
-
private signal?: AbortSignal;
|
|
22
|
-
|
|
23
|
-
constructor(target: HTMLElement, ctx: ToolContext, tools: Tool[], signal?: AbortSignal) {
|
|
24
|
-
this.target = target;
|
|
25
|
-
this.ctx = ctx;
|
|
26
|
-
this.signal = signal;
|
|
27
|
-
for (const tool of tools) this.tools.set(tool.name, tool);
|
|
28
|
-
this.active = this.tools.get("select")!;
|
|
29
|
-
this.bindEvents();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get activeName(): ToolName {
|
|
33
|
-
return this.active.name;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
onToolChange(fn: (tool: ToolName) => void): void {
|
|
37
|
-
this.toolChangeListeners.push(fn);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
setTool(name: ToolName): void {
|
|
41
|
-
if (this.active.name === name) {
|
|
42
|
-
// re-trigger activate for tools like image picker
|
|
43
|
-
this.active.onActivate?.(this.ctx);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
this.active.onDeactivate?.(this.ctx);
|
|
47
|
-
const tool = this.tools.get(name);
|
|
48
|
-
if (!tool) return;
|
|
49
|
-
this.active = tool;
|
|
50
|
-
this.ctx.setCursor(tool.cursor);
|
|
51
|
-
tool.onActivate?.(this.ctx);
|
|
52
|
-
for (const fn of this.toolChangeListeners) fn(name);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
cancel(): void {
|
|
56
|
-
this.active.onCancel?.(this.ctx);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private bindEvents(): void {
|
|
60
|
-
const t = this.target;
|
|
61
|
-
|
|
62
|
-
t.addEventListener("pointerdown", (e) => {
|
|
63
|
-
// middle mouse, right mouse, or spacebar temporarily pan.
|
|
64
|
-
if (e.button === 1 || e.button === 2 || this.spaceDown) {
|
|
65
|
-
e.preventDefault();
|
|
66
|
-
this.tempPanning = true;
|
|
67
|
-
this.panLast = { x: e.clientX, y: e.clientY };
|
|
68
|
-
this.ctx.setCursor("grabbing");
|
|
69
|
-
t.setPointerCapture(e.pointerId);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (e.button !== 0) return;
|
|
73
|
-
t.setPointerCapture(e.pointerId);
|
|
74
|
-
this.active.onPointerDown(e, this.ctx);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
t.addEventListener("pointermove", (e) => {
|
|
78
|
-
if (this.tempPanning) {
|
|
79
|
-
const vp = this.ctx.state.viewport;
|
|
80
|
-
vp.offsetX += e.clientX - this.panLast.x;
|
|
81
|
-
vp.offsetY += e.clientY - this.panLast.y;
|
|
82
|
-
this.panLast = { x: e.clientX, y: e.clientY };
|
|
83
|
-
this.ctx.state.requestRender();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
this.active.onPointerMove(e, this.ctx);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
t.addEventListener("pointerup", (e) => {
|
|
90
|
-
if (this.tempPanning) {
|
|
91
|
-
this.tempPanning = false;
|
|
92
|
-
this.ctx.setCursor(this.spaceDown ? "grab" : this.active.cursor);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
this.active.onPointerUp(e, this.ctx);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
t.addEventListener("pointercancel", () => {
|
|
99
|
-
if (!this.tempPanning) return;
|
|
100
|
-
this.tempPanning = false;
|
|
101
|
-
this.ctx.setCursor(this.spaceDown ? "grab" : this.active.cursor);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
t.addEventListener("dblclick", (e) => {
|
|
105
|
-
this.active.onDoubleClick?.(e as unknown as PointerEvent, this.ctx);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// wheel: zoom (ctrl/cmd or pinch) and trackpad pan
|
|
109
|
-
t.addEventListener(
|
|
110
|
-
"wheel",
|
|
111
|
-
(e) => {
|
|
112
|
-
e.preventDefault();
|
|
113
|
-
const vp = this.ctx.state.viewport;
|
|
114
|
-
const dx = normalizeWheelDelta(e.deltaX, e.deltaMode, this.ctx.state.viewport.scale);
|
|
115
|
-
const dy = normalizeWheelDelta(e.deltaY, e.deltaMode, this.ctx.state.viewport.scale);
|
|
116
|
-
if (e.ctrlKey || e.metaKey) {
|
|
117
|
-
// pinch-zoom or ctrl+wheel: cursor-centered zoom with normalized deltas
|
|
118
|
-
const factor = Math.exp(-clampWheelDelta(dy) * ZOOM_SENSITIVITY);
|
|
119
|
-
zoomAt(vp, e.clientX, e.clientY, vp.scale * factor);
|
|
120
|
-
} else {
|
|
121
|
-
// trackpad two-finger pan / mouse wheel scroll.
|
|
122
|
-
// Shift+wheel pans horizontally to make wide boards easier to navigate.
|
|
123
|
-
if (e.shiftKey && Math.abs(dx) < Math.abs(dy)) {
|
|
124
|
-
vp.offsetX -= dy;
|
|
125
|
-
} else {
|
|
126
|
-
vp.offsetX -= dx;
|
|
127
|
-
vp.offsetY -= dy;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
this.ctx.state.requestRender();
|
|
131
|
-
},
|
|
132
|
-
{ passive: false }
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
// spacebar temporary hand tool
|
|
136
|
-
window.addEventListener("keydown", (e) => {
|
|
137
|
-
if (e.code === "Space" && !this.isEditingText(e)) {
|
|
138
|
-
if (!this.spaceDown) {
|
|
139
|
-
this.spaceDown = true;
|
|
140
|
-
if (!this.tempPanning) this.ctx.setCursor("grab");
|
|
141
|
-
}
|
|
142
|
-
e.preventDefault();
|
|
143
|
-
}
|
|
144
|
-
}, { signal: this.signal });
|
|
145
|
-
|
|
146
|
-
window.addEventListener("keyup", (e) => {
|
|
147
|
-
if (e.code === "Space") {
|
|
148
|
-
this.spaceDown = false;
|
|
149
|
-
if (!this.tempPanning) this.ctx.setCursor(this.active.cursor);
|
|
150
|
-
}
|
|
151
|
-
}, { signal: this.signal });
|
|
152
|
-
|
|
153
|
-
// prevent context menu so right-drag can pan cleanly.
|
|
154
|
-
t.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private isEditingText(e: KeyboardEvent): boolean {
|
|
158
|
-
const target = e.target as HTMLElement | null;
|
|
159
|
-
return (
|
|
160
|
-
!!target &&
|
|
161
|
-
(target.tagName === "TEXTAREA" ||
|
|
162
|
-
target.tagName === "INPUT" ||
|
|
163
|
-
target.isContentEditable)
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function normalizeWheelDelta(delta: number, deltaMode: number, scale: number): number {
|
|
169
|
-
const modeFactor = deltaMode === 1 ? LINE_HEIGHT_PX : deltaMode === 2 ? PAGE_HEIGHT_PX : 1;
|
|
170
|
-
const pixels = delta * modeFactor;
|
|
171
|
-
// When zoomed far out, slightly reduce pan velocity to avoid overshooting.
|
|
172
|
-
const zoomFriction = scale < 0.5 ? 0.8 : scale > 2 ? 1.08 : 1;
|
|
173
|
-
return pixels * zoomFriction;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function clampWheelDelta(delta: number): number {
|
|
177
|
-
return Math.max(-MAX_WHEEL_DELTA, Math.min(MAX_WHEEL_DELTA, delta));
|
|
178
|
-
}
|
package/src/ui/Modal.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal modal helper. Content is built with DOM APIs (no user innerHTML).
|
|
3
|
-
*/
|
|
4
|
-
export function openModal(title: string, build: (body: HTMLDivElement, close: () => void) => void): void {
|
|
5
|
-
const backdrop = document.createElement("div");
|
|
6
|
-
backdrop.className = "modal-backdrop";
|
|
7
|
-
|
|
8
|
-
const modal = document.createElement("div");
|
|
9
|
-
modal.className = "modal";
|
|
10
|
-
modal.setAttribute("role", "dialog");
|
|
11
|
-
modal.setAttribute("aria-label", title);
|
|
12
|
-
|
|
13
|
-
const closeBtn = document.createElement("button");
|
|
14
|
-
closeBtn.className = "modal-close";
|
|
15
|
-
closeBtn.textContent = "✕";
|
|
16
|
-
closeBtn.setAttribute("aria-label", "Close");
|
|
17
|
-
|
|
18
|
-
const heading = document.createElement("h2");
|
|
19
|
-
heading.textContent = title;
|
|
20
|
-
|
|
21
|
-
const body = document.createElement("div");
|
|
22
|
-
|
|
23
|
-
const close = () => backdrop.remove();
|
|
24
|
-
closeBtn.addEventListener("click", close);
|
|
25
|
-
backdrop.addEventListener("click", (e) => {
|
|
26
|
-
if (e.target === backdrop) close();
|
|
27
|
-
});
|
|
28
|
-
backdrop.addEventListener("keydown", (e) => {
|
|
29
|
-
e.stopPropagation();
|
|
30
|
-
if (e.key === "Escape") close();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
modal.appendChild(closeBtn);
|
|
34
|
-
modal.appendChild(heading);
|
|
35
|
-
modal.appendChild(body);
|
|
36
|
-
backdrop.appendChild(modal);
|
|
37
|
-
document.body.appendChild(backdrop);
|
|
38
|
-
|
|
39
|
-
build(body, close);
|
|
40
|
-
closeBtn.focus();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let toastEl: HTMLDivElement | null = null;
|
|
44
|
-
let toastTimer: number | null = null;
|
|
45
|
-
|
|
46
|
-
export function showToast(message: string): void {
|
|
47
|
-
if (!toastEl) {
|
|
48
|
-
toastEl = document.createElement("div");
|
|
49
|
-
toastEl.className = "toast";
|
|
50
|
-
document.body.appendChild(toastEl);
|
|
51
|
-
}
|
|
52
|
-
toastEl.textContent = message;
|
|
53
|
-
toastEl.classList.add("visible");
|
|
54
|
-
if (toastTimer !== null) clearTimeout(toastTimer);
|
|
55
|
-
toastTimer = window.setTimeout(() => {
|
|
56
|
-
toastEl?.classList.remove("visible");
|
|
57
|
-
}, 2600);
|
|
58
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { BoardState } from "../app/BoardState";
|
|
2
|
-
import type { CanvasRenderer } from "../canvas/CanvasRenderer";
|
|
3
|
-
import type { HitTester } from "../canvas/HitTester";
|
|
4
|
-
import type { PerformanceMonitor } from "../canvas/PerformanceMonitor";
|
|
5
|
-
import type { ImageCache } from "../canvas/ImageCache";
|
|
6
|
-
import { boardStore } from "../storage/LocalBoardStore";
|
|
7
|
-
import { assetStore } from "../storage/AssetStore";
|
|
8
|
-
|
|
9
|
-
export class PerformanceOverlay {
|
|
10
|
-
element: HTMLDivElement;
|
|
11
|
-
visible = false;
|
|
12
|
-
|
|
13
|
-
private state: BoardState;
|
|
14
|
-
private renderer: CanvasRenderer;
|
|
15
|
-
private hitTester: HitTester;
|
|
16
|
-
private monitor: PerformanceMonitor;
|
|
17
|
-
private imageCache: ImageCache;
|
|
18
|
-
private timer: number | null = null;
|
|
19
|
-
private storageEstimate = "–";
|
|
20
|
-
|
|
21
|
-
constructor(
|
|
22
|
-
parent: HTMLElement,
|
|
23
|
-
state: BoardState,
|
|
24
|
-
renderer: CanvasRenderer,
|
|
25
|
-
hitTester: HitTester,
|
|
26
|
-
monitor: PerformanceMonitor,
|
|
27
|
-
imageCache: ImageCache
|
|
28
|
-
) {
|
|
29
|
-
this.state = state;
|
|
30
|
-
this.renderer = renderer;
|
|
31
|
-
this.hitTester = hitTester;
|
|
32
|
-
this.monitor = monitor;
|
|
33
|
-
this.imageCache = imageCache;
|
|
34
|
-
this.element = document.createElement("div");
|
|
35
|
-
this.element.className = "perf-overlay";
|
|
36
|
-
parent.appendChild(this.element);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
toggle(): void {
|
|
40
|
-
this.visible = !this.visible;
|
|
41
|
-
this.element.classList.toggle("visible", this.visible);
|
|
42
|
-
if (this.visible) {
|
|
43
|
-
this.monitor.start();
|
|
44
|
-
this.update();
|
|
45
|
-
this.timer = window.setInterval(() => this.update(), 500);
|
|
46
|
-
void this.refreshStorage();
|
|
47
|
-
} else if (this.timer !== null) {
|
|
48
|
-
clearInterval(this.timer);
|
|
49
|
-
this.timer = null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private async refreshStorage(): Promise<void> {
|
|
54
|
-
const bytes = await assetStore.totalSizeBytes();
|
|
55
|
-
this.storageEstimate = (bytes / (1024 * 1024)).toFixed(1) + "MB";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private update(): void {
|
|
59
|
-
const vp = this.state.viewport;
|
|
60
|
-
const lines = [
|
|
61
|
-
`FPS: ${this.monitor.fps}`,
|
|
62
|
-
`Total elements: ${this.state.elements.length.toLocaleString()}`,
|
|
63
|
-
`Visible elements: ${this.renderer.visibleCount.toLocaleString()}`,
|
|
64
|
-
`Render time: ${this.renderer.lastRenderMs.toFixed(1)}ms`,
|
|
65
|
-
`Hit-test time: ${this.hitTester.lastHitTestMs.toFixed(2)}ms`,
|
|
66
|
-
`Save time: ${boardStore.lastSaveMs.toFixed(0)}ms`,
|
|
67
|
-
`Image cache: ${this.imageCache.size}`,
|
|
68
|
-
`Estimated storage: ${this.storageEstimate}`,
|
|
69
|
-
`Zoom: ${Math.round(vp.scale * 100)}%`,
|
|
70
|
-
`Viewport: x: ${Math.round(-vp.offsetX / vp.scale)}, y: ${Math.round(-vp.offsetY / vp.scale)}`,
|
|
71
|
-
`Culling: ${this.renderer.cullingEnabled ? "on" : "off"} LOD: ${this.renderer.lodEnabled ? "on" : "off"}`,
|
|
72
|
-
];
|
|
73
|
-
this.element.textContent = lines.join("\n");
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/ui/StorageManager.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { openModal, showToast } from "./Modal";
|
|
2
|
-
import type { BoardState } from "../app/BoardState";
|
|
3
|
-
import { assetStore } from "../storage/AssetStore";
|
|
4
|
-
import { boardStore } from "../storage/LocalBoardStore";
|
|
5
|
-
|
|
6
|
-
function formatBytes(bytes: number): string {
|
|
7
|
-
if (bytes < 1024) return bytes + " B";
|
|
8
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
9
|
-
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function openStorageManager(
|
|
13
|
-
state: BoardState,
|
|
14
|
-
onClearBoard: () => void,
|
|
15
|
-
onResetAll: () => void
|
|
16
|
-
): void {
|
|
17
|
-
openModal("Local storage", async (body, close) => {
|
|
18
|
-
const rows = document.createElement("div");
|
|
19
|
-
body.appendChild(rows);
|
|
20
|
-
|
|
21
|
-
const addRow = (name: string, value: string) => {
|
|
22
|
-
const row = document.createElement("div");
|
|
23
|
-
row.className = "storage-row";
|
|
24
|
-
const a = document.createElement("span");
|
|
25
|
-
a.textContent = name;
|
|
26
|
-
const b = document.createElement("span");
|
|
27
|
-
b.textContent = value;
|
|
28
|
-
row.appendChild(a);
|
|
29
|
-
row.appendChild(b);
|
|
30
|
-
rows.appendChild(row);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
addRow("Elements on board", String(state.elements.length));
|
|
34
|
-
|
|
35
|
-
const boardJson = JSON.stringify(state.elements);
|
|
36
|
-
addRow("Board data size", formatBytes(boardJson.length));
|
|
37
|
-
|
|
38
|
-
const assetKeys = await assetStore.getAllKeys();
|
|
39
|
-
addRow("Image assets", String(assetKeys.length));
|
|
40
|
-
|
|
41
|
-
const assetBytes = await assetStore.totalSizeBytes();
|
|
42
|
-
addRow("Image asset size", formatBytes(assetBytes));
|
|
43
|
-
|
|
44
|
-
const est = await boardStore.estimateStorage();
|
|
45
|
-
if (est.quota > 0) {
|
|
46
|
-
addRow(
|
|
47
|
-
"Browser storage",
|
|
48
|
-
`${formatBytes(est.usage)} of ${formatBytes(est.quota)}`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const actions = document.createElement("div");
|
|
53
|
-
actions.className = "modal-actions";
|
|
54
|
-
|
|
55
|
-
const clearUnusedBtn = document.createElement("button");
|
|
56
|
-
clearUnusedBtn.className = "btn";
|
|
57
|
-
clearUnusedBtn.textContent = "Clear unused assets";
|
|
58
|
-
clearUnusedBtn.addEventListener("click", async () => {
|
|
59
|
-
const referenced = new Set<string>();
|
|
60
|
-
for (const el of state.elements) {
|
|
61
|
-
const id = el.data.assetId;
|
|
62
|
-
if (typeof id === "string") referenced.add(id);
|
|
63
|
-
}
|
|
64
|
-
const removed = await assetStore.clearUnused(referenced);
|
|
65
|
-
showToast(`Removed ${removed} unused asset${removed === 1 ? "" : "s"}`);
|
|
66
|
-
close();
|
|
67
|
-
});
|
|
68
|
-
actions.appendChild(clearUnusedBtn);
|
|
69
|
-
|
|
70
|
-
const clearBoardBtn = document.createElement("button");
|
|
71
|
-
clearBoardBtn.className = "btn danger";
|
|
72
|
-
clearBoardBtn.textContent = "Clear board";
|
|
73
|
-
clearBoardBtn.addEventListener("click", () => {
|
|
74
|
-
if (confirm("Remove all elements from this board?")) {
|
|
75
|
-
onClearBoard();
|
|
76
|
-
close();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
actions.appendChild(clearBoardBtn);
|
|
80
|
-
|
|
81
|
-
const resetBtn = document.createElement("button");
|
|
82
|
-
resetBtn.className = "btn danger";
|
|
83
|
-
resetBtn.textContent = "Reset all local data";
|
|
84
|
-
resetBtn.addEventListener("click", () => {
|
|
85
|
-
if (confirm("Delete ALL local boards, assets and preferences? This cannot be undone.")) {
|
|
86
|
-
onResetAll();
|
|
87
|
-
close();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
actions.appendChild(resetBtn);
|
|
91
|
-
|
|
92
|
-
body.appendChild(actions);
|
|
93
|
-
});
|
|
94
|
-
}
|
package/src/ui/TemplatePicker.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { openModal } from "./Modal";
|
|
2
|
-
import { TEMPLATES } from "../templates";
|
|
3
|
-
import type { BoardState } from "../app/BoardState";
|
|
4
|
-
import type { CommandManager } from "../app/CommandManager";
|
|
5
|
-
import { screenToWorld } from "../canvas/Viewport";
|
|
6
|
-
import type { BoardElement } from "../elements/types";
|
|
7
|
-
|
|
8
|
-
export function openTemplatePicker(state: BoardState, commands: CommandManager): void {
|
|
9
|
-
openModal("Insert a template", (body, close) => {
|
|
10
|
-
const grid = document.createElement("div");
|
|
11
|
-
grid.className = "template-grid";
|
|
12
|
-
|
|
13
|
-
for (const def of TEMPLATES) {
|
|
14
|
-
const card = document.createElement("button");
|
|
15
|
-
card.className = "template-card";
|
|
16
|
-
|
|
17
|
-
const name = document.createElement("span");
|
|
18
|
-
name.className = "t-name";
|
|
19
|
-
name.textContent = def.name;
|
|
20
|
-
|
|
21
|
-
const desc = document.createElement("span");
|
|
22
|
-
desc.className = "t-desc";
|
|
23
|
-
desc.textContent = def.description;
|
|
24
|
-
|
|
25
|
-
card.appendChild(name);
|
|
26
|
-
card.appendChild(desc);
|
|
27
|
-
card.addEventListener("click", () => {
|
|
28
|
-
insertTemplate(state, commands, def.build);
|
|
29
|
-
close();
|
|
30
|
-
});
|
|
31
|
-
grid.appendChild(card);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
body.appendChild(grid);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function insertTemplate(
|
|
39
|
-
state: BoardState,
|
|
40
|
-
commands: CommandManager,
|
|
41
|
-
build: (ox: number, oy: number, theme: "light" | "dark", baseZ: number) => BoardElement[]
|
|
42
|
-
): void {
|
|
43
|
-
const center = screenToWorld(
|
|
44
|
-
window.innerWidth / 2,
|
|
45
|
-
window.innerHeight / 2,
|
|
46
|
-
state.viewport
|
|
47
|
-
);
|
|
48
|
-
const elements = build(center.x - 380, center.y - 280, state.theme, state.maxZIndex());
|
|
49
|
-
|
|
50
|
-
commands.execute(
|
|
51
|
-
"insert-template",
|
|
52
|
-
() => {
|
|
53
|
-
for (const el of elements) {
|
|
54
|
-
if (!state.elementsById.has(el.id)) state.addElement(el);
|
|
55
|
-
}
|
|
56
|
-
state.emitChange();
|
|
57
|
-
},
|
|
58
|
-
() => {
|
|
59
|
-
for (const el of elements) state.removeElement(el.id);
|
|
60
|
-
state.emitChange();
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
state.clearSelection();
|
|
65
|
-
for (const el of elements) state.selectedIds.add(el.id);
|
|
66
|
-
state.requestRender();
|
|
67
|
-
}
|