@edv4h/usketch-plugin-shape-image 3.0.6 → 3.2.0
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/external-content-handler.d.ts +18 -1
- package/dist/external-content-handler.d.ts.map +1 -1
- package/dist/external-content-handler.js +103 -21
- package/dist/external-content-handler.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +38 -15
- package/dist/plugin.js.map +1 -1
- package/dist/svg-utils.d.ts +44 -0
- package/dist/svg-utils.d.ts.map +1 -0
- package/dist/svg-utils.js +119 -0
- package/dist/svg-utils.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { AssetStore } from "@edv4h/usketch-plugin-asset-store";
|
|
1
2
|
import type { ExternalContentHandler, ExternalContentOf } from "@edv4h/usketch-shared";
|
|
3
|
+
/** Resolve the shared asset store lazily (undefined if the plugin isn't wired). */
|
|
4
|
+
export type GetAssetStore = () => AssetStore | undefined;
|
|
2
5
|
export interface ImageFileHandlerOptions {
|
|
3
6
|
/**
|
|
4
7
|
* Max accepted size per file. Default 4 (MB). Larger files are rejected and
|
|
@@ -25,6 +28,20 @@ export interface ImageFileHandlerOptions {
|
|
|
25
28
|
* point — handlers decide their own placement strategy. A future canvas-engine
|
|
26
29
|
* read API may expose the last pointer position for handlers that want it.
|
|
27
30
|
*/
|
|
28
|
-
export declare function createImageFileHandler(options?: ImageFileHandlerOptions): ExternalContentHandler<"file">;
|
|
31
|
+
export declare function createImageFileHandler(options?: ImageFileHandlerOptions, getAssets?: GetAssetStore): ExternalContentHandler<"file">;
|
|
32
|
+
export interface ImageUrlHandlerOptions {
|
|
33
|
+
/** Larger than the embed generic handler (order 0) so `.svg` URLs win. Default 5. */
|
|
34
|
+
order?: number;
|
|
35
|
+
/** Default rendered size (world units) for a URL image (no intrinsic size fetch). Default 200. */
|
|
36
|
+
size?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* `kind:"url"` handler: a dropped/pasted `.svg` URL becomes an image shape that
|
|
40
|
+
* renders it via `<img src>` (script-safe by the `<img>` non-scripting context;
|
|
41
|
+
* a remote SVG can't be sanitized locally). Registered above the embed generic
|
|
42
|
+
* URL handler (order 0) so `.svg` links become images, not a generic iframe.
|
|
43
|
+
* Only `.svg` URLs match — other links fall through to embed as before.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createImageUrlHandler(options?: ImageUrlHandlerOptions): ExternalContentHandler<"url">;
|
|
29
46
|
export type ImageFileContent = ExternalContentOf<"file">;
|
|
30
47
|
//# sourceMappingURL=external-content-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-content-handler.d.ts","sourceRoot":"","sources":["../src/external-content-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,sBAAsB,EAEtB,iBAAiB,EACjB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"external-content-handler.d.ts","sourceRoot":"","sources":["../src/external-content-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,KAAK,EACX,sBAAsB,EAEtB,iBAAiB,EACjB,MAAM,uBAAuB,CAAC;AAa/B,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,MAAM,UAAU,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACrC,OAAO,GAAE,uBAA4B,EACrC,SAAS,CAAC,EAAE,aAAa,GACvB,sBAAsB,CAAC,MAAM,CAAC,CA8BhC;AAuID,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kGAAkG;IAClG,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,GAAE,sBAA2B,GAClC,sBAAsB,CAAC,KAAK,CAAC,CA6B/B;AAGD,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generateId } from "@edv4h/usketch-shared";
|
|
2
2
|
import { fileToBase64, getImageDimensions, resizeImage, validateImage } from "./image-utils.js";
|
|
3
|
+
import { isHttpUrl, isSvgFile, isSvgUrl, readFileAsText, sanitizeSvg, svgIntrinsicSize, svgToDataUri, } from "./svg-utils.js";
|
|
3
4
|
/**
|
|
4
5
|
* Default external-content handler that turns a drop / paste of image files
|
|
5
6
|
* into image shapes on the canvas. Registered at `order: 0` (lowest) so
|
|
@@ -10,7 +11,7 @@ import { fileToBase64, getImageDimensions, resizeImage, validateImage } from "./
|
|
|
10
11
|
* point — handlers decide their own placement strategy. A future canvas-engine
|
|
11
12
|
* read API may expose the last pointer position for handlers that want it.
|
|
12
13
|
*/
|
|
13
|
-
export function createImageFileHandler(options = {}) {
|
|
14
|
+
export function createImageFileHandler(options = {}, getAssets) {
|
|
14
15
|
const { maxSizeMB = 4, maxDimension = 2048, order = 0, maxRenderedSize = 400, gap = 20, } = options;
|
|
15
16
|
return {
|
|
16
17
|
id: "usketch-plugin-shape-image:image-file",
|
|
@@ -26,6 +27,7 @@ export function createImageFileHandler(options = {}) {
|
|
|
26
27
|
maxSizeMB,
|
|
27
28
|
maxDimension,
|
|
28
29
|
maxRenderedSize,
|
|
30
|
+
assets: getAssets?.(),
|
|
29
31
|
});
|
|
30
32
|
offsetX += maxRenderedSize + gap;
|
|
31
33
|
}
|
|
@@ -55,31 +57,73 @@ async function placeImageShape(file, ctx, opts) {
|
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
59
|
let dataUrl;
|
|
58
|
-
try {
|
|
59
|
-
dataUrl = await fileToBase64(file);
|
|
60
|
-
dataUrl = await resizeImage(dataUrl, opts.maxDimension);
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
ctx.events.emit("ai:status", {
|
|
64
|
-
status: "error",
|
|
65
|
-
message: err instanceof Error ? err.message : "Failed to process image",
|
|
66
|
-
});
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
60
|
let dimensions;
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
let mimeType = file.type;
|
|
62
|
+
if (isSvgFile(file)) {
|
|
63
|
+
// Keep SVG as vector: sanitize the markup and embed it as an SVG data URI
|
|
64
|
+
// (no rasterization / JPEG re-encode). Size comes from width/height/viewBox.
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFileAsText(file);
|
|
67
|
+
const clean = sanitizeSvg(raw);
|
|
68
|
+
if (!clean) {
|
|
69
|
+
ctx.events.emit("ai:status", { status: "error", message: "Invalid or unsafe SVG" });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
dataUrl = svgToDataUri(clean);
|
|
73
|
+
dimensions = svgIntrinsicSize(clean, opts.maxRenderedSize);
|
|
74
|
+
mimeType = "image/svg+xml";
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
ctx.events.emit("ai:status", {
|
|
78
|
+
status: "error",
|
|
79
|
+
message: err instanceof Error ? err.message : "Failed to read SVG",
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
72
83
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
dataUrl = await fileToBase64(file);
|
|
87
|
+
dataUrl = await resizeImage(dataUrl, opts.maxDimension);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
ctx.events.emit("ai:status", {
|
|
91
|
+
status: "error",
|
|
92
|
+
message: err instanceof Error ? err.message : "Failed to process image",
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
dimensions = await getImageDimensions(dataUrl);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
ctx.events.emit("ai:status", {
|
|
101
|
+
status: "error",
|
|
102
|
+
message: err instanceof Error ? err.message : "Failed to read image",
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
79
106
|
}
|
|
80
107
|
const scale = Math.min(opts.maxRenderedSize / dimensions.width, opts.maxRenderedSize / dimensions.height, 1);
|
|
81
108
|
const w = Math.round(dimensions.width * scale);
|
|
82
109
|
const h = Math.round(dimensions.height * scale);
|
|
110
|
+
// Prefer the content-addressed asset store: the shape holds only an assetId,
|
|
111
|
+
// so duplicating it reuses the same asset (no base64 duplication, synced once).
|
|
112
|
+
// Falls back to an inline data URL when no asset store is wired.
|
|
113
|
+
let assetId;
|
|
114
|
+
if (opts.assets) {
|
|
115
|
+
try {
|
|
116
|
+
assetId = await opts.assets.upload("image", dataUrl, {
|
|
117
|
+
w: dimensions.width,
|
|
118
|
+
h: dimensions.height,
|
|
119
|
+
mimeType,
|
|
120
|
+
size: file.size,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
assetId = undefined; // fall back to inline src below
|
|
125
|
+
}
|
|
126
|
+
}
|
|
83
127
|
const id = generateId();
|
|
84
128
|
const shape = {
|
|
85
129
|
id,
|
|
@@ -94,7 +138,8 @@ async function placeImageShape(file, ctx, opts) {
|
|
|
94
138
|
strokeWidth: 1,
|
|
95
139
|
opacity: 1,
|
|
96
140
|
},
|
|
97
|
-
src: dataUrl,
|
|
141
|
+
src: assetId ? "" : dataUrl,
|
|
142
|
+
...(assetId ? { assetId } : {}),
|
|
98
143
|
};
|
|
99
144
|
ctx.commands.execute({
|
|
100
145
|
execute: () => ctx.store.addShape(shape),
|
|
@@ -102,4 +147,41 @@ async function placeImageShape(file, ctx, opts) {
|
|
|
102
147
|
});
|
|
103
148
|
ctx.store.setSelection([id]);
|
|
104
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* `kind:"url"` handler: a dropped/pasted `.svg` URL becomes an image shape that
|
|
152
|
+
* renders it via `<img src>` (script-safe by the `<img>` non-scripting context;
|
|
153
|
+
* a remote SVG can't be sanitized locally). Registered above the embed generic
|
|
154
|
+
* URL handler (order 0) so `.svg` links become images, not a generic iframe.
|
|
155
|
+
* Only `.svg` URLs match — other links fall through to embed as before.
|
|
156
|
+
*/
|
|
157
|
+
export function createImageUrlHandler(options = {}) {
|
|
158
|
+
const { order = 5, size = 200 } = options;
|
|
159
|
+
return {
|
|
160
|
+
id: "usketch-plugin-shape-image:image-url",
|
|
161
|
+
kind: "url",
|
|
162
|
+
order,
|
|
163
|
+
// Only absolute http(s) `.svg` links — drop payloads can carry arbitrary
|
|
164
|
+
// schemes (file:/data:/relative), which must not become an `<img src>`.
|
|
165
|
+
match: (content) => isHttpUrl(content.url) && isSvgUrl(content.url),
|
|
166
|
+
handle: (content, ctx) => {
|
|
167
|
+
const base = viewportCenterToWorld(ctx);
|
|
168
|
+
const id = generateId();
|
|
169
|
+
const shape = {
|
|
170
|
+
id,
|
|
171
|
+
type: "image",
|
|
172
|
+
x: Math.round(base.x - size / 2),
|
|
173
|
+
y: Math.round(base.y - size / 2),
|
|
174
|
+
width: size,
|
|
175
|
+
height: size,
|
|
176
|
+
style: { fill: "#f5f5f5", stroke: "#e0e0e0", strokeWidth: 1, opacity: 1 },
|
|
177
|
+
src: content.url,
|
|
178
|
+
};
|
|
179
|
+
ctx.commands.execute({
|
|
180
|
+
execute: () => ctx.store.addShape(shape),
|
|
181
|
+
undo: () => ctx.store.deleteShape(id),
|
|
182
|
+
});
|
|
183
|
+
ctx.store.setSelection([id]);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
105
187
|
//# sourceMappingURL=external-content-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-content-handler.js","sourceRoot":"","sources":["../src/external-content-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"external-content-handler.js","sourceRoot":"","sources":["../src/external-content-handler.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EACN,SAAS,EACT,SAAS,EACT,QAAQ,EACR,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,GACZ,MAAM,gBAAgB,CAAC;AAsBxB;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CACrC,OAAO,GAA4B,EAAE,EACrC,SAAyB;IAEzB,MAAM,EACL,SAAS,GAAG,CAAC,EACb,YAAY,GAAG,IAAI,EACnB,KAAK,GAAG,CAAC,EACT,eAAe,GAAG,GAAG,EACrB,GAAG,GAAG,EAAE,GACR,GAAG,OAAO,CAAC;IAEZ,OAAO;QACN,EAAE,EAAE,uCAAuC;QAC3C,IAAI,EAAE,MAAM;QACZ,KAAK;QACL,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAClB,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACpF,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;gBAClC,MAAM,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE;oBAChC,UAAU,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE;oBAC9C,SAAS;oBACT,YAAY;oBACZ,eAAe;oBACf,MAAM,EAAE,SAAS,EAAE,EAAE;iBACrB,CAAC,CAAC;gBACH,OAAO,IAAI,eAAe,GAAG,GAAG,CAAC;YAClC,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,GAA8B;IAC5D,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO;QACN,CAAC,EAAE,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI;QACjC,CAAC,EAAE,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI;KACjC,CAAC;AACH,CAAC;AAUD,KAAK,UAAU,eAAe,CAC7B,IAAU,EACV,GAA8B,EAC9B,IAAkB;IAElB,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACvB,4EAA4E;QAC5E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7E,OAAO;IACR,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,UAA6C,CAAC;IAClD,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;IAEzB,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,0EAA0E;QAC1E,6EAA6E;QAC7E,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;gBACpF,OAAO;YACR,CAAC;YACD,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAC9B,UAAU,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YAC3D,QAAQ,GAAG,eAAe,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC5B,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB;aAClE,CAAC,CAAC;YACH,OAAO;QACR,CAAC;IACF,CAAC;SAAM,CAAC;QACP,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC5B,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB;aACvE,CAAC,CAAC;YACH,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,UAAU,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC5B,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;aACpE,CAAC,CAAC;YACH,OAAO;QACR,CAAC;IACF,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACrB,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,KAAK,EACvC,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,MAAM,EACxC,CAAC,CACD,CAAC;IACF,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAEhD,6EAA6E;IAC7E,gFAAgF;IAChF,iEAAiE;IACjE,IAAI,OAA2B,CAAC;IAChC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE;gBACpD,CAAC,EAAE,UAAU,CAAC,KAAK;gBACnB,CAAC,EAAE,UAAU,CAAC,MAAM;gBACpB,QAAQ;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,GAAG,SAAS,CAAC,CAAC,gCAAgC;QACtD,CAAC;IACF,CAAC;IAED,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG;QACb,EAAE;QACF,IAAI,EAAE,OAAO;QACb,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,KAAK,EAAE;YACN,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,CAAC;SACV;QACD,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;QAC3B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/B,CAAC;IAEF,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QACpB,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QACxC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;KACrC,CAAC,CAAC;IACH,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACpC,OAAO,GAA2B,EAAE;IAEpC,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;IAC1C,OAAO;QACN,EAAE,EAAE,sCAAsC;QAC1C,IAAI,EAAE,KAAK;QACX,KAAK;QACL,yEAAyE;QACzE,wEAAwE;QACxE,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;QACnE,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;YACxB,MAAM,IAAI,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG;gBACb,EAAE;gBACF,IAAI,EAAE,OAAO;gBACb,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;gBAChC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;gBAChC,KAAK,EAAE,IAAI;gBACX,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;gBACzE,GAAG,EAAE,OAAO,CAAC,GAAG;aAChB,CAAC;YACF,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACpB,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACxC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;aACrC,CAAC,CAAC;YACH,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9B,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"AACA,OAAO,EAON,KAAK,aAAa,EAElB,MAAM,uBAAuB,CAAC;AA+J/B,wBAAgB,sBAAsB,IAAI,aAAa,CAuCtD"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getAssetStore } from "@edv4h/usketch-plugin-asset-store";
|
|
2
3
|
import { DEFAULT_STYLE, withRotation, } from "@edv4h/usketch-shared";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
5
|
+
import { createImageFileHandler, createImageUrlHandler } from "./external-content-handler.js";
|
|
6
|
+
// Lazily resolves the shared asset store at render/serialize time (these run
|
|
7
|
+
// without a PluginContext). Set in setup, so it's order-independent.
|
|
8
|
+
let getAssets = null;
|
|
9
|
+
/** The usable src for an image shape: resolve its assetId, else the inline src. */
|
|
10
|
+
function resolveSrc(data) {
|
|
11
|
+
if (data.assetId)
|
|
12
|
+
return getAssets?.()?.resolve(data.assetId);
|
|
13
|
+
return data.src || undefined;
|
|
14
|
+
}
|
|
15
|
+
/** Image body — subscribes to the asset store so a late-arriving (remote) asset re-renders. */
|
|
16
|
+
function ImageView({ data }) {
|
|
17
|
+
const assets = getAssets?.();
|
|
18
|
+
const subscribe = useCallback((cb) => assets?.subscribe(cb) ?? (() => { }), [assets]);
|
|
19
|
+
const src = useSyncExternalStore(subscribe, () => resolveSrc(data));
|
|
7
20
|
return (_jsx("div", { style: {
|
|
8
21
|
width: "100%",
|
|
9
22
|
height: "100%",
|
|
@@ -23,6 +36,9 @@ function render(shape) {
|
|
|
23
36
|
pointerEvents: "none",
|
|
24
37
|
} })) : (_jsx("span", { style: { color: "#999", fontSize: 13, fontFamily: "system-ui" }, children: "No image" })) }));
|
|
25
38
|
}
|
|
39
|
+
function render(shape) {
|
|
40
|
+
return _jsx(ImageView, { data: shape });
|
|
41
|
+
}
|
|
26
42
|
function getBounds(data) {
|
|
27
43
|
return { x: data.x, y: data.y, width: data.width, height: data.height };
|
|
28
44
|
}
|
|
@@ -85,18 +101,18 @@ function createDefault(params) {
|
|
|
85
101
|
};
|
|
86
102
|
}
|
|
87
103
|
function serializeForAi(shape) {
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
104
|
+
const src = resolveSrc(shape);
|
|
105
|
+
if (!src)
|
|
90
106
|
return {};
|
|
91
107
|
// Image `src` can be a base64 Data URL that easily blows past LLM token
|
|
92
108
|
// budgets (and may leak embedded image bytes into prompts). Return a small
|
|
93
109
|
// summary instead — the shape's `type === "image"` already tells the AI
|
|
94
110
|
// this is an image; consumers who actually need pixel data should use
|
|
95
111
|
// `serializeForRecognition`.
|
|
96
|
-
const isDataUrl =
|
|
112
|
+
const isDataUrl = src.startsWith("data:");
|
|
97
113
|
return isDataUrl
|
|
98
|
-
? { srcKind: "data", srcLength:
|
|
99
|
-
: { srcKind: "url", srcOrigin: safeOrigin(
|
|
114
|
+
? { srcKind: "data", srcLength: src.length }
|
|
115
|
+
: { srcKind: "url", srcOrigin: safeOrigin(src) };
|
|
100
116
|
}
|
|
101
117
|
function safeOrigin(url) {
|
|
102
118
|
try {
|
|
@@ -107,14 +123,14 @@ function safeOrigin(url) {
|
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
function serializeForRecognition(shape) {
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
126
|
+
const src = resolveSrc(shape);
|
|
127
|
+
if (!src)
|
|
112
128
|
return null;
|
|
113
|
-
return { kind: "image", src
|
|
129
|
+
return { kind: "image", src };
|
|
114
130
|
}
|
|
115
131
|
function debugFields(shape) {
|
|
116
132
|
const data = shape;
|
|
117
|
-
return {
|
|
133
|
+
return { assetId: data.assetId ?? "", src: resolveSrc(data) ?? "" };
|
|
118
134
|
}
|
|
119
135
|
export function createImageShapePlugin() {
|
|
120
136
|
return {
|
|
@@ -133,12 +149,19 @@ export function createImageShapePlugin() {
|
|
|
133
149
|
serializeForRecognition,
|
|
134
150
|
debugFields,
|
|
135
151
|
});
|
|
152
|
+
// Resolve the asset store lazily at render/serialize time (order-independent).
|
|
153
|
+
getAssets = () => getAssetStore(ctx);
|
|
136
154
|
// Self-register the default "image file → image shape" external-content
|
|
137
155
|
// handler at order 0. Apps and third-party plugins can override by
|
|
138
|
-
// registering a higher-`order` `kind: "file"` handler.
|
|
139
|
-
|
|
156
|
+
// registering a higher-`order` `kind: "file"` handler. The handler stores
|
|
157
|
+
// imported images in the asset store (content-addressed) when available.
|
|
158
|
+
const unregisterHandler = ctx.externalContent.register(createImageFileHandler({}, () => getAssetStore(ctx)));
|
|
159
|
+
// `.svg` URL drop/paste → image shape (above embed's generic URL handler).
|
|
160
|
+
const unregisterUrlHandler = ctx.externalContent.register(createImageUrlHandler());
|
|
140
161
|
return () => {
|
|
141
162
|
unregisterHandler();
|
|
163
|
+
unregisterUrlHandler();
|
|
164
|
+
getAssets = null;
|
|
142
165
|
};
|
|
143
166
|
},
|
|
144
167
|
};
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEN,aAAa,EAMb,YAAY,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG helpers for importing SVG "as-is" (vector) into an image shape.
|
|
3
|
+
*
|
|
4
|
+
* Security: the image shape renders via `<img src>`, and SVG loaded through
|
|
5
|
+
* `<img>` runs in a non-scripted, no-external-fetch context per the HTML spec —
|
|
6
|
+
* so embedded `<script>` never executes. On top of that browser guarantee we
|
|
7
|
+
* still sanitize on import (defense-in-depth, and in case the markup is ever
|
|
8
|
+
* rendered inline): strip `<script>`/`<foreignObject>`, `on*` handlers, and
|
|
9
|
+
* `javascript:` hrefs. Remote `.svg` URLs can't be sanitized (not fetched); they
|
|
10
|
+
* rely on the same `<img>` non-scripting guarantee.
|
|
11
|
+
*/
|
|
12
|
+
/** Whether a dropped file is an SVG (by MIME or `.svg` extension). */
|
|
13
|
+
export declare function isSvgFile(file: {
|
|
14
|
+
type: string;
|
|
15
|
+
name: string;
|
|
16
|
+
}): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether a URL is an absolute `http:`/`https:` URL. Drop/paste payloads
|
|
19
|
+
* (`text/uri-list`) can carry arbitrary schemes (`file:`, `data:`, relative
|
|
20
|
+
* paths), so the SVG URL handler must gate on this before using the value as
|
|
21
|
+
* an `<img src>`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isHttpUrl(url: string): boolean;
|
|
24
|
+
/** Whether a URL points at an `.svg` (ignoring query/hash). */
|
|
25
|
+
export declare function isSvgUrl(url: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Sanitize SVG markup: drop `<script>`/`<foreignObject>`, `on*` event-handler
|
|
28
|
+
* attributes, and `javascript:` (x)href values. Returns `null` if the markup
|
|
29
|
+
* can't be parsed as SVG or no DOM is available (caller should refuse to embed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function sanitizeSvg(markup: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Intrinsic size from `width`/`height` (px) or `viewBox`, falling back to a
|
|
34
|
+
* square default. Fixes the fragile `naturalWidth === 0` path for size-less SVGs.
|
|
35
|
+
*/
|
|
36
|
+
export declare function svgIntrinsicSize(markup: string, fallback?: number): {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
};
|
|
40
|
+
/** A `<img>`-safe data URI for sanitized SVG markup (kept as vector, not rasterized). */
|
|
41
|
+
export declare function svgToDataUri(markup: string): string;
|
|
42
|
+
/** Read a File as UTF-8 text. */
|
|
43
|
+
export declare function readFileAsText(file: File): Promise<string>;
|
|
44
|
+
//# sourceMappingURL=svg-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg-utils.d.ts","sourceRoot":"","sources":["../src/svg-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO9C;AAED,+DAA+D;AAC/D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO7C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQzD;AA2BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,SAAM,GACZ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAmBnC;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,iCAAiC;AACjC,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAO1D"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG helpers for importing SVG "as-is" (vector) into an image shape.
|
|
3
|
+
*
|
|
4
|
+
* Security: the image shape renders via `<img src>`, and SVG loaded through
|
|
5
|
+
* `<img>` runs in a non-scripted, no-external-fetch context per the HTML spec —
|
|
6
|
+
* so embedded `<script>` never executes. On top of that browser guarantee we
|
|
7
|
+
* still sanitize on import (defense-in-depth, and in case the markup is ever
|
|
8
|
+
* rendered inline): strip `<script>`/`<foreignObject>`, `on*` handlers, and
|
|
9
|
+
* `javascript:` hrefs. Remote `.svg` URLs can't be sanitized (not fetched); they
|
|
10
|
+
* rely on the same `<img>` non-scripting guarantee.
|
|
11
|
+
*/
|
|
12
|
+
/** Whether a dropped file is an SVG (by MIME or `.svg` extension). */
|
|
13
|
+
export function isSvgFile(file) {
|
|
14
|
+
return file.type === "image/svg+xml" || /\.svg$/i.test(file.name);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether a URL is an absolute `http:`/`https:` URL. Drop/paste payloads
|
|
18
|
+
* (`text/uri-list`) can carry arbitrary schemes (`file:`, `data:`, relative
|
|
19
|
+
* paths), so the SVG URL handler must gate on this before using the value as
|
|
20
|
+
* an `<img src>`.
|
|
21
|
+
*/
|
|
22
|
+
export function isHttpUrl(url) {
|
|
23
|
+
try {
|
|
24
|
+
const u = new URL(url);
|
|
25
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Whether a URL points at an `.svg` (ignoring query/hash). */
|
|
32
|
+
export function isSvgUrl(url) {
|
|
33
|
+
try {
|
|
34
|
+
const u = new URL(url);
|
|
35
|
+
return /\.svg$/i.test(u.pathname);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return /\.svg(?:[?#]|$)/i.test(url);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sanitize SVG markup: drop `<script>`/`<foreignObject>`, `on*` event-handler
|
|
43
|
+
* attributes, and `javascript:` (x)href values. Returns `null` if the markup
|
|
44
|
+
* can't be parsed as SVG or no DOM is available (caller should refuse to embed).
|
|
45
|
+
*/
|
|
46
|
+
export function sanitizeSvg(markup) {
|
|
47
|
+
if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined")
|
|
48
|
+
return null;
|
|
49
|
+
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
|
|
50
|
+
if (doc.getElementsByTagName("parsererror").length > 0)
|
|
51
|
+
return null;
|
|
52
|
+
const svg = doc.documentElement;
|
|
53
|
+
if (!svg || svg.nodeName.toLowerCase() !== "svg")
|
|
54
|
+
return null;
|
|
55
|
+
sanitizeElement(svg);
|
|
56
|
+
return new XMLSerializer().serializeToString(svg);
|
|
57
|
+
}
|
|
58
|
+
function sanitizeElement(el) {
|
|
59
|
+
// Snapshot children first — we remove some during the walk.
|
|
60
|
+
for (const child of Array.from(el.children)) {
|
|
61
|
+
const tag = child.nodeName.toLowerCase();
|
|
62
|
+
if (tag === "script" || tag === "foreignobject") {
|
|
63
|
+
child.remove();
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
sanitizeElement(child);
|
|
67
|
+
}
|
|
68
|
+
for (const attr of Array.from(el.attributes)) {
|
|
69
|
+
const name = attr.name.toLowerCase();
|
|
70
|
+
if (name.startsWith("on")) {
|
|
71
|
+
el.removeAttribute(attr.name);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if ((name === "href" || name === "xlink:href" || name === "src") &&
|
|
75
|
+
/^\s*javascript:/i.test(attr.value)) {
|
|
76
|
+
el.removeAttribute(attr.name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Intrinsic size from `width`/`height` (px) or `viewBox`, falling back to a
|
|
82
|
+
* square default. Fixes the fragile `naturalWidth === 0` path for size-less SVGs.
|
|
83
|
+
*/
|
|
84
|
+
export function svgIntrinsicSize(markup, fallback = 200) {
|
|
85
|
+
if (typeof DOMParser !== "undefined") {
|
|
86
|
+
try {
|
|
87
|
+
const svg = new DOMParser().parseFromString(markup, "image/svg+xml").documentElement;
|
|
88
|
+
const w = Number.parseFloat(svg.getAttribute("width") ?? "");
|
|
89
|
+
const h = Number.parseFloat(svg.getAttribute("height") ?? "");
|
|
90
|
+
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
91
|
+
return { width: w, height: h };
|
|
92
|
+
}
|
|
93
|
+
const vb = svg.getAttribute("viewBox");
|
|
94
|
+
if (vb) {
|
|
95
|
+
const p = vb.split(/[\s,]+/).map(Number);
|
|
96
|
+
if (p.length === 4 && p[2] > 0 && p[3] > 0)
|
|
97
|
+
return { width: p[2], height: p[3] };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// fall through to default
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { width: fallback, height: fallback };
|
|
105
|
+
}
|
|
106
|
+
/** A `<img>`-safe data URI for sanitized SVG markup (kept as vector, not rasterized). */
|
|
107
|
+
export function svgToDataUri(markup) {
|
|
108
|
+
return `data:image/svg+xml,${encodeURIComponent(markup)}`;
|
|
109
|
+
}
|
|
110
|
+
/** Read a File as UTF-8 text. */
|
|
111
|
+
export function readFileAsText(file) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const reader = new FileReader();
|
|
114
|
+
reader.onload = () => resolve(reader.result);
|
|
115
|
+
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
116
|
+
reader.readAsText(file);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=svg-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg-utils.js","sourceRoot":"","sources":["../src/svg-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { ShapeData } from "@edv4h/usketch-shared";
|
|
2
2
|
/** Image shape extension: intrinsic data for the `image` shape. */
|
|
3
3
|
export interface ImageShapeData extends ShapeData {
|
|
4
|
+
/**
|
|
5
|
+
* Inline source (data URL or external URL). Kept for backward compatibility
|
|
6
|
+
* and external links. New imports leave this empty and use {@link assetId}.
|
|
7
|
+
*/
|
|
4
8
|
src: string;
|
|
9
|
+
/**
|
|
10
|
+
* Content-addressed asset id (resolved via the asset store). Preferred over
|
|
11
|
+
* `src`: duplicating the shape reuses the same asset (no data duplication).
|
|
12
|
+
*/
|
|
13
|
+
assetId?: string;
|
|
5
14
|
}
|
|
6
15
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,mEAAmE;AACnE,MAAM,WAAW,cAAe,SAAQ,SAAS;IAChD,GAAG,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,mEAAmE;AACnE,MAAM,WAAW,cAAe,SAAQ,SAAS;IAChD;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edv4h/usketch-plugin-shape-image",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@edv4h/usketch-
|
|
15
|
+
"@edv4h/usketch-plugin-asset-store": "0.1.1",
|
|
16
|
+
"@edv4h/usketch-shared": "4.6.0"
|
|
16
17
|
},
|
|
17
18
|
"peerDependencies": {
|
|
18
19
|
"react": ">=19"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@types/react": "19.2.17",
|
|
23
|
+
"jsdom": "29.1.1",
|
|
22
24
|
"typescript": "7.0.2",
|
|
23
25
|
"vitest": "4.1.10"
|
|
24
26
|
},
|