orio-ui 1.23.2 → 1.23.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.
- package/dist/module.json +1 -1
- package/dist/runtime/canvas.d.ts +1 -1
- package/dist/runtime/canvas.js +4 -1
- package/dist/runtime/components/Canvas/context.d.ts +9 -0
- package/dist/runtime/components/Canvas/index.d.vue.ts +3 -0
- package/dist/runtime/components/Canvas/index.vue +19 -2
- package/dist/runtime/components/Canvas/index.vue.d.ts +3 -0
- package/dist/runtime/components/Canvas/tools/exportTool.d.ts +53 -6
- package/dist/runtime/components/Canvas/tools/exportTool.js +79 -32
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/runtime/canvas.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export { rotateTool, type RotateToolOptions, } from "./components/Canvas/tools/r
|
|
|
18
18
|
export { resizeTool, type ResizeToolOptions, } from "./components/Canvas/tools/resizeTool.js";
|
|
19
19
|
export { transformTool, type TransformToolOptions, } from "./components/Canvas/tools/transformTool.js";
|
|
20
20
|
export { imageTool, type ImageToolOptions, type ImageNodeData, } from "./components/Canvas/tools/imageTool.js";
|
|
21
|
-
export { exportTool, type ExportToolOptions, type ExportFormat, } from "./components/Canvas/tools/exportTool.js";
|
|
21
|
+
export { exportTool, performExport, renderCanvasSnapshot, DEFAULT_EXPORT_OPTIONS, type ExportToolOptions, type ExportOptions, type ExportResult, type ExportFormat, type CanvasSnapshot, } from "./components/Canvas/tools/exportTool.js";
|
package/dist/runtime/canvas.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComputedRef, type InjectionKey, type Ref } from "vue";
|
|
2
2
|
import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
|
|
3
|
+
import type { ExportOptions, ExportResult } from "./tools/exportTool.js";
|
|
3
4
|
export interface CanvasContext {
|
|
4
5
|
tools: Ref<CanvasTool[]>;
|
|
5
6
|
activeToolId: Ref<string | null>;
|
|
@@ -33,6 +34,14 @@ export interface CanvasContext {
|
|
|
33
34
|
endAction: () => void;
|
|
34
35
|
/** Keyboard handler for undo/redo shortcuts. Attach to the stage element. */
|
|
35
36
|
onKeyDown: (e: KeyboardEvent) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Export the current canvas as an image. Overrides merge over the registered
|
|
39
|
+
* `exportTool` options (if any) and the built-in defaults. Without
|
|
40
|
+
* `onExport`/`download` overrides the call triggers a browser download for
|
|
41
|
+
* backwards compatibility; pass `{ download: false }` or supply `onExport`
|
|
42
|
+
* to consume the result instead (e.g. push to a cart, upload to a backend).
|
|
43
|
+
*/
|
|
44
|
+
exportCanvas: (overrides?: ExportOptions) => Promise<ExportResult>;
|
|
36
45
|
}
|
|
37
46
|
export declare const CANVAS_CONTEXT: InjectionKey<CanvasContext>;
|
|
38
47
|
export declare function useCanvasContext(): CanvasContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
|
|
2
|
+
import { type ExportOptions, type ExportResult } from "./tools/exportTool.js";
|
|
2
3
|
export interface CanvasProps {
|
|
3
4
|
/**
|
|
4
5
|
* Unique name for this canvas instance. Required so detached toolbars and
|
|
@@ -31,6 +32,7 @@ export interface CanvasProps {
|
|
|
31
32
|
type __VLS_Props = CanvasProps;
|
|
32
33
|
declare function getToolOptions<T extends Record<string, unknown>>(id: string): T;
|
|
33
34
|
declare function setActiveTool(id: string | null): void;
|
|
35
|
+
declare function exportCanvas(overrides?: ExportOptions): Promise<ExportResult>;
|
|
34
36
|
type __VLS_ModelProps = {
|
|
35
37
|
"nodes"?: CanvasNode[];
|
|
36
38
|
};
|
|
@@ -54,6 +56,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
54
56
|
redo: () => void;
|
|
55
57
|
canUndo: import("vue").ComputedRef<boolean>;
|
|
56
58
|
canRedo: import("vue").ComputedRef<boolean>;
|
|
59
|
+
exportCanvas: typeof exportCanvas;
|
|
57
60
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
58
61
|
"update:nodes": (value: CanvasNode<unknown>[]) => any;
|
|
59
62
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
@@ -15,6 +15,9 @@ import { useCanvasNodes } from "./composables/useCanvasNodes";
|
|
|
15
15
|
import { useCanvasSetup } from "./composables/useCanvasSetup";
|
|
16
16
|
import Stage from "./components/Stage.vue";
|
|
17
17
|
import Toolbar from "./components/Toolbar.vue";
|
|
18
|
+
import {
|
|
19
|
+
performExport
|
|
20
|
+
} from "./tools/exportTool";
|
|
18
21
|
const props = defineProps({
|
|
19
22
|
name: { type: String, required: true },
|
|
20
23
|
tools: { type: Array, required: false, default: () => [] },
|
|
@@ -122,6 +125,18 @@ function handleKeyDown(e) {
|
|
|
122
125
|
}
|
|
123
126
|
onKeyDown(e);
|
|
124
127
|
}
|
|
128
|
+
function exportCanvas(overrides = {}) {
|
|
129
|
+
const toolOpts = getToolOptions("export");
|
|
130
|
+
return performExport(
|
|
131
|
+
{
|
|
132
|
+
nodes: nodes.value,
|
|
133
|
+
tools: props.tools,
|
|
134
|
+
width: props.width,
|
|
135
|
+
height: props.height
|
|
136
|
+
},
|
|
137
|
+
{ ...toolOpts, ...overrides }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
125
140
|
const context = {
|
|
126
141
|
tools: toolsRef,
|
|
127
142
|
activeToolId,
|
|
@@ -146,7 +161,8 @@ const context = {
|
|
|
146
161
|
endAction,
|
|
147
162
|
onKeyDown: handleKeyDown,
|
|
148
163
|
cursorOverride,
|
|
149
|
-
setCursor
|
|
164
|
+
setCursor,
|
|
165
|
+
exportCanvas
|
|
150
166
|
};
|
|
151
167
|
provide(CANVAS_CONTEXT, context);
|
|
152
168
|
let registeredName = null;
|
|
@@ -190,7 +206,8 @@ defineExpose({
|
|
|
190
206
|
undo,
|
|
191
207
|
redo,
|
|
192
208
|
canUndo,
|
|
193
|
-
canRedo
|
|
209
|
+
canRedo,
|
|
210
|
+
exportCanvas
|
|
194
211
|
});
|
|
195
212
|
</script>
|
|
196
213
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CanvasNode, CanvasTool, CanvasToolApi } from "./types.js";
|
|
2
|
+
import { type ExportOptions, type ExportResult } from "./tools/exportTool.js";
|
|
2
3
|
export interface CanvasProps {
|
|
3
4
|
/**
|
|
4
5
|
* Unique name for this canvas instance. Required so detached toolbars and
|
|
@@ -31,6 +32,7 @@ export interface CanvasProps {
|
|
|
31
32
|
type __VLS_Props = CanvasProps;
|
|
32
33
|
declare function getToolOptions<T extends Record<string, unknown>>(id: string): T;
|
|
33
34
|
declare function setActiveTool(id: string | null): void;
|
|
35
|
+
declare function exportCanvas(overrides?: ExportOptions): Promise<ExportResult>;
|
|
34
36
|
type __VLS_ModelProps = {
|
|
35
37
|
"nodes"?: CanvasNode[];
|
|
36
38
|
};
|
|
@@ -54,6 +56,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
54
56
|
redo: () => void;
|
|
55
57
|
canUndo: import("vue").ComputedRef<boolean>;
|
|
56
58
|
canRedo: import("vue").ComputedRef<boolean>;
|
|
59
|
+
exportCanvas: typeof exportCanvas;
|
|
57
60
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
58
61
|
"update:nodes": (value: CanvasNode<unknown>[]) => any;
|
|
59
62
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
@@ -1,18 +1,65 @@
|
|
|
1
|
-
import type { CanvasTool } from "../types.js";
|
|
1
|
+
import type { CanvasNode, CanvasTool } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Image formats Canvas2D's `toBlob`/`toDataURL` natively supports across
|
|
4
4
|
* modern browsers. Anything else (e.g. AVIF, TIFF) is encoder-dependent and
|
|
5
5
|
* not guaranteed to round-trip.
|
|
6
6
|
*/
|
|
7
7
|
export type ExportFormat = "png" | "jpeg" | "webp";
|
|
8
|
-
export interface
|
|
9
|
-
|
|
8
|
+
export interface ExportResult {
|
|
9
|
+
blob: Blob;
|
|
10
|
+
dataURL: string;
|
|
11
|
+
/** Same bytes as `blob`, wrapped as a `File` with the configured filename. */
|
|
12
|
+
file: File;
|
|
10
13
|
format: ExportFormat;
|
|
14
|
+
/** Output pixel width (already multiplied by `scale`). */
|
|
15
|
+
width: number;
|
|
16
|
+
/** Output pixel height (already multiplied by `scale`). */
|
|
17
|
+
height: number;
|
|
18
|
+
filename: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ExportOptions {
|
|
21
|
+
/** Output format. Defaults to PNG. */
|
|
22
|
+
format?: ExportFormat;
|
|
11
23
|
/** Quality for lossy formats (jpeg, webp), 0–1. Ignored for PNG. */
|
|
12
|
-
quality
|
|
24
|
+
quality?: number;
|
|
13
25
|
/** File name without extension. */
|
|
14
|
-
filename
|
|
26
|
+
filename?: string;
|
|
15
27
|
/** Pixel scale relative to the visible canvas (1 = same size). */
|
|
16
|
-
scale
|
|
28
|
+
scale?: number;
|
|
29
|
+
/**
|
|
30
|
+
* If provided, receives the export result instead of (or alongside) the
|
|
31
|
+
* download. Use to push the image into application state (e.g. a cart) or
|
|
32
|
+
* upload to a backend. Async — the canvas awaits the returned Promise.
|
|
33
|
+
*/
|
|
34
|
+
onExport?: (result: ExportResult) => void | Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Trigger a browser download. Defaults to `true` when `onExport` is not
|
|
37
|
+
* provided, `false` when it is.
|
|
38
|
+
*/
|
|
39
|
+
download?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ExportToolOptions extends Required<Pick<ExportOptions, "format" | "quality" | "filename" | "scale">>, Pick<ExportOptions, "onExport" | "download">, Record<string, unknown> {
|
|
17
42
|
}
|
|
43
|
+
export declare const DEFAULT_EXPORT_OPTIONS: ExportToolOptions;
|
|
44
|
+
export interface CanvasSnapshot {
|
|
45
|
+
nodes: CanvasNode[];
|
|
46
|
+
tools: CanvasTool[];
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render the snapshot to an offscreen canvas and produce a Blob/dataURL/File.
|
|
52
|
+
* Pure: does not download or invoke callbacks.
|
|
53
|
+
*
|
|
54
|
+
* The offscreen canvas is sized exactly to `snapshot.width × snapshot.height`
|
|
55
|
+
* (× `scale`), so any node geometry outside the canvas is naturally clipped —
|
|
56
|
+
* the result captures only what would be visible on the live canvas.
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderCanvasSnapshot(snapshot: CanvasSnapshot, options?: ExportOptions): Promise<ExportResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Render the snapshot, then optionally hand the result to `onExport` and/or
|
|
61
|
+
* trigger a browser download. Resolves with the result so callers can chain
|
|
62
|
+
* additional work (uploads, state updates).
|
|
63
|
+
*/
|
|
64
|
+
export declare function performExport(snapshot: CanvasSnapshot, options?: ExportOptions): Promise<ExportResult>;
|
|
18
65
|
export declare function exportTool(options?: Partial<ExportToolOptions>): CanvasTool<never, ExportToolOptions>;
|
|
@@ -6,11 +6,15 @@ const MIME_TYPES = {
|
|
|
6
6
|
jpeg: "image/jpeg",
|
|
7
7
|
webp: "image/webp"
|
|
8
8
|
};
|
|
9
|
+
export const DEFAULT_EXPORT_OPTIONS = {
|
|
10
|
+
format: "png",
|
|
11
|
+
quality: 0.92,
|
|
12
|
+
filename: "canvas",
|
|
13
|
+
scale: 1
|
|
14
|
+
};
|
|
9
15
|
function renderNodesTo(target, nodes, tools) {
|
|
10
16
|
const toolMap = new Map(tools.map((t) => [t.id, t]));
|
|
11
|
-
const ordered = [...nodes].sort(
|
|
12
|
-
(a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0)
|
|
13
|
-
);
|
|
17
|
+
const ordered = [...nodes].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
14
18
|
for (const node of ordered) {
|
|
15
19
|
const renderFn = toolMap.get(node.type)?.render;
|
|
16
20
|
if (!renderFn) continue;
|
|
@@ -39,6 +43,67 @@ function downloadBlob(blob, filename) {
|
|
|
39
43
|
a.remove();
|
|
40
44
|
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
41
45
|
}
|
|
46
|
+
function resolveFormat(format) {
|
|
47
|
+
return MIME_TYPES[format] ? format : "png";
|
|
48
|
+
}
|
|
49
|
+
function sanitizeFilename(name) {
|
|
50
|
+
return (name ?? "canvas").replace(/[^A-Za-z0-9._-]/g, "_") || "canvas";
|
|
51
|
+
}
|
|
52
|
+
export async function renderCanvasSnapshot(snapshot, options = {}) {
|
|
53
|
+
const format = resolveFormat(options.format);
|
|
54
|
+
const quality = options.quality ?? DEFAULT_EXPORT_OPTIONS.quality;
|
|
55
|
+
const scale = options.scale ?? DEFAULT_EXPORT_OPTIONS.scale;
|
|
56
|
+
if (!Number.isFinite(scale) || scale <= 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[orio-canvas] invalid export option: scale must be a finite number > 0 (got ${scale})`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (format !== "png" && options.quality !== void 0 && (!Number.isFinite(quality) || quality < 0 || quality > 1)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`[orio-canvas] invalid export option: quality must be a finite number in [0, 1] (got ${quality})`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const mime = MIME_TYPES[format];
|
|
67
|
+
const ext = format === "jpeg" ? "jpg" : format;
|
|
68
|
+
const filename = `${sanitizeFilename(options.filename)}.${ext}`;
|
|
69
|
+
const out = document.createElement("canvas");
|
|
70
|
+
const w = Math.max(1, Math.round(snapshot.width * scale));
|
|
71
|
+
const h = Math.max(1, Math.round(snapshot.height * scale));
|
|
72
|
+
out.width = w;
|
|
73
|
+
out.height = h;
|
|
74
|
+
const c = out.getContext("2d");
|
|
75
|
+
if (!c) {
|
|
76
|
+
throw new Error("[orio-canvas] failed to acquire 2D context for export");
|
|
77
|
+
}
|
|
78
|
+
c.scale(scale, scale);
|
|
79
|
+
if (format === "jpeg") {
|
|
80
|
+
c.fillStyle = "#ffffff";
|
|
81
|
+
c.fillRect(0, 0, snapshot.width, snapshot.height);
|
|
82
|
+
}
|
|
83
|
+
renderNodesTo(c, snapshot.nodes, snapshot.tools);
|
|
84
|
+
const encoderQuality = format === "png" ? void 0 : quality;
|
|
85
|
+
const blob = await new Promise((resolve, reject) => {
|
|
86
|
+
out.toBlob(
|
|
87
|
+
(b) => b ? resolve(b) : reject(new Error("[orio-canvas] toBlob returned null")),
|
|
88
|
+
mime,
|
|
89
|
+
encoderQuality
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
const dataURL = out.toDataURL(mime, encoderQuality);
|
|
93
|
+
const file = new File([blob], filename, { type: mime });
|
|
94
|
+
return { blob, dataURL, file, format, width: w, height: h, filename };
|
|
95
|
+
}
|
|
96
|
+
export async function performExport(snapshot, options = {}) {
|
|
97
|
+
const result = await renderCanvasSnapshot(snapshot, options);
|
|
98
|
+
if (options.onExport) {
|
|
99
|
+
await options.onExport(result);
|
|
100
|
+
}
|
|
101
|
+
const shouldDownload = options.download ?? !options.onExport;
|
|
102
|
+
if (shouldDownload) {
|
|
103
|
+
downloadBlob(result.blob, result.filename);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
42
107
|
export function exportTool(options = {}) {
|
|
43
108
|
return defineCanvasTool({
|
|
44
109
|
id: "export",
|
|
@@ -47,43 +112,25 @@ export function exportTool(options = {}) {
|
|
|
47
112
|
kind: "action",
|
|
48
113
|
tooltip: ExportTooltip,
|
|
49
114
|
defaultOptions: {
|
|
50
|
-
|
|
51
|
-
quality: 0.92,
|
|
52
|
-
filename: "canvas",
|
|
53
|
-
scale: 1,
|
|
115
|
+
...DEFAULT_EXPORT_OPTIONS,
|
|
54
116
|
...options
|
|
55
117
|
},
|
|
56
118
|
disabled(api) {
|
|
57
119
|
return api.nodes.value.length === 0;
|
|
58
120
|
},
|
|
59
121
|
action(api) {
|
|
60
|
-
const { quality, scale } = api.options;
|
|
61
122
|
const { width, height } = api.size();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const h = Math.max(1, Math.round(height * scale));
|
|
69
|
-
out.width = w;
|
|
70
|
-
out.height = h;
|
|
71
|
-
const c = out.getContext("2d");
|
|
72
|
-
if (!c) return;
|
|
73
|
-
c.scale(scale, scale);
|
|
74
|
-
if (knownFormat === "jpeg") {
|
|
75
|
-
c.fillStyle = "#ffffff";
|
|
76
|
-
c.fillRect(0, 0, width, height);
|
|
77
|
-
}
|
|
78
|
-
renderNodesTo(c, api.nodes.value, api.getTools());
|
|
79
|
-
out.toBlob(
|
|
80
|
-
(blob) => {
|
|
81
|
-
if (!blob) return;
|
|
82
|
-
downloadBlob(blob, `${sanitized}.${ext}`);
|
|
123
|
+
performExport(
|
|
124
|
+
{
|
|
125
|
+
nodes: api.nodes.value,
|
|
126
|
+
tools: api.getTools(),
|
|
127
|
+
width,
|
|
128
|
+
height
|
|
83
129
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
130
|
+
api.options
|
|
131
|
+
).catch((err) => {
|
|
132
|
+
console.error("[orio-canvas] export failed", err);
|
|
133
|
+
});
|
|
87
134
|
}
|
|
88
135
|
});
|
|
89
136
|
}
|