fabricjs-document-engine 0.3.0 → 0.5.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/README.md +88 -3
- package/dist/assets/asset-pipeline.cjs +1 -0
- package/dist/assets/asset-pipeline.js +1 -1
- package/dist/engine/create-document-engine.cjs +150 -7
- package/dist/engine/create-document-engine.d.cts +40 -0
- package/dist/engine/create-document-engine.d.ts +40 -0
- package/dist/engine/create-document-engine.js +150 -7
- package/dist/engine/errors.cjs +1 -0
- package/dist/engine/errors.d.cts +4 -1
- package/dist/engine/errors.d.ts +4 -1
- package/dist/engine/errors.js +1 -0
- package/dist/export/download-export.cjs +14 -0
- package/dist/export/download-export.d.cts +7 -0
- package/dist/export/download-export.d.ts +7 -0
- package/dist/export/download-export.js +14 -0
- package/dist/export/export-area.cjs +46 -0
- package/dist/export/export-area.js +46 -0
- package/dist/export/export-options.cjs +47 -0
- package/dist/export/export-options.d.cts +20 -0
- package/dist/export/export-options.d.ts +20 -0
- package/dist/export/export-options.js +46 -0
- package/dist/export/preflight-export.cjs +105 -0
- package/dist/export/preflight-export.d.cts +17 -0
- package/dist/export/preflight-export.d.ts +17 -0
- package/dist/export/preflight-export.js +105 -0
- package/dist/export/render-export.cjs +69 -0
- package/dist/export/render-export.js +65 -0
- package/dist/history/apply-state.cjs +1 -1
- package/dist/history/apply-state.js +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +2 -1
- package/dist/recovery/indexed-db-recovery.cjs +80 -0
- package/dist/recovery/indexed-db-recovery.d.cts +8 -0
- package/dist/recovery/indexed-db-recovery.d.ts +8 -0
- package/dist/recovery/indexed-db-recovery.js +80 -0
- package/dist/recovery/recovery-controller.cjs +159 -0
- package/dist/recovery/recovery-controller.d.cts +19 -0
- package/dist/recovery/recovery-controller.d.ts +19 -0
- package/dist/recovery/recovery-controller.js +158 -0
- package/dist/recovery/recovery-store.cjs +19 -0
- package/dist/recovery/recovery-store.d.cts +10 -0
- package/dist/recovery/recovery-store.d.ts +10 -0
- package/dist/recovery/recovery-store.js +19 -0
- package/dist/recovery.cjs +5 -0
- package/dist/recovery.d.cts +4 -0
- package/dist/recovery.d.ts +4 -0
- package/dist/recovery.js +3 -0
- package/package.json +13 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const require_font_check = require("../assets/font-check.cjs");
|
|
2
|
+
const require_image_check = require("../assets/image-check.cjs");
|
|
3
|
+
const require_export_options = require("./export-options.cjs");
|
|
4
|
+
const require_object_ids = require("../fabric/object-ids.cjs");
|
|
5
|
+
const require_walk_objects = require("../fabric/walk-objects.cjs");
|
|
6
|
+
//#region src/export/preflight-export.ts
|
|
7
|
+
function imageElementsOf(object) {
|
|
8
|
+
const elements = [];
|
|
9
|
+
const withElement = object;
|
|
10
|
+
if (typeof withElement.getElement === "function") elements.push(withElement.getElement());
|
|
11
|
+
for (const paint of [object.fill, object.stroke]) {
|
|
12
|
+
const source = paint?.source;
|
|
13
|
+
if (source !== void 0) elements.push(source);
|
|
14
|
+
}
|
|
15
|
+
return elements.filter((element) => typeof HTMLImageElement !== "undefined" && element instanceof HTMLImageElement);
|
|
16
|
+
}
|
|
17
|
+
function collectImageUses(objects) {
|
|
18
|
+
const uses = /* @__PURE__ */ new Map();
|
|
19
|
+
const pending = objects.map((object) => ({
|
|
20
|
+
object,
|
|
21
|
+
ownerId: require_object_ids.readObjectId(object)
|
|
22
|
+
}));
|
|
23
|
+
while (pending.length > 0) {
|
|
24
|
+
const { object, ownerId } = pending.pop();
|
|
25
|
+
const id = require_object_ids.readObjectId(object) ?? ownerId;
|
|
26
|
+
for (const element of imageElementsOf(object)) {
|
|
27
|
+
const url = element.currentSrc || element.src;
|
|
28
|
+
const use = uses.get(url) ?? {
|
|
29
|
+
element,
|
|
30
|
+
objectIds: /* @__PURE__ */ new Set()
|
|
31
|
+
};
|
|
32
|
+
if (id !== void 0) use.objectIds.add(id);
|
|
33
|
+
uses.set(url, use);
|
|
34
|
+
}
|
|
35
|
+
require_walk_objects.childrenOf(object).forEach((child) => pending.push({
|
|
36
|
+
object: child,
|
|
37
|
+
ownerId: id
|
|
38
|
+
}));
|
|
39
|
+
if (object.clipPath) pending.push({
|
|
40
|
+
object: object.clipPath,
|
|
41
|
+
ownerId: id
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return uses;
|
|
45
|
+
}
|
|
46
|
+
function isBroken(element) {
|
|
47
|
+
return element.complete && element.naturalWidth === 0;
|
|
48
|
+
}
|
|
49
|
+
function shortUrl(url) {
|
|
50
|
+
return url.length > 80 ? `${url.slice(0, 77)}...` : url;
|
|
51
|
+
}
|
|
52
|
+
async function preflightExport(canvas, format, fonts, assetOptions) {
|
|
53
|
+
const problems = [];
|
|
54
|
+
const warnings = [];
|
|
55
|
+
if (format === "json") return {
|
|
56
|
+
ok: true,
|
|
57
|
+
problems,
|
|
58
|
+
warnings
|
|
59
|
+
};
|
|
60
|
+
for (const [url, use] of collectImageUses(canvas.getObjects())) {
|
|
61
|
+
const objectIds = [...use.objectIds];
|
|
62
|
+
if (isBroken(use.element)) problems.push({
|
|
63
|
+
code: "MISSING_IMAGE",
|
|
64
|
+
message: `The image ${shortUrl(url)} did not load`,
|
|
65
|
+
url,
|
|
66
|
+
objectIds
|
|
67
|
+
});
|
|
68
|
+
else if (require_export_options.isRasterFormat(format) && require_image_check.isCrossOriginUrl(url) && !use.element.crossOrigin) problems.push({
|
|
69
|
+
code: "CROSS_ORIGIN_IMAGE",
|
|
70
|
+
message: `The image ${shortUrl(url)} comes from another site without crossOrigin, so the browser blocks exporting it. Load it with crossOrigin: 'anonymous' from a server that allows CORS.`,
|
|
71
|
+
url,
|
|
72
|
+
objectIds
|
|
73
|
+
});
|
|
74
|
+
else if (format === "svg" && !require_image_check.isPortableUrl(url)) warnings.push({
|
|
75
|
+
code: "ASSET_NOT_PORTABLE",
|
|
76
|
+
message: `The SVG links to ${shortUrl(url)}, which only exists in this tab`,
|
|
77
|
+
url,
|
|
78
|
+
objectIds
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const fontSet = typeof document === "undefined" ? void 0 : document.fonts;
|
|
82
|
+
if (fontSet) await fontSet.ready;
|
|
83
|
+
for (const font of await require_font_check.findUnavailableFonts(fonts, assetOptions.loadFont)) {
|
|
84
|
+
const message = `The font "${font.family}" (${font.weight}, ${font.style}) is not available`;
|
|
85
|
+
if (assetOptions.requireFonts) problems.push({
|
|
86
|
+
code: "MISSING_FONT",
|
|
87
|
+
message,
|
|
88
|
+
family: font.family,
|
|
89
|
+
objectIds: font.objectIds
|
|
90
|
+
});
|
|
91
|
+
else warnings.push({
|
|
92
|
+
code: "FONT_UNAVAILABLE",
|
|
93
|
+
message: `${message}, text will use a fallback font`,
|
|
94
|
+
family: font.family,
|
|
95
|
+
objectIds: font.objectIds
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: problems.length === 0,
|
|
100
|
+
problems,
|
|
101
|
+
warnings
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
exports.preflightExport = preflightExport;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AssetWarning } from "../assets/asset-pipeline.cjs";
|
|
2
|
+
import { StaticCanvas } from "fabric";
|
|
3
|
+
//#region src/export/preflight-export.d.ts
|
|
4
|
+
export type ExportProblemCode = "MISSING_IMAGE" | "CROSS_ORIGIN_IMAGE" | "MISSING_FONT";
|
|
5
|
+
export interface ExportProblem {
|
|
6
|
+
code: ExportProblemCode;
|
|
7
|
+
message: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
family?: string;
|
|
10
|
+
objectIds: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ExportPreflight {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
problems: ExportProblem[];
|
|
15
|
+
warnings: AssetWarning[];
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AssetWarning } from "../assets/asset-pipeline.js";
|
|
2
|
+
import { StaticCanvas } from "fabric";
|
|
3
|
+
//#region src/export/preflight-export.d.ts
|
|
4
|
+
export type ExportProblemCode = "MISSING_IMAGE" | "CROSS_ORIGIN_IMAGE" | "MISSING_FONT";
|
|
5
|
+
export interface ExportProblem {
|
|
6
|
+
code: ExportProblemCode;
|
|
7
|
+
message: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
family?: string;
|
|
10
|
+
objectIds: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ExportPreflight {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
problems: ExportProblem[];
|
|
15
|
+
warnings: AssetWarning[];
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { findUnavailableFonts } from "../assets/font-check.js";
|
|
2
|
+
import { isCrossOriginUrl, isPortableUrl } from "../assets/image-check.js";
|
|
3
|
+
import { isRasterFormat } from "./export-options.js";
|
|
4
|
+
import { readObjectId } from "../fabric/object-ids.js";
|
|
5
|
+
import { childrenOf } from "../fabric/walk-objects.js";
|
|
6
|
+
//#region src/export/preflight-export.ts
|
|
7
|
+
function imageElementsOf(object) {
|
|
8
|
+
const elements = [];
|
|
9
|
+
const withElement = object;
|
|
10
|
+
if (typeof withElement.getElement === "function") elements.push(withElement.getElement());
|
|
11
|
+
for (const paint of [object.fill, object.stroke]) {
|
|
12
|
+
const source = paint?.source;
|
|
13
|
+
if (source !== void 0) elements.push(source);
|
|
14
|
+
}
|
|
15
|
+
return elements.filter((element) => typeof HTMLImageElement !== "undefined" && element instanceof HTMLImageElement);
|
|
16
|
+
}
|
|
17
|
+
function collectImageUses(objects) {
|
|
18
|
+
const uses = /* @__PURE__ */ new Map();
|
|
19
|
+
const pending = objects.map((object) => ({
|
|
20
|
+
object,
|
|
21
|
+
ownerId: readObjectId(object)
|
|
22
|
+
}));
|
|
23
|
+
while (pending.length > 0) {
|
|
24
|
+
const { object, ownerId } = pending.pop();
|
|
25
|
+
const id = readObjectId(object) ?? ownerId;
|
|
26
|
+
for (const element of imageElementsOf(object)) {
|
|
27
|
+
const url = element.currentSrc || element.src;
|
|
28
|
+
const use = uses.get(url) ?? {
|
|
29
|
+
element,
|
|
30
|
+
objectIds: /* @__PURE__ */ new Set()
|
|
31
|
+
};
|
|
32
|
+
if (id !== void 0) use.objectIds.add(id);
|
|
33
|
+
uses.set(url, use);
|
|
34
|
+
}
|
|
35
|
+
childrenOf(object).forEach((child) => pending.push({
|
|
36
|
+
object: child,
|
|
37
|
+
ownerId: id
|
|
38
|
+
}));
|
|
39
|
+
if (object.clipPath) pending.push({
|
|
40
|
+
object: object.clipPath,
|
|
41
|
+
ownerId: id
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return uses;
|
|
45
|
+
}
|
|
46
|
+
function isBroken(element) {
|
|
47
|
+
return element.complete && element.naturalWidth === 0;
|
|
48
|
+
}
|
|
49
|
+
function shortUrl(url) {
|
|
50
|
+
return url.length > 80 ? `${url.slice(0, 77)}...` : url;
|
|
51
|
+
}
|
|
52
|
+
async function preflightExport(canvas, format, fonts, assetOptions) {
|
|
53
|
+
const problems = [];
|
|
54
|
+
const warnings = [];
|
|
55
|
+
if (format === "json") return {
|
|
56
|
+
ok: true,
|
|
57
|
+
problems,
|
|
58
|
+
warnings
|
|
59
|
+
};
|
|
60
|
+
for (const [url, use] of collectImageUses(canvas.getObjects())) {
|
|
61
|
+
const objectIds = [...use.objectIds];
|
|
62
|
+
if (isBroken(use.element)) problems.push({
|
|
63
|
+
code: "MISSING_IMAGE",
|
|
64
|
+
message: `The image ${shortUrl(url)} did not load`,
|
|
65
|
+
url,
|
|
66
|
+
objectIds
|
|
67
|
+
});
|
|
68
|
+
else if (isRasterFormat(format) && isCrossOriginUrl(url) && !use.element.crossOrigin) problems.push({
|
|
69
|
+
code: "CROSS_ORIGIN_IMAGE",
|
|
70
|
+
message: `The image ${shortUrl(url)} comes from another site without crossOrigin, so the browser blocks exporting it. Load it with crossOrigin: 'anonymous' from a server that allows CORS.`,
|
|
71
|
+
url,
|
|
72
|
+
objectIds
|
|
73
|
+
});
|
|
74
|
+
else if (format === "svg" && !isPortableUrl(url)) warnings.push({
|
|
75
|
+
code: "ASSET_NOT_PORTABLE",
|
|
76
|
+
message: `The SVG links to ${shortUrl(url)}, which only exists in this tab`,
|
|
77
|
+
url,
|
|
78
|
+
objectIds
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const fontSet = typeof document === "undefined" ? void 0 : document.fonts;
|
|
82
|
+
if (fontSet) await fontSet.ready;
|
|
83
|
+
for (const font of await findUnavailableFonts(fonts, assetOptions.loadFont)) {
|
|
84
|
+
const message = `The font "${font.family}" (${font.weight}, ${font.style}) is not available`;
|
|
85
|
+
if (assetOptions.requireFonts) problems.push({
|
|
86
|
+
code: "MISSING_FONT",
|
|
87
|
+
message,
|
|
88
|
+
family: font.family,
|
|
89
|
+
objectIds: font.objectIds
|
|
90
|
+
});
|
|
91
|
+
else warnings.push({
|
|
92
|
+
code: "FONT_UNAVAILABLE",
|
|
93
|
+
message: `${message}, text will use a fallback font`,
|
|
94
|
+
family: font.family,
|
|
95
|
+
objectIds: font.objectIds
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: problems.length === 0,
|
|
100
|
+
problems,
|
|
101
|
+
warnings
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { preflightExport };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/export/render-export.ts
|
|
2
|
+
const identity = [
|
|
3
|
+
1,
|
|
4
|
+
0,
|
|
5
|
+
0,
|
|
6
|
+
1,
|
|
7
|
+
0,
|
|
8
|
+
0
|
|
9
|
+
];
|
|
10
|
+
const mimeTypes = {
|
|
11
|
+
png: "image/png",
|
|
12
|
+
jpeg: "image/jpeg",
|
|
13
|
+
webp: "image/webp",
|
|
14
|
+
svg: "image/svg+xml",
|
|
15
|
+
json: "application/json"
|
|
16
|
+
};
|
|
17
|
+
function chooseBackground(background, format, current) {
|
|
18
|
+
if (background === "transparent") return format === "jpeg" ? "white" : "";
|
|
19
|
+
if (background !== "keep") return background;
|
|
20
|
+
return format === "jpeg" && !(current !== void 0 && current !== null && current !== "") ? "white" : current;
|
|
21
|
+
}
|
|
22
|
+
function withExportView(canvas, background, format, work) {
|
|
23
|
+
const savedTransform = [...canvas.viewportTransform];
|
|
24
|
+
const savedBackground = canvas.backgroundColor;
|
|
25
|
+
canvas.setViewportTransform(identity);
|
|
26
|
+
canvas.backgroundColor = chooseBackground(background, format, savedBackground);
|
|
27
|
+
try {
|
|
28
|
+
return work();
|
|
29
|
+
} finally {
|
|
30
|
+
canvas.backgroundColor = savedBackground;
|
|
31
|
+
canvas.setViewportTransform(savedTransform);
|
|
32
|
+
canvas.requestRenderAll();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function renderRaster(canvas, area, format, scale, quality) {
|
|
36
|
+
return canvas.toDataURL({
|
|
37
|
+
...area,
|
|
38
|
+
format,
|
|
39
|
+
quality,
|
|
40
|
+
multiplier: scale,
|
|
41
|
+
enableRetinaScaling: false
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function renderSvg(canvas, area, scale) {
|
|
45
|
+
return canvas.toSVG({
|
|
46
|
+
viewBox: {
|
|
47
|
+
x: area.left,
|
|
48
|
+
y: area.top,
|
|
49
|
+
width: area.width,
|
|
50
|
+
height: area.height
|
|
51
|
+
},
|
|
52
|
+
width: String(area.width * scale),
|
|
53
|
+
height: String(area.height * scale)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function dataUrlToBlob(dataUrl) {
|
|
57
|
+
const [header, payload = ""] = dataUrl.split(",");
|
|
58
|
+
const mimeType = /data:([^;]+)/.exec(header ?? "")?.[1] ?? "application/octet-stream";
|
|
59
|
+
const binary = atob(payload);
|
|
60
|
+
const bytes = new Uint8Array(binary.length);
|
|
61
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
62
|
+
return new Blob([bytes], { type: mimeType });
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
exports.dataUrlToBlob = dataUrlToBlob;
|
|
66
|
+
exports.mimeTypes = mimeTypes;
|
|
67
|
+
exports.renderRaster = renderRaster;
|
|
68
|
+
exports.renderSvg = renderSvg;
|
|
69
|
+
exports.withExportView = withExportView;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/export/render-export.ts
|
|
2
|
+
const identity = [
|
|
3
|
+
1,
|
|
4
|
+
0,
|
|
5
|
+
0,
|
|
6
|
+
1,
|
|
7
|
+
0,
|
|
8
|
+
0
|
|
9
|
+
];
|
|
10
|
+
const mimeTypes = {
|
|
11
|
+
png: "image/png",
|
|
12
|
+
jpeg: "image/jpeg",
|
|
13
|
+
webp: "image/webp",
|
|
14
|
+
svg: "image/svg+xml",
|
|
15
|
+
json: "application/json"
|
|
16
|
+
};
|
|
17
|
+
function chooseBackground(background, format, current) {
|
|
18
|
+
if (background === "transparent") return format === "jpeg" ? "white" : "";
|
|
19
|
+
if (background !== "keep") return background;
|
|
20
|
+
return format === "jpeg" && !(current !== void 0 && current !== null && current !== "") ? "white" : current;
|
|
21
|
+
}
|
|
22
|
+
function withExportView(canvas, background, format, work) {
|
|
23
|
+
const savedTransform = [...canvas.viewportTransform];
|
|
24
|
+
const savedBackground = canvas.backgroundColor;
|
|
25
|
+
canvas.setViewportTransform(identity);
|
|
26
|
+
canvas.backgroundColor = chooseBackground(background, format, savedBackground);
|
|
27
|
+
try {
|
|
28
|
+
return work();
|
|
29
|
+
} finally {
|
|
30
|
+
canvas.backgroundColor = savedBackground;
|
|
31
|
+
canvas.setViewportTransform(savedTransform);
|
|
32
|
+
canvas.requestRenderAll();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function renderRaster(canvas, area, format, scale, quality) {
|
|
36
|
+
return canvas.toDataURL({
|
|
37
|
+
...area,
|
|
38
|
+
format,
|
|
39
|
+
quality,
|
|
40
|
+
multiplier: scale,
|
|
41
|
+
enableRetinaScaling: false
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function renderSvg(canvas, area, scale) {
|
|
45
|
+
return canvas.toSVG({
|
|
46
|
+
viewBox: {
|
|
47
|
+
x: area.left,
|
|
48
|
+
y: area.top,
|
|
49
|
+
width: area.width,
|
|
50
|
+
height: area.height
|
|
51
|
+
},
|
|
52
|
+
width: String(area.width * scale),
|
|
53
|
+
height: String(area.height * scale)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function dataUrlToBlob(dataUrl) {
|
|
57
|
+
const [header, payload = ""] = dataUrl.split(",");
|
|
58
|
+
const mimeType = /data:([^;]+)/.exec(header ?? "")?.[1] ?? "application/octet-stream";
|
|
59
|
+
const binary = atob(payload);
|
|
60
|
+
const bytes = new Uint8Array(binary.length);
|
|
61
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
62
|
+
return new Blob([bytes], { type: mimeType });
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { dataUrlToBlob, mimeTypes, renderRaster, renderSvg, withExportView };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const require_fabric_adapter = require("../fabric/fabric-adapter.cjs");
|
|
2
1
|
const require_object_ids = require("../fabric/object-ids.cjs");
|
|
2
|
+
const require_fabric_adapter = require("../fabric/fabric-adapter.cjs");
|
|
3
3
|
//#region src/history/apply-state.ts
|
|
4
4
|
function arrangeObjects(current, change, created) {
|
|
5
5
|
const arranged = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createObjects } from "../fabric/fabric-adapter.js";
|
|
2
1
|
import { readObjectId } from "../fabric/object-ids.js";
|
|
2
|
+
import { createObjects } from "../fabric/fabric-adapter.js";
|
|
3
3
|
//#region src/history/apply-state.ts
|
|
4
4
|
function arrangeObjects(current, change, created) {
|
|
5
5
|
const arranged = [];
|
package/dist/index.cjs
CHANGED
|
@@ -5,11 +5,13 @@ const require_validate_document = require("./document/validate-document.cjs");
|
|
|
5
5
|
const require_create_document_engine = require("./engine/create-document-engine.cjs");
|
|
6
6
|
const require_keyboard_shortcuts = require("./history/keyboard-shortcuts.cjs");
|
|
7
7
|
const require_unsaved_changes_warning = require("./save/unsaved-changes-warning.cjs");
|
|
8
|
+
const require_download_export = require("./export/download-export.cjs");
|
|
8
9
|
exports.CURRENT_SCHEMA_VERSION = require_document_format.CURRENT_SCHEMA_VERSION;
|
|
9
10
|
exports.DocumentEngineError = require_errors.DocumentEngineError;
|
|
10
11
|
exports.bindKeyboardShortcuts = require_keyboard_shortcuts.bindKeyboardShortcuts;
|
|
11
12
|
exports.bindUnsavedChangesWarning = require_unsaved_changes_warning.bindUnsavedChangesWarning;
|
|
12
13
|
exports.createConflictError = require_errors.createConflictError;
|
|
13
14
|
exports.createDocumentEngine = require_create_document_engine.createDocumentEngine;
|
|
15
|
+
exports.downloadExport = require_download_export.downloadExport;
|
|
14
16
|
exports.isDocumentEngineError = require_errors.isDocumentEngineError;
|
|
15
17
|
exports.validateDocument = require_validate_document.validateDocument;
|
package/dist/index.d.cts
CHANGED
|
@@ -2,17 +2,22 @@ import { CURRENT_SCHEMA_VERSION, DocumentCanvas, DocumentInfo, FabricDocument, S
|
|
|
2
2
|
import { AssetManifest, FontAsset, ImageAsset } from "./assets/asset-manifest.cjs";
|
|
3
3
|
import { FontLoader } from "./assets/font-check.cjs";
|
|
4
4
|
import { AssetOptions, AssetReport, AssetWarning, AssetWarningCode, UploadRequest } from "./assets/asset-pipeline.cjs";
|
|
5
|
+
import { ExportArea, ExportBackground, ExportFormat, ExportOptions, ExportRect } from "./export/export-options.cjs";
|
|
6
|
+
import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/preflight-export.cjs";
|
|
5
7
|
import { NewDocumentOptions } from "./document/create-document.cjs";
|
|
6
8
|
import { CustomObjectDefinition } from "./fabric/object-registry.cjs";
|
|
7
9
|
import { HistoryOptions, HistoryState } from "./history/create-history.cjs";
|
|
10
|
+
import { RecoveryStore } from "./recovery/recovery-store.cjs";
|
|
11
|
+
import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.cjs";
|
|
8
12
|
import { AutosaveOptions } from "./save/autosave-scheduler.cjs";
|
|
9
13
|
import { RetryOptions } from "./save/retry.cjs";
|
|
10
14
|
import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, DocumentIssue, createConflictError, isDocumentEngineError } from "./engine/errors.cjs";
|
|
11
15
|
import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.cjs";
|
|
12
16
|
import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.cjs";
|
|
13
17
|
import { Unsubscribe } from "./engine/event-emitter.cjs";
|
|
14
|
-
import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
|
|
18
|
+
import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.cjs";
|
|
15
19
|
import { validateDocument } from "./document/validate-document.cjs";
|
|
16
20
|
import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.cjs";
|
|
17
21
|
import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.cjs";
|
|
18
|
-
|
|
22
|
+
import { DownloadableExport, downloadExport } from "./export/download-export.cjs";
|
|
23
|
+
export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,17 +2,22 @@ import { CURRENT_SCHEMA_VERSION, DocumentCanvas, DocumentInfo, FabricDocument, S
|
|
|
2
2
|
import { AssetManifest, FontAsset, ImageAsset } from "./assets/asset-manifest.js";
|
|
3
3
|
import { FontLoader } from "./assets/font-check.js";
|
|
4
4
|
import { AssetOptions, AssetReport, AssetWarning, AssetWarningCode, UploadRequest } from "./assets/asset-pipeline.js";
|
|
5
|
+
import { ExportArea, ExportBackground, ExportFormat, ExportOptions, ExportRect } from "./export/export-options.js";
|
|
6
|
+
import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/preflight-export.js";
|
|
5
7
|
import { NewDocumentOptions } from "./document/create-document.js";
|
|
6
8
|
import { CustomObjectDefinition } from "./fabric/object-registry.js";
|
|
7
9
|
import { HistoryOptions, HistoryState } from "./history/create-history.js";
|
|
10
|
+
import { RecoveryStore } from "./recovery/recovery-store.js";
|
|
11
|
+
import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.js";
|
|
8
12
|
import { AutosaveOptions } from "./save/autosave-scheduler.js";
|
|
9
13
|
import { RetryOptions } from "./save/retry.js";
|
|
10
14
|
import { DocumentEngineError, DocumentEngineErrorDetails, DocumentErrorCode, DocumentIssue, createConflictError, isDocumentEngineError } from "./engine/errors.js";
|
|
11
15
|
import { DocumentStorage, SaveContext, SaveResult } from "./storage/storage-contract.js";
|
|
12
16
|
import { SaveOptions, SaveRetryEvent, SaveState, SaveStatus } from "./save/save-controller.js";
|
|
13
17
|
import { Unsubscribe } from "./engine/event-emitter.js";
|
|
14
|
-
import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
|
|
18
|
+
import { DocumentEngine, DocumentEngineEvents, DocumentEngineOptions, ExportResult, LoadOptions, createDocumentEngine } from "./engine/create-document-engine.js";
|
|
15
19
|
import { validateDocument } from "./document/validate-document.js";
|
|
16
20
|
import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
|
|
17
21
|
import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
|
|
18
|
-
|
|
22
|
+
import { DownloadableExport, downloadExport } from "./export/download-export.js";
|
|
23
|
+
export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,5 @@ import { validateDocument } from "./document/validate-document.js";
|
|
|
4
4
|
import { createDocumentEngine } from "./engine/create-document-engine.js";
|
|
5
5
|
import { bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
|
|
6
6
|
import { bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
|
|
7
|
-
|
|
7
|
+
import { downloadExport } from "./export/download-export.js";
|
|
8
|
+
export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, downloadExport, isDocumentEngineError, validateDocument };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/recovery/indexed-db-recovery.ts
|
|
2
|
+
function requestToPromise(request) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
request.onsuccess = () => resolve(request.result);
|
|
5
|
+
request.onerror = () => reject(request.error);
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function browserStorage() {
|
|
9
|
+
try {
|
|
10
|
+
return globalThis.localStorage;
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createIndexedDbRecovery(options = {}) {
|
|
16
|
+
const databaseName = options.databaseName ?? "fabricjs-document-engine";
|
|
17
|
+
const storeName = options.storeName ?? "recovery";
|
|
18
|
+
let opening;
|
|
19
|
+
let database;
|
|
20
|
+
const emergencyPrefix = `${databaseName}/${storeName}:`;
|
|
21
|
+
const emergencyStorage = browserStorage();
|
|
22
|
+
function readEmergency(key) {
|
|
23
|
+
const json = emergencyStorage?.getItem(`${emergencyPrefix}${key}`);
|
|
24
|
+
return json === null || json === void 0 ? void 0 : JSON.parse(json);
|
|
25
|
+
}
|
|
26
|
+
function emergencyKeys() {
|
|
27
|
+
if (!emergencyStorage) return [];
|
|
28
|
+
return Array.from({ length: emergencyStorage.length }, (_, index) => emergencyStorage.key(index) ?? "").filter((key) => key.startsWith(emergencyPrefix)).map((key) => key.slice(emergencyPrefix.length));
|
|
29
|
+
}
|
|
30
|
+
function openDatabase() {
|
|
31
|
+
opening ?? (opening = new Promise((resolve, reject) => {
|
|
32
|
+
const request = indexedDB.open(databaseName, 1);
|
|
33
|
+
request.onupgradeneeded = () => {
|
|
34
|
+
if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
|
|
35
|
+
};
|
|
36
|
+
request.onsuccess = () => {
|
|
37
|
+
database = request.result;
|
|
38
|
+
resolve(request.result);
|
|
39
|
+
};
|
|
40
|
+
request.onerror = () => {
|
|
41
|
+
opening = void 0;
|
|
42
|
+
reject(request.error);
|
|
43
|
+
};
|
|
44
|
+
}));
|
|
45
|
+
return opening;
|
|
46
|
+
}
|
|
47
|
+
async function run(mode, work) {
|
|
48
|
+
const transaction = (database ?? await openDatabase()).transaction(storeName, mode);
|
|
49
|
+
const result = requestToPromise(work(transaction.objectStore(storeName)));
|
|
50
|
+
await new Promise((resolve, reject) => {
|
|
51
|
+
transaction.oncomplete = () => resolve();
|
|
52
|
+
transaction.onerror = () => reject(transaction.error);
|
|
53
|
+
transaction.onabort = () => reject(transaction.error);
|
|
54
|
+
});
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
get: async (key) => await run("readonly", (store) => store.get(key)) ?? readEmergency(key),
|
|
59
|
+
set: async (key, value) => {
|
|
60
|
+
await run("readwrite", (store) => store.put(value, key));
|
|
61
|
+
},
|
|
62
|
+
setNow(key, value) {
|
|
63
|
+
try {
|
|
64
|
+
emergencyStorage?.setItem(`${emergencyPrefix}${key}`, JSON.stringify(value));
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
delete: async (key) => {
|
|
70
|
+
emergencyStorage?.removeItem(`${emergencyPrefix}${key}`);
|
|
71
|
+
await run("readwrite", (store) => store.delete(key));
|
|
72
|
+
},
|
|
73
|
+
keys: async () => {
|
|
74
|
+
const stored = (await run("readonly", (store) => store.getAllKeys())).map(String);
|
|
75
|
+
return [.../* @__PURE__ */ new Set([...stored, ...emergencyKeys()])];
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
exports.createIndexedDbRecovery = createIndexedDbRecovery;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RecoveryStore } from "./recovery-store.cjs";
|
|
2
|
+
//#region src/recovery/indexed-db-recovery.d.ts
|
|
3
|
+
export interface IndexedDbRecoveryOptions {
|
|
4
|
+
databaseName?: string;
|
|
5
|
+
storeName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createIndexedDbRecovery(options?: IndexedDbRecoveryOptions): RecoveryStore;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RecoveryStore } from "./recovery-store.js";
|
|
2
|
+
//#region src/recovery/indexed-db-recovery.d.ts
|
|
3
|
+
export interface IndexedDbRecoveryOptions {
|
|
4
|
+
databaseName?: string;
|
|
5
|
+
storeName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createIndexedDbRecovery(options?: IndexedDbRecoveryOptions): RecoveryStore;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/recovery/indexed-db-recovery.ts
|
|
2
|
+
function requestToPromise(request) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
request.onsuccess = () => resolve(request.result);
|
|
5
|
+
request.onerror = () => reject(request.error);
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function browserStorage() {
|
|
9
|
+
try {
|
|
10
|
+
return globalThis.localStorage;
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createIndexedDbRecovery(options = {}) {
|
|
16
|
+
const databaseName = options.databaseName ?? "fabricjs-document-engine";
|
|
17
|
+
const storeName = options.storeName ?? "recovery";
|
|
18
|
+
let opening;
|
|
19
|
+
let database;
|
|
20
|
+
const emergencyPrefix = `${databaseName}/${storeName}:`;
|
|
21
|
+
const emergencyStorage = browserStorage();
|
|
22
|
+
function readEmergency(key) {
|
|
23
|
+
const json = emergencyStorage?.getItem(`${emergencyPrefix}${key}`);
|
|
24
|
+
return json === null || json === void 0 ? void 0 : JSON.parse(json);
|
|
25
|
+
}
|
|
26
|
+
function emergencyKeys() {
|
|
27
|
+
if (!emergencyStorage) return [];
|
|
28
|
+
return Array.from({ length: emergencyStorage.length }, (_, index) => emergencyStorage.key(index) ?? "").filter((key) => key.startsWith(emergencyPrefix)).map((key) => key.slice(emergencyPrefix.length));
|
|
29
|
+
}
|
|
30
|
+
function openDatabase() {
|
|
31
|
+
opening ?? (opening = new Promise((resolve, reject) => {
|
|
32
|
+
const request = indexedDB.open(databaseName, 1);
|
|
33
|
+
request.onupgradeneeded = () => {
|
|
34
|
+
if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
|
|
35
|
+
};
|
|
36
|
+
request.onsuccess = () => {
|
|
37
|
+
database = request.result;
|
|
38
|
+
resolve(request.result);
|
|
39
|
+
};
|
|
40
|
+
request.onerror = () => {
|
|
41
|
+
opening = void 0;
|
|
42
|
+
reject(request.error);
|
|
43
|
+
};
|
|
44
|
+
}));
|
|
45
|
+
return opening;
|
|
46
|
+
}
|
|
47
|
+
async function run(mode, work) {
|
|
48
|
+
const transaction = (database ?? await openDatabase()).transaction(storeName, mode);
|
|
49
|
+
const result = requestToPromise(work(transaction.objectStore(storeName)));
|
|
50
|
+
await new Promise((resolve, reject) => {
|
|
51
|
+
transaction.oncomplete = () => resolve();
|
|
52
|
+
transaction.onerror = () => reject(transaction.error);
|
|
53
|
+
transaction.onabort = () => reject(transaction.error);
|
|
54
|
+
});
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
get: async (key) => await run("readonly", (store) => store.get(key)) ?? readEmergency(key),
|
|
59
|
+
set: async (key, value) => {
|
|
60
|
+
await run("readwrite", (store) => store.put(value, key));
|
|
61
|
+
},
|
|
62
|
+
setNow(key, value) {
|
|
63
|
+
try {
|
|
64
|
+
emergencyStorage?.setItem(`${emergencyPrefix}${key}`, JSON.stringify(value));
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
delete: async (key) => {
|
|
70
|
+
emergencyStorage?.removeItem(`${emergencyPrefix}${key}`);
|
|
71
|
+
await run("readwrite", (store) => store.delete(key));
|
|
72
|
+
},
|
|
73
|
+
keys: async () => {
|
|
74
|
+
const stored = (await run("readonly", (store) => store.getAllKeys())).map(String);
|
|
75
|
+
return [.../* @__PURE__ */ new Set([...stored, ...emergencyKeys()])];
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { createIndexedDbRecovery };
|