orio-ui 1.20.0 → 1.23.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/README.md +12 -5
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -2
- package/dist/runtime/canvas.d.ts +21 -0
- package/dist/runtime/canvas.js +49 -0
- package/dist/runtime/components/Canvas/REQUIREMENTS.md +174 -0
- package/dist/runtime/components/Canvas/components/Stage.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/components/Stage.vue +150 -0
- package/dist/runtime/components/Canvas/components/Stage.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/components/ToolButton.d.vue.ts +24 -0
- package/dist/runtime/components/Canvas/components/ToolButton.vue +62 -0
- package/dist/runtime/components/Canvas/components/ToolButton.vue.d.ts +24 -0
- package/dist/runtime/components/Canvas/components/Toolbar.d.vue.ts +24 -0
- package/dist/runtime/components/Canvas/components/Toolbar.vue +48 -0
- package/dist/runtime/components/Canvas/components/Toolbar.vue.d.ts +24 -0
- package/dist/runtime/components/Canvas/composables/useCanvasHistory.d.ts +17 -0
- package/dist/runtime/components/Canvas/composables/useCanvasHistory.js +76 -0
- package/dist/runtime/components/Canvas/composables/useCanvasNodes.d.ts +13 -0
- package/dist/runtime/components/Canvas/composables/useCanvasNodes.js +60 -0
- package/dist/runtime/components/Canvas/composables/useCanvasSetup.d.ts +5 -0
- package/dist/runtime/components/Canvas/composables/useCanvasSetup.js +19 -0
- package/dist/runtime/components/Canvas/context.d.ts +38 -0
- package/dist/runtime/components/Canvas/context.js +11 -0
- package/dist/runtime/components/Canvas/index.d.vue.ts +77 -0
- package/dist/runtime/components/Canvas/index.vue +208 -0
- package/dist/runtime/components/Canvas/index.vue.d.ts +77 -0
- package/dist/runtime/components/Canvas/registry.d.ts +1 -0
- package/dist/runtime/components/Canvas/registry.js +2 -0
- package/dist/runtime/components/Canvas/tools/ColorPickerWidget.d.vue.ts +7 -0
- package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue +32 -0
- package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue.d.ts +7 -0
- package/dist/runtime/components/Canvas/tools/clearTool.d.ts +1 -0
- package/dist/runtime/components/Canvas/tools/clearTool.js +16 -0
- package/dist/runtime/components/Canvas/tools/colorPickerTool.d.ts +6 -0
- package/dist/runtime/components/Canvas/tools/colorPickerTool.js +15 -0
- package/dist/runtime/components/Canvas/tools/drawTool.d.ts +16 -0
- package/dist/runtime/components/Canvas/tools/drawTool.js +92 -0
- package/dist/runtime/components/Canvas/tools/eraseTool.d.ts +5 -0
- package/dist/runtime/components/Canvas/tools/eraseTool.js +62 -0
- package/dist/runtime/components/Canvas/tools/exportTool.d.ts +18 -0
- package/dist/runtime/components/Canvas/tools/exportTool.js +89 -0
- package/dist/runtime/components/Canvas/tools/highlightTool.d.ts +11 -0
- package/dist/runtime/components/Canvas/tools/highlightTool.js +51 -0
- package/dist/runtime/components/Canvas/tools/hitTest.d.ts +20 -0
- package/dist/runtime/components/Canvas/tools/hitTest.js +111 -0
- package/dist/runtime/components/Canvas/tools/imageTool.d.ts +18 -0
- package/dist/runtime/components/Canvas/tools/imageTool.js +163 -0
- package/dist/runtime/components/Canvas/tools/moveTool.d.ts +5 -0
- package/dist/runtime/components/Canvas/tools/moveTool.js +94 -0
- package/dist/runtime/components/Canvas/tools/redoTool.d.ts +1 -0
- package/dist/runtime/components/Canvas/tools/redoTool.js +17 -0
- package/dist/runtime/components/Canvas/tools/resizeTool.d.ts +7 -0
- package/dist/runtime/components/Canvas/tools/resizeTool.js +132 -0
- package/dist/runtime/components/Canvas/tools/rotateTool.d.ts +5 -0
- package/dist/runtime/components/Canvas/tools/rotateTool.js +109 -0
- package/dist/runtime/components/Canvas/tools/textTool.d.ts +14 -0
- package/dist/runtime/components/Canvas/tools/textTool.js +99 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Clear.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue +12 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Draw.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue +12 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Erase.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue +12 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Export.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Export.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Export.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Highlight.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue +12 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Image.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Image.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Image.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Move.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Move.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Move.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Redo.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Resize.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Rotate.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Text.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Text.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Text.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Transform.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue +14 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Undo.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue +13 -0
- package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/transformHandles.d.ts +74 -0
- package/dist/runtime/components/Canvas/tools/transformHandles.js +191 -0
- package/dist/runtime/components/Canvas/tools/transformTool.d.ts +7 -0
- package/dist/runtime/components/Canvas/tools/transformTool.js +210 -0
- package/dist/runtime/components/Canvas/tools/undoTool.d.ts +1 -0
- package/dist/runtime/components/Canvas/tools/undoTool.js +17 -0
- package/dist/runtime/components/Canvas/types.d.ts +125 -0
- package/dist/runtime/components/Canvas/types.js +3 -0
- package/dist/runtime/components/ControlElement.vue +5 -1
- package/dist/runtime/components/DateRangePicker.vue +16 -4
- package/dist/runtime/components/Icon.vue +2 -2
- package/dist/runtime/components/LocaleSwitcher.d.vue.ts +13 -0
- package/dist/runtime/components/LocaleSwitcher.vue +43 -0
- package/dist/runtime/components/LocaleSwitcher.vue.d.ts +13 -0
- package/dist/runtime/components/Selector.vue +14 -5
- package/dist/runtime/components/Tooltip.vue +17 -7
- package/dist/runtime/components/ZoomableContainer.d.vue.ts +48 -0
- package/dist/runtime/components/ZoomableContainer.vue +238 -0
- package/dist/runtime/components/ZoomableContainer.vue.d.ts +48 -0
- package/dist/runtime/components/gallery/Carousel.vue +1 -1
- package/dist/runtime/components/gallery/CarouselPreview.d.vue.ts +31 -0
- package/dist/runtime/components/gallery/CarouselPreview.vue +64 -0
- package/dist/runtime/components/gallery/CarouselPreview.vue.d.ts +31 -0
- package/dist/runtime/components/view/Dates.vue +5 -3
- package/dist/runtime/components/view/KeyBinds.d.vue.ts +7 -0
- package/dist/runtime/components/view/KeyBinds.vue +36 -0
- package/dist/runtime/components/view/KeyBinds.vue.d.ts +7 -0
- package/dist/runtime/components/view/Text.vue +4 -4
- package/dist/runtime/composables/useInertia.d.ts +10 -0
- package/dist/runtime/composables/useInertia.js +49 -0
- package/dist/runtime/composables/usePinchZoom.d.ts +13 -0
- package/dist/runtime/composables/usePinchZoom.js +66 -0
- package/dist/runtime/composables/useValidation.js +11 -1
- package/dist/runtime/i18n/en.json +20 -0
- package/dist/runtime/i18n/index.d.ts +11 -0
- package/dist/runtime/i18n/index.js +19 -0
- package/dist/runtime/i18n/uk.json +20 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +16 -0
- package/dist/runtime/plugins/i18n.d.ts +2 -0
- package/dist/runtime/plugins/i18n.js +18 -0
- package/dist/runtime/utils/icon-registry.js +13 -1
- package/package.json +9 -4
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import ImageTooltip from "./tooltips/Image.vue";
|
|
3
|
+
const DEFAULT_ACCEPT = "image/png,image/jpeg,image/webp,image/gif,image/svg+xml,image/avif,image/bmp";
|
|
4
|
+
function makeCache() {
|
|
5
|
+
return /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
function fileToDataUrl(file) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const reader = new FileReader();
|
|
10
|
+
reader.onload = () => resolve(reader.result);
|
|
11
|
+
reader.onerror = () => reject(reader.error);
|
|
12
|
+
reader.readAsDataURL(file);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function decodeImage(src) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const img = new Image();
|
|
18
|
+
img.crossOrigin = "anonymous";
|
|
19
|
+
img.onload = () => resolve(img);
|
|
20
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
|
21
|
+
img.src = src;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function imageTool(options = {}) {
|
|
25
|
+
const cache = makeCache();
|
|
26
|
+
let latestApi = null;
|
|
27
|
+
function ensureLoaded(src) {
|
|
28
|
+
const existing = cache.get(src);
|
|
29
|
+
if (existing) return existing;
|
|
30
|
+
const img = new Image();
|
|
31
|
+
img.crossOrigin = "anonymous";
|
|
32
|
+
const entry = { img, failed: false };
|
|
33
|
+
const triggerRender = () => {
|
|
34
|
+
if (latestApi) {
|
|
35
|
+
latestApi.requestRender();
|
|
36
|
+
} else {
|
|
37
|
+
const poll = () => {
|
|
38
|
+
if (latestApi) latestApi.requestRender();
|
|
39
|
+
else requestAnimationFrame(poll);
|
|
40
|
+
};
|
|
41
|
+
requestAnimationFrame(poll);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
img.onload = triggerRender;
|
|
45
|
+
img.onerror = () => {
|
|
46
|
+
entry.failed = true;
|
|
47
|
+
console.warn(`[imageTool] Failed to load image: ${src}`);
|
|
48
|
+
triggerRender();
|
|
49
|
+
};
|
|
50
|
+
img.src = src;
|
|
51
|
+
cache.set(src, entry);
|
|
52
|
+
return entry;
|
|
53
|
+
}
|
|
54
|
+
function pickFiles(input) {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
let settled = false;
|
|
57
|
+
let focusTimer = null;
|
|
58
|
+
const cleanup = () => {
|
|
59
|
+
input.onchange = null;
|
|
60
|
+
input.oncancel = null;
|
|
61
|
+
window.removeEventListener("focus", onFocus);
|
|
62
|
+
if (focusTimer !== null) {
|
|
63
|
+
clearTimeout(focusTimer);
|
|
64
|
+
focusTimer = null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const settle = (value) => {
|
|
68
|
+
if (settled) return;
|
|
69
|
+
settled = true;
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve(value);
|
|
72
|
+
};
|
|
73
|
+
const onFocus = () => {
|
|
74
|
+
focusTimer = setTimeout(() => {
|
|
75
|
+
if (!settled) {
|
|
76
|
+
settle(input.files && input.files.length > 0 ? input.files : null);
|
|
77
|
+
}
|
|
78
|
+
}, 300);
|
|
79
|
+
};
|
|
80
|
+
input.onchange = () => settle(input.files);
|
|
81
|
+
input.oncancel = () => settle(null);
|
|
82
|
+
window.addEventListener("focus", onFocus);
|
|
83
|
+
input.click();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function pickAndAdd(api) {
|
|
87
|
+
latestApi = api;
|
|
88
|
+
const input = document.createElement("input");
|
|
89
|
+
input.type = "file";
|
|
90
|
+
input.accept = api.options.accept;
|
|
91
|
+
input.multiple = api.options.multiple;
|
|
92
|
+
const file = await pickFiles(input);
|
|
93
|
+
if (!file || file.length === 0) return;
|
|
94
|
+
const { width: cw, height: ch } = api.size();
|
|
95
|
+
for (const f of Array.from(file)) {
|
|
96
|
+
try {
|
|
97
|
+
const src = await fileToDataUrl(f);
|
|
98
|
+
const img = await decodeImage(src);
|
|
99
|
+
cache.set(src, { img, failed: false });
|
|
100
|
+
const max = api.options.maxSize;
|
|
101
|
+
const scale = Math.min(
|
|
102
|
+
1,
|
|
103
|
+
max / Math.max(img.naturalWidth, img.naturalHeight)
|
|
104
|
+
);
|
|
105
|
+
const w = img.naturalWidth * scale;
|
|
106
|
+
const h = img.naturalHeight * scale;
|
|
107
|
+
api.addNode({
|
|
108
|
+
type: "image",
|
|
109
|
+
x: (cw - w) / 2,
|
|
110
|
+
y: (ch - h) / 2,
|
|
111
|
+
width: w,
|
|
112
|
+
height: h,
|
|
113
|
+
data: {
|
|
114
|
+
src,
|
|
115
|
+
naturalWidth: img.naturalWidth,
|
|
116
|
+
naturalHeight: img.naturalHeight,
|
|
117
|
+
alt: f.name
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return defineCanvasTool({
|
|
125
|
+
id: "image",
|
|
126
|
+
label: "Image",
|
|
127
|
+
icon: "image",
|
|
128
|
+
kind: "action",
|
|
129
|
+
tooltip: ImageTooltip,
|
|
130
|
+
defaultOptions: {
|
|
131
|
+
accept: DEFAULT_ACCEPT,
|
|
132
|
+
maxSize: 320,
|
|
133
|
+
multiple: false,
|
|
134
|
+
...options
|
|
135
|
+
},
|
|
136
|
+
disabled(api) {
|
|
137
|
+
latestApi = api;
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
action(api) {
|
|
141
|
+
latestApi = api;
|
|
142
|
+
void pickAndAdd(api);
|
|
143
|
+
},
|
|
144
|
+
render(ctx, node) {
|
|
145
|
+
const data = node.data;
|
|
146
|
+
if (!data?.src) return;
|
|
147
|
+
const w = node.width ?? data.naturalWidth;
|
|
148
|
+
const h = node.height ?? data.naturalHeight;
|
|
149
|
+
const entry = ensureLoaded(data.src);
|
|
150
|
+
if (!entry.failed && entry.img.complete && entry.img.naturalWidth > 0) {
|
|
151
|
+
ctx.drawImage(entry.img, node.x, node.y, w, h);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
ctx.save();
|
|
155
|
+
ctx.fillStyle = entry.failed ? "rgba(200, 0, 0, 0.08)" : "rgba(0, 0, 0, 0.05)";
|
|
156
|
+
ctx.fillRect(node.x, node.y, w, h);
|
|
157
|
+
ctx.strokeStyle = entry.failed ? "rgba(200, 0, 0, 0.3)" : "rgba(0, 0, 0, 0.2)";
|
|
158
|
+
ctx.lineWidth = 1;
|
|
159
|
+
ctx.strokeRect(node.x, node.y, w, h);
|
|
160
|
+
ctx.restore();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import { findTopNode, renderHighlightRect } from "./hitTest.js";
|
|
3
|
+
import MoveTooltip from "./tooltips/Move.vue";
|
|
4
|
+
export function moveTool(options = {}) {
|
|
5
|
+
let activeNodeId = null;
|
|
6
|
+
let hoveredNodeId = null;
|
|
7
|
+
let lastX = 0;
|
|
8
|
+
let lastY = 0;
|
|
9
|
+
return defineCanvasTool({
|
|
10
|
+
id: "move",
|
|
11
|
+
label: "Move",
|
|
12
|
+
icon: "move",
|
|
13
|
+
cursor: "grab",
|
|
14
|
+
tooltip: MoveTooltip,
|
|
15
|
+
defaultOptions: {
|
|
16
|
+
radius: 10,
|
|
17
|
+
...options
|
|
18
|
+
},
|
|
19
|
+
onPointerDown(e, api) {
|
|
20
|
+
const node = findTopNode(e, api, api.options.radius);
|
|
21
|
+
if (!node) return;
|
|
22
|
+
activeNodeId = node.id;
|
|
23
|
+
hoveredNodeId = node.id;
|
|
24
|
+
lastX = e.x;
|
|
25
|
+
lastY = e.y;
|
|
26
|
+
},
|
|
27
|
+
onPointerMove(e, api) {
|
|
28
|
+
if (activeNodeId && e.buttons !== 0) {
|
|
29
|
+
const node2 = api.getNode(activeNodeId);
|
|
30
|
+
if (!node2) return;
|
|
31
|
+
const dx = e.x - lastX;
|
|
32
|
+
const dy = e.y - lastY;
|
|
33
|
+
lastX = e.x;
|
|
34
|
+
lastY = e.y;
|
|
35
|
+
const patch = {
|
|
36
|
+
x: node2.x + dx,
|
|
37
|
+
y: node2.y + dy
|
|
38
|
+
};
|
|
39
|
+
if (Array.isArray(node2.data?.points)) {
|
|
40
|
+
const data = node2.data;
|
|
41
|
+
patch.data = {
|
|
42
|
+
...node2.data,
|
|
43
|
+
points: data.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
api.updateNode(activeNodeId, patch);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const node = findTopNode(e, api, api.options.radius);
|
|
50
|
+
const newId = node?.id ?? null;
|
|
51
|
+
if (newId !== hoveredNodeId) {
|
|
52
|
+
hoveredNodeId = newId;
|
|
53
|
+
api.requestRender();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
onPointerUp() {
|
|
57
|
+
activeNodeId = null;
|
|
58
|
+
},
|
|
59
|
+
onDeactivate(api) {
|
|
60
|
+
if (hoveredNodeId) {
|
|
61
|
+
hoveredNodeId = null;
|
|
62
|
+
api.requestRender();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onKeyDown(e, api) {
|
|
66
|
+
const targetId = activeNodeId ?? hoveredNodeId;
|
|
67
|
+
if (!targetId) return;
|
|
68
|
+
if (e.key !== "[" && e.key !== "]") return;
|
|
69
|
+
const node = api.getNode(targetId);
|
|
70
|
+
if (!node) return;
|
|
71
|
+
const nodes = api.nodes.value;
|
|
72
|
+
const zIndices = nodes.map((n) => n.zIndex ?? 0);
|
|
73
|
+
const currentZ = node.zIndex ?? 0;
|
|
74
|
+
if (e.key === "]") {
|
|
75
|
+
const maxZ = Math.max(...zIndices);
|
|
76
|
+
if (currentZ < maxZ) {
|
|
77
|
+
api.updateNode(targetId, { zIndex: maxZ + 1 });
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const minZ = Math.min(...zIndices);
|
|
81
|
+
if (currentZ > minZ) {
|
|
82
|
+
api.updateNode(targetId, { zIndex: minZ - 1 });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
renderOverlay(ctx, api) {
|
|
87
|
+
const id = activeNodeId ?? hoveredNodeId;
|
|
88
|
+
if (!id) return;
|
|
89
|
+
const node = api.getNode(id);
|
|
90
|
+
if (!node) return;
|
|
91
|
+
renderHighlightRect(ctx, node);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function redoTool(): import("../types.js").CanvasTool<unknown, Record<string, unknown>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import Redo from "./tooltips/Redo.vue";
|
|
3
|
+
export function redoTool() {
|
|
4
|
+
return defineCanvasTool({
|
|
5
|
+
id: "redo",
|
|
6
|
+
label: "Redo",
|
|
7
|
+
icon: "redo",
|
|
8
|
+
kind: "action",
|
|
9
|
+
tooltip: Redo,
|
|
10
|
+
action(api) {
|
|
11
|
+
api.redo();
|
|
12
|
+
},
|
|
13
|
+
disabled(api) {
|
|
14
|
+
return !api.canRedo.value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ResizeToolOptions extends Record<string, unknown> {
|
|
2
|
+
/** Hit-test radius in CSS pixels for picking a node to select. */
|
|
3
|
+
radius: number;
|
|
4
|
+
/** Minimum side length when resizing. */
|
|
5
|
+
minSize: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function resizeTool(options?: Partial<ResizeToolOptions>): import("../types.js").CanvasTool<never, ResizeToolOptions>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import {
|
|
3
|
+
findTopNode,
|
|
4
|
+
getNodeBounds,
|
|
5
|
+
renderHighlightRect,
|
|
6
|
+
toLocalPoint
|
|
7
|
+
} from "./hitTest.js";
|
|
8
|
+
import {
|
|
9
|
+
computeResizePatch,
|
|
10
|
+
cursorForHandle,
|
|
11
|
+
hitHandle,
|
|
12
|
+
renderResizeHandles
|
|
13
|
+
} from "./transformHandles.js";
|
|
14
|
+
import ResizeTooltip from "./tooltips/Resize.vue";
|
|
15
|
+
const RESIZE_HANDLES = [
|
|
16
|
+
"n",
|
|
17
|
+
"s",
|
|
18
|
+
"e",
|
|
19
|
+
"w",
|
|
20
|
+
"nw",
|
|
21
|
+
"ne",
|
|
22
|
+
"sw",
|
|
23
|
+
"se"
|
|
24
|
+
];
|
|
25
|
+
export function resizeTool(options = {}) {
|
|
26
|
+
let selectedNodeId = null;
|
|
27
|
+
let activeHandle = null;
|
|
28
|
+
let dragCtx = null;
|
|
29
|
+
let hoveredHandle = null;
|
|
30
|
+
function clearSelection(api) {
|
|
31
|
+
if (selectedNodeId) {
|
|
32
|
+
selectedNodeId = null;
|
|
33
|
+
hoveredHandle = null;
|
|
34
|
+
api.setCursor(null);
|
|
35
|
+
api.requestRender();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return defineCanvasTool({
|
|
39
|
+
id: "resize",
|
|
40
|
+
label: "Resize",
|
|
41
|
+
icon: "resize",
|
|
42
|
+
cursor: "default",
|
|
43
|
+
tooltip: ResizeTooltip,
|
|
44
|
+
defaultOptions: {
|
|
45
|
+
radius: 10,
|
|
46
|
+
minSize: 8,
|
|
47
|
+
...options
|
|
48
|
+
},
|
|
49
|
+
onPointerDown(e, api) {
|
|
50
|
+
if (selectedNodeId) {
|
|
51
|
+
const node2 = api.getNode(selectedNodeId);
|
|
52
|
+
if (node2) {
|
|
53
|
+
const handle = hitHandle(node2, e, { handles: RESIZE_HANDLES });
|
|
54
|
+
if (handle && handle !== "rotate") {
|
|
55
|
+
activeHandle = handle;
|
|
56
|
+
dragCtx = {
|
|
57
|
+
original: {
|
|
58
|
+
...node2,
|
|
59
|
+
data: JSON.parse(JSON.stringify(node2.data))
|
|
60
|
+
},
|
|
61
|
+
originalBounds: getNodeBounds(node2),
|
|
62
|
+
startLocal: toLocalPoint(node2, e)
|
|
63
|
+
};
|
|
64
|
+
api.setCursor("none");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const node = findTopNode(e, api, api.options.radius);
|
|
70
|
+
selectedNodeId = node ? node.id : null;
|
|
71
|
+
hoveredHandle = null;
|
|
72
|
+
api.requestRender();
|
|
73
|
+
},
|
|
74
|
+
onPointerMove(e, api) {
|
|
75
|
+
if (activeHandle && dragCtx && selectedNodeId && e.buttons !== 0) {
|
|
76
|
+
const node = api.getNode(selectedNodeId);
|
|
77
|
+
if (!node) return;
|
|
78
|
+
const local = toLocalPoint(dragCtx.original, e);
|
|
79
|
+
const patch = computeResizePatch(activeHandle, local, dragCtx, {
|
|
80
|
+
minSize: api.options.minSize
|
|
81
|
+
});
|
|
82
|
+
if (patch) api.updateNode(selectedNodeId, patch);
|
|
83
|
+
api.setCursor("none");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (selectedNodeId) {
|
|
87
|
+
const node = api.getNode(selectedNodeId);
|
|
88
|
+
if (node) {
|
|
89
|
+
const handle = hitHandle(node, e, { handles: RESIZE_HANDLES });
|
|
90
|
+
const next = handle && handle !== "rotate" ? handle : null;
|
|
91
|
+
if (next !== hoveredHandle) {
|
|
92
|
+
hoveredHandle = next;
|
|
93
|
+
api.requestRender();
|
|
94
|
+
}
|
|
95
|
+
api.setCursor(next ? cursorForHandle(next, node.rotation) : null);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
api.setCursor(null);
|
|
100
|
+
},
|
|
101
|
+
onPointerUp(_e, api) {
|
|
102
|
+
activeHandle = null;
|
|
103
|
+
dragCtx = null;
|
|
104
|
+
api.setCursor(null);
|
|
105
|
+
},
|
|
106
|
+
onDeactivate(api) {
|
|
107
|
+
activeHandle = null;
|
|
108
|
+
dragCtx = null;
|
|
109
|
+
api.setCursor(null);
|
|
110
|
+
clearSelection(api);
|
|
111
|
+
},
|
|
112
|
+
onKeyDown(e, api) {
|
|
113
|
+
if (e.key === "Escape" && selectedNodeId) {
|
|
114
|
+
clearSelection(api);
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
renderOverlay(ctx, api) {
|
|
119
|
+
if (!selectedNodeId) return;
|
|
120
|
+
const node = api.getNode(selectedNodeId);
|
|
121
|
+
if (!node) {
|
|
122
|
+
selectedNodeId = null;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
renderHighlightRect(ctx, node);
|
|
126
|
+
renderResizeHandles(ctx, node, {
|
|
127
|
+
handles: RESIZE_HANDLES,
|
|
128
|
+
hovered: activeHandle ?? hoveredHandle
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface RotateToolOptions extends Record<string, unknown> {
|
|
2
|
+
/** Hit-test radius in CSS pixels for picking a node to select. */
|
|
3
|
+
radius: number;
|
|
4
|
+
}
|
|
5
|
+
export declare function rotateTool(options?: Partial<RotateToolOptions>): import("../types.js").CanvasTool<never, RotateToolOptions>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import { findTopNode, getNodeBounds, renderHighlightRect } from "./hitTest.js";
|
|
3
|
+
import {
|
|
4
|
+
cursorForHandle,
|
|
5
|
+
hitHandle,
|
|
6
|
+
normalizeAngleDelta,
|
|
7
|
+
renderRotationHandle
|
|
8
|
+
} from "./transformHandles.js";
|
|
9
|
+
import RotateTooltip from "./tooltips/Rotate.vue";
|
|
10
|
+
export function rotateTool(options = {}) {
|
|
11
|
+
let selectedNodeId = null;
|
|
12
|
+
let prevAngle = 0;
|
|
13
|
+
let accumulatedRotation = 0;
|
|
14
|
+
let dragging = false;
|
|
15
|
+
function clearSelection(api) {
|
|
16
|
+
dragging = false;
|
|
17
|
+
prevAngle = 0;
|
|
18
|
+
if (selectedNodeId) {
|
|
19
|
+
selectedNodeId = null;
|
|
20
|
+
api.setCursor(null);
|
|
21
|
+
api.requestRender();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return defineCanvasTool({
|
|
25
|
+
id: "rotate",
|
|
26
|
+
label: "Rotate",
|
|
27
|
+
icon: "rotate",
|
|
28
|
+
cursor: "default",
|
|
29
|
+
tooltip: RotateTooltip,
|
|
30
|
+
defaultOptions: {
|
|
31
|
+
radius: 10,
|
|
32
|
+
...options
|
|
33
|
+
},
|
|
34
|
+
onPointerDown(e, api) {
|
|
35
|
+
if (selectedNodeId) {
|
|
36
|
+
const node2 = api.getNode(selectedNodeId);
|
|
37
|
+
if (node2) {
|
|
38
|
+
const handle = hitHandle(node2, e, { handles: ["rotate"] });
|
|
39
|
+
if (handle === "rotate") {
|
|
40
|
+
const b = getNodeBounds(node2);
|
|
41
|
+
dragging = true;
|
|
42
|
+
accumulatedRotation = node2.rotation ?? 0;
|
|
43
|
+
prevAngle = Math.atan2(
|
|
44
|
+
e.y - (b.y + b.height / 2),
|
|
45
|
+
e.x - (b.x + b.width / 2)
|
|
46
|
+
);
|
|
47
|
+
api.setCursor("none");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
dragging = false;
|
|
53
|
+
prevAngle = 0;
|
|
54
|
+
const node = findTopNode(e, api, api.options.radius);
|
|
55
|
+
selectedNodeId = node ? node.id : null;
|
|
56
|
+
api.requestRender();
|
|
57
|
+
},
|
|
58
|
+
onPointerMove(e, api) {
|
|
59
|
+
if (dragging && selectedNodeId && e.buttons !== 0) {
|
|
60
|
+
const node = api.getNode(selectedNodeId);
|
|
61
|
+
if (!node) return;
|
|
62
|
+
const b = getNodeBounds(node);
|
|
63
|
+
const angle = Math.atan2(
|
|
64
|
+
e.y - (b.y + b.height / 2),
|
|
65
|
+
e.x - (b.x + b.width / 2)
|
|
66
|
+
);
|
|
67
|
+
accumulatedRotation += normalizeAngleDelta(angle - prevAngle);
|
|
68
|
+
prevAngle = angle;
|
|
69
|
+
api.updateNode(selectedNodeId, { rotation: accumulatedRotation });
|
|
70
|
+
api.setCursor("none");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (selectedNodeId) {
|
|
74
|
+
const node = api.getNode(selectedNodeId);
|
|
75
|
+
if (node) {
|
|
76
|
+
const handle = hitHandle(node, e, { handles: ["rotate"] });
|
|
77
|
+
api.setCursor(handle ? cursorForHandle(handle, node.rotation) : null);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
api.setCursor(null);
|
|
82
|
+
},
|
|
83
|
+
onPointerUp(_e, api) {
|
|
84
|
+
dragging = false;
|
|
85
|
+
api.setCursor(null);
|
|
86
|
+
},
|
|
87
|
+
onDeactivate(api) {
|
|
88
|
+
dragging = false;
|
|
89
|
+
api.setCursor(null);
|
|
90
|
+
clearSelection(api);
|
|
91
|
+
},
|
|
92
|
+
onKeyDown(e, api) {
|
|
93
|
+
if (e.key === "Escape" && selectedNodeId) {
|
|
94
|
+
clearSelection(api);
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
renderOverlay(ctx, api) {
|
|
99
|
+
if (!selectedNodeId) return;
|
|
100
|
+
const node = api.getNode(selectedNodeId);
|
|
101
|
+
if (!node) {
|
|
102
|
+
selectedNodeId = null;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
renderHighlightRect(ctx, node);
|
|
106
|
+
renderRotationHandle(ctx, node);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TextToolOptions extends Record<string, unknown> {
|
|
2
|
+
fontSize: number;
|
|
3
|
+
fontFamily: string;
|
|
4
|
+
color: string;
|
|
5
|
+
weight: "normal" | "bold" | number;
|
|
6
|
+
}
|
|
7
|
+
export interface TextNodeData {
|
|
8
|
+
text: string;
|
|
9
|
+
fontSize: number;
|
|
10
|
+
fontFamily: string;
|
|
11
|
+
color: string;
|
|
12
|
+
weight: TextToolOptions["weight"];
|
|
13
|
+
}
|
|
14
|
+
export declare function textTool(options?: Partial<TextToolOptions>): import("../types.js").CanvasTool<TextNodeData, TextToolOptions>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { defineCanvasTool } from "../types.js";
|
|
2
|
+
import TextTooltip from "./tooltips/Text.vue";
|
|
3
|
+
export function textTool(options = {}) {
|
|
4
|
+
return defineCanvasTool({
|
|
5
|
+
id: "text",
|
|
6
|
+
label: "Text",
|
|
7
|
+
icon: "text",
|
|
8
|
+
cursor: "text",
|
|
9
|
+
tooltip: TextTooltip,
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
fontSize: 24,
|
|
12
|
+
fontFamily: "system-ui, sans-serif",
|
|
13
|
+
color: "#111111",
|
|
14
|
+
weight: "normal",
|
|
15
|
+
...options
|
|
16
|
+
},
|
|
17
|
+
onPointerDown(e, api) {
|
|
18
|
+
const stage = api.stageEl();
|
|
19
|
+
if (!stage) return;
|
|
20
|
+
if (e.originalEvent.target?.closest?.(
|
|
21
|
+
".canvas-text-editor"
|
|
22
|
+
)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const input = document.createElement("input");
|
|
26
|
+
input.type = "text";
|
|
27
|
+
input.className = "canvas-text-editor";
|
|
28
|
+
input.style.position = "absolute";
|
|
29
|
+
input.style.left = `${e.x}px`;
|
|
30
|
+
input.style.top = `${e.y}px`;
|
|
31
|
+
input.style.transform = "translateY(-50%)";
|
|
32
|
+
input.style.font = `${api.options.weight} ${api.options.fontSize}px ${api.options.fontFamily}`;
|
|
33
|
+
input.style.color = api.options.color;
|
|
34
|
+
input.style.background = "transparent";
|
|
35
|
+
input.style.border = "1px dashed currentColor";
|
|
36
|
+
input.style.outline = "none";
|
|
37
|
+
input.style.padding = "0 2px";
|
|
38
|
+
input.style.minWidth = "1ch";
|
|
39
|
+
input.style.zIndex = "10";
|
|
40
|
+
const snapshot = { ...api.options };
|
|
41
|
+
stage.appendChild(input);
|
|
42
|
+
requestAnimationFrame(() => input.focus());
|
|
43
|
+
let committed = false;
|
|
44
|
+
const commit = () => {
|
|
45
|
+
if (committed) return;
|
|
46
|
+
committed = true;
|
|
47
|
+
const text = input.value;
|
|
48
|
+
input.remove();
|
|
49
|
+
if (!text.trim()) return;
|
|
50
|
+
api.addNode({
|
|
51
|
+
type: "text",
|
|
52
|
+
x: e.x,
|
|
53
|
+
y: e.y,
|
|
54
|
+
data: {
|
|
55
|
+
text,
|
|
56
|
+
fontSize: snapshot.fontSize,
|
|
57
|
+
fontFamily: snapshot.fontFamily,
|
|
58
|
+
color: snapshot.color,
|
|
59
|
+
weight: snapshot.weight
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
api.requestRender();
|
|
63
|
+
};
|
|
64
|
+
const cancel = () => {
|
|
65
|
+
if (committed) return;
|
|
66
|
+
committed = true;
|
|
67
|
+
input.remove();
|
|
68
|
+
};
|
|
69
|
+
input.addEventListener("blur", commit);
|
|
70
|
+
input.addEventListener("keydown", (ev) => {
|
|
71
|
+
if (ev.key === "Enter") {
|
|
72
|
+
ev.preventDefault();
|
|
73
|
+
input.blur();
|
|
74
|
+
} else if (ev.key === "Escape") {
|
|
75
|
+
ev.preventDefault();
|
|
76
|
+
input.removeEventListener("blur", commit);
|
|
77
|
+
cancel();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
hitTest(node, point, radius) {
|
|
82
|
+
const { text, fontSize } = node.data;
|
|
83
|
+
if (!text) return false;
|
|
84
|
+
const estimatedWidth = text.length * fontSize * 0.6;
|
|
85
|
+
const halfHeight = fontSize / 2;
|
|
86
|
+
return point.x + radius >= node.x && point.x - radius <= node.x + estimatedWidth && point.y + radius >= node.y - halfHeight && point.y - radius <= node.y + halfHeight;
|
|
87
|
+
},
|
|
88
|
+
render(ctx, node) {
|
|
89
|
+
const { text, fontSize, fontFamily, color, weight } = node.data;
|
|
90
|
+
if (!text) return;
|
|
91
|
+
ctx.save();
|
|
92
|
+
ctx.font = `${weight} ${fontSize}px ${fontFamily}`;
|
|
93
|
+
ctx.fillStyle = color;
|
|
94
|
+
ctx.textBaseline = "middle";
|
|
95
|
+
ctx.fillText(text, node.x, node.y);
|
|
96
|
+
ctx.restore();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tool-tip">
|
|
3
|
+
<orio-view-text type="title" size="medium">Clear</orio-view-text>
|
|
4
|
+
<orio-view-text type="subtitle" size="small">
|
|
5
|
+
Remove all nodes from the canvas.
|
|
6
|
+
</orio-view-text>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<style scoped>
|
|
11
|
+
.tool-tip{--view-text-color:#fff;display:flex;flex-direction:column;gap:.25rem;max-width:200px;white-space:normal}
|
|
12
|
+
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|