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,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
|
-
}
|
package/src/tools/ArrowTool.ts
DELETED
|
@@ -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
|
-
}
|
package/src/tools/ImageTool.ts
DELETED
|
@@ -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
|
-
}
|
package/src/tools/PanTool.ts
DELETED
|
@@ -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
|
-
}
|