fabricjs-document-engine 0.6.0 → 0.8.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 +74 -3
- package/dist/assets/asset-pipeline.cjs +3 -1
- package/dist/assets/asset-pipeline.js +3 -1
- package/dist/engine/create-document-engine.cjs +8 -4
- package/dist/engine/create-document-engine.d.cts +5 -0
- package/dist/engine/create-document-engine.d.ts +5 -0
- package/dist/engine/create-document-engine.js +9 -5
- package/dist/engine/errors.d.cts +1 -1
- package/dist/engine/errors.d.ts +1 -1
- package/dist/export/render-export.cjs +3 -15
- package/dist/export/render-export.js +4 -15
- package/dist/history/apply-state.cjs +3 -1
- package/dist/history/apply-state.js +3 -1
- package/dist/history/create-history.cjs +4 -1
- package/dist/history/create-history.d.cts +1 -0
- package/dist/history/create-history.d.ts +1 -0
- package/dist/history/create-history.js +4 -1
- package/dist/history/history-stack.cjs +44 -8
- package/dist/history/history-stack.js +44 -9
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/react/engine-context.cjs +12 -0
- package/dist/react/engine-context.d.cts +10 -0
- package/dist/react/engine-context.d.ts +10 -0
- package/dist/react/engine-context.js +11 -0
- package/dist/react/use-document-engine.cjs +23 -0
- package/dist/react/use-document-engine.d.cts +6 -0
- package/dist/react/use-document-engine.d.ts +6 -0
- package/dist/react/use-document-engine.js +23 -0
- package/dist/react/use-document-event.cjs +12 -0
- package/dist/react/use-document-event.d.cts +4 -0
- package/dist/react/use-document-event.d.ts +4 -0
- package/dist/react/use-document-event.js +12 -0
- package/dist/react/use-document-state.cjs +11 -0
- package/dist/react/use-document-state.d.cts +5 -0
- package/dist/react/use-document-state.d.ts +5 -0
- package/dist/react/use-document-state.js +11 -0
- package/dist/react.cjs +11 -0
- package/dist/react.d.cts +6 -0
- package/dist/react.d.ts +6 -0
- package/dist/react.js +6 -0
- package/dist/recovery/recovery-controller.cjs +29 -3
- package/dist/recovery/recovery-controller.js +29 -3
- package/dist/security/content-limits.cjs +86 -0
- package/dist/security/content-limits.d.cts +11 -0
- package/dist/security/content-limits.d.ts +11 -0
- package/dist/security/content-limits.js +84 -0
- package/dist/state/document-state-store.cjs +114 -0
- package/dist/state/document-state-store.d.cts +28 -0
- package/dist/state/document-state-store.d.ts +28 -0
- package/dist/state/document-state-store.js +114 -0
- package/package.json +27 -5
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/state/document-state-store.ts
|
|
2
|
+
function initialState(engine) {
|
|
3
|
+
const saveState = engine.getSaveState();
|
|
4
|
+
const history = engine.getHistory();
|
|
5
|
+
return {
|
|
6
|
+
documentId: engine.getDocumentInfo().id,
|
|
7
|
+
isLoading: false,
|
|
8
|
+
loadError: void 0,
|
|
9
|
+
saveStatus: saveState.status,
|
|
10
|
+
isDirty: saveState.isDirty,
|
|
11
|
+
isSaving: saveState.isSaving,
|
|
12
|
+
revision: saveState.revision,
|
|
13
|
+
lastSavedAt: saveState.lastSavedAt,
|
|
14
|
+
saveError: saveState.error,
|
|
15
|
+
canUndo: history.undo.length > 0,
|
|
16
|
+
canRedo: history.redo.length > 0,
|
|
17
|
+
undoLabel: history.undo[0],
|
|
18
|
+
redoLabel: history.redo[0],
|
|
19
|
+
assetWarnings: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function withLiveEngineValues(engine, previous) {
|
|
23
|
+
return {
|
|
24
|
+
...previous,
|
|
25
|
+
...initialState(engine),
|
|
26
|
+
isLoading: previous.isLoading,
|
|
27
|
+
loadError: previous.loadError,
|
|
28
|
+
assetWarnings: previous.assetWarnings
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isSameState(first, second) {
|
|
32
|
+
return Object.keys(first).every((key) => first[key] === second[key]);
|
|
33
|
+
}
|
|
34
|
+
function createDocumentStateStore(engine) {
|
|
35
|
+
let state = initialState(engine);
|
|
36
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
37
|
+
let detachFromEngine;
|
|
38
|
+
function update(changes) {
|
|
39
|
+
const next = {
|
|
40
|
+
...state,
|
|
41
|
+
...changes
|
|
42
|
+
};
|
|
43
|
+
if (isSameState(state, next)) return;
|
|
44
|
+
state = next;
|
|
45
|
+
for (const listener of [...listeners]) listener();
|
|
46
|
+
}
|
|
47
|
+
function attachToEngine() {
|
|
48
|
+
const unsubscribers = [
|
|
49
|
+
engine.on("load:start", () => update({
|
|
50
|
+
isLoading: true,
|
|
51
|
+
loadError: void 0
|
|
52
|
+
})),
|
|
53
|
+
engine.on("load:success", ({ warnings }) => update({
|
|
54
|
+
isLoading: false,
|
|
55
|
+
assetWarnings: warnings
|
|
56
|
+
})),
|
|
57
|
+
engine.on("load:error", ({ error }) => {
|
|
58
|
+
if (error.code !== "LOAD_ABORTED") update({
|
|
59
|
+
isLoading: false,
|
|
60
|
+
loadError: error
|
|
61
|
+
});
|
|
62
|
+
}),
|
|
63
|
+
engine.on("document:change", ({ documentId }) => update({
|
|
64
|
+
documentId,
|
|
65
|
+
loadError: void 0
|
|
66
|
+
})),
|
|
67
|
+
engine.on("assets:warning", ({ warnings }) => update({ assetWarnings: warnings })),
|
|
68
|
+
engine.on("save:status", (saveState) => update({
|
|
69
|
+
saveStatus: saveState.status,
|
|
70
|
+
isDirty: saveState.isDirty,
|
|
71
|
+
isSaving: saveState.isSaving,
|
|
72
|
+
revision: saveState.revision,
|
|
73
|
+
lastSavedAt: saveState.lastSavedAt,
|
|
74
|
+
saveError: saveState.error
|
|
75
|
+
})),
|
|
76
|
+
engine.on("history:change", (history) => update({
|
|
77
|
+
canUndo: history.canUndo,
|
|
78
|
+
canRedo: history.canRedo,
|
|
79
|
+
undoLabel: history.undoLabel,
|
|
80
|
+
redoLabel: history.redoLabel
|
|
81
|
+
}))
|
|
82
|
+
];
|
|
83
|
+
return () => unsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
84
|
+
}
|
|
85
|
+
function refreshWhileDetached() {
|
|
86
|
+
if (detachFromEngine) return;
|
|
87
|
+
const fresh = withLiveEngineValues(engine, state);
|
|
88
|
+
if (!isSameState(state, fresh)) state = fresh;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
getSnapshot() {
|
|
92
|
+
refreshWhileDetached();
|
|
93
|
+
return state;
|
|
94
|
+
},
|
|
95
|
+
subscribe(listener) {
|
|
96
|
+
refreshWhileDetached();
|
|
97
|
+
listeners.add(listener);
|
|
98
|
+
detachFromEngine ?? (detachFromEngine = attachToEngine());
|
|
99
|
+
return () => {
|
|
100
|
+
listeners.delete(listener);
|
|
101
|
+
if (listeners.size > 0) return;
|
|
102
|
+
detachFromEngine?.();
|
|
103
|
+
detachFromEngine = void 0;
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
destroy() {
|
|
107
|
+
listeners.clear();
|
|
108
|
+
detachFromEngine?.();
|
|
109
|
+
detachFromEngine = void 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
exports.createDocumentStateStore = createDocumentStateStore;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AssetWarning } from "../assets/asset-pipeline.cjs";
|
|
2
|
+
import { DocumentEngineError } from "../engine/errors.cjs";
|
|
3
|
+
import { SaveStatus } from "../save/save-controller.cjs";
|
|
4
|
+
import { DocumentEngine } from "../engine/create-document-engine.cjs";
|
|
5
|
+
//#region src/state/document-state-store.d.ts
|
|
6
|
+
export interface DocumentState {
|
|
7
|
+
documentId: string;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
loadError: DocumentEngineError | undefined;
|
|
10
|
+
saveStatus: SaveStatus;
|
|
11
|
+
isDirty: boolean;
|
|
12
|
+
isSaving: boolean;
|
|
13
|
+
revision: number;
|
|
14
|
+
lastSavedAt: string | undefined;
|
|
15
|
+
saveError: DocumentEngineError | undefined;
|
|
16
|
+
canUndo: boolean;
|
|
17
|
+
canRedo: boolean;
|
|
18
|
+
undoLabel: string | undefined;
|
|
19
|
+
redoLabel: string | undefined;
|
|
20
|
+
assetWarnings: AssetWarning[];
|
|
21
|
+
}
|
|
22
|
+
export interface DocumentStateStore {
|
|
23
|
+
getSnapshot(): DocumentState;
|
|
24
|
+
subscribe(listener: () => void): () => void;
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function createDocumentStateStore(engine: DocumentEngine): DocumentStateStore;
|
|
28
|
+
//#endregion
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AssetWarning } from "../assets/asset-pipeline.js";
|
|
2
|
+
import { DocumentEngineError } from "../engine/errors.js";
|
|
3
|
+
import { SaveStatus } from "../save/save-controller.js";
|
|
4
|
+
import { DocumentEngine } from "../engine/create-document-engine.js";
|
|
5
|
+
//#region src/state/document-state-store.d.ts
|
|
6
|
+
export interface DocumentState {
|
|
7
|
+
documentId: string;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
loadError: DocumentEngineError | undefined;
|
|
10
|
+
saveStatus: SaveStatus;
|
|
11
|
+
isDirty: boolean;
|
|
12
|
+
isSaving: boolean;
|
|
13
|
+
revision: number;
|
|
14
|
+
lastSavedAt: string | undefined;
|
|
15
|
+
saveError: DocumentEngineError | undefined;
|
|
16
|
+
canUndo: boolean;
|
|
17
|
+
canRedo: boolean;
|
|
18
|
+
undoLabel: string | undefined;
|
|
19
|
+
redoLabel: string | undefined;
|
|
20
|
+
assetWarnings: AssetWarning[];
|
|
21
|
+
}
|
|
22
|
+
export interface DocumentStateStore {
|
|
23
|
+
getSnapshot(): DocumentState;
|
|
24
|
+
subscribe(listener: () => void): () => void;
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function createDocumentStateStore(engine: DocumentEngine): DocumentStateStore;
|
|
28
|
+
//#endregion
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/state/document-state-store.ts
|
|
2
|
+
function initialState(engine) {
|
|
3
|
+
const saveState = engine.getSaveState();
|
|
4
|
+
const history = engine.getHistory();
|
|
5
|
+
return {
|
|
6
|
+
documentId: engine.getDocumentInfo().id,
|
|
7
|
+
isLoading: false,
|
|
8
|
+
loadError: void 0,
|
|
9
|
+
saveStatus: saveState.status,
|
|
10
|
+
isDirty: saveState.isDirty,
|
|
11
|
+
isSaving: saveState.isSaving,
|
|
12
|
+
revision: saveState.revision,
|
|
13
|
+
lastSavedAt: saveState.lastSavedAt,
|
|
14
|
+
saveError: saveState.error,
|
|
15
|
+
canUndo: history.undo.length > 0,
|
|
16
|
+
canRedo: history.redo.length > 0,
|
|
17
|
+
undoLabel: history.undo[0],
|
|
18
|
+
redoLabel: history.redo[0],
|
|
19
|
+
assetWarnings: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function withLiveEngineValues(engine, previous) {
|
|
23
|
+
return {
|
|
24
|
+
...previous,
|
|
25
|
+
...initialState(engine),
|
|
26
|
+
isLoading: previous.isLoading,
|
|
27
|
+
loadError: previous.loadError,
|
|
28
|
+
assetWarnings: previous.assetWarnings
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isSameState(first, second) {
|
|
32
|
+
return Object.keys(first).every((key) => first[key] === second[key]);
|
|
33
|
+
}
|
|
34
|
+
function createDocumentStateStore(engine) {
|
|
35
|
+
let state = initialState(engine);
|
|
36
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
37
|
+
let detachFromEngine;
|
|
38
|
+
function update(changes) {
|
|
39
|
+
const next = {
|
|
40
|
+
...state,
|
|
41
|
+
...changes
|
|
42
|
+
};
|
|
43
|
+
if (isSameState(state, next)) return;
|
|
44
|
+
state = next;
|
|
45
|
+
for (const listener of [...listeners]) listener();
|
|
46
|
+
}
|
|
47
|
+
function attachToEngine() {
|
|
48
|
+
const unsubscribers = [
|
|
49
|
+
engine.on("load:start", () => update({
|
|
50
|
+
isLoading: true,
|
|
51
|
+
loadError: void 0
|
|
52
|
+
})),
|
|
53
|
+
engine.on("load:success", ({ warnings }) => update({
|
|
54
|
+
isLoading: false,
|
|
55
|
+
assetWarnings: warnings
|
|
56
|
+
})),
|
|
57
|
+
engine.on("load:error", ({ error }) => {
|
|
58
|
+
if (error.code !== "LOAD_ABORTED") update({
|
|
59
|
+
isLoading: false,
|
|
60
|
+
loadError: error
|
|
61
|
+
});
|
|
62
|
+
}),
|
|
63
|
+
engine.on("document:change", ({ documentId }) => update({
|
|
64
|
+
documentId,
|
|
65
|
+
loadError: void 0
|
|
66
|
+
})),
|
|
67
|
+
engine.on("assets:warning", ({ warnings }) => update({ assetWarnings: warnings })),
|
|
68
|
+
engine.on("save:status", (saveState) => update({
|
|
69
|
+
saveStatus: saveState.status,
|
|
70
|
+
isDirty: saveState.isDirty,
|
|
71
|
+
isSaving: saveState.isSaving,
|
|
72
|
+
revision: saveState.revision,
|
|
73
|
+
lastSavedAt: saveState.lastSavedAt,
|
|
74
|
+
saveError: saveState.error
|
|
75
|
+
})),
|
|
76
|
+
engine.on("history:change", (history) => update({
|
|
77
|
+
canUndo: history.canUndo,
|
|
78
|
+
canRedo: history.canRedo,
|
|
79
|
+
undoLabel: history.undoLabel,
|
|
80
|
+
redoLabel: history.redoLabel
|
|
81
|
+
}))
|
|
82
|
+
];
|
|
83
|
+
return () => unsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
84
|
+
}
|
|
85
|
+
function refreshWhileDetached() {
|
|
86
|
+
if (detachFromEngine) return;
|
|
87
|
+
const fresh = withLiveEngineValues(engine, state);
|
|
88
|
+
if (!isSameState(state, fresh)) state = fresh;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
getSnapshot() {
|
|
92
|
+
refreshWhileDetached();
|
|
93
|
+
return state;
|
|
94
|
+
},
|
|
95
|
+
subscribe(listener) {
|
|
96
|
+
refreshWhileDetached();
|
|
97
|
+
listeners.add(listener);
|
|
98
|
+
detachFromEngine ?? (detachFromEngine = attachToEngine());
|
|
99
|
+
return () => {
|
|
100
|
+
listeners.delete(listener);
|
|
101
|
+
if (listeners.size > 0) return;
|
|
102
|
+
detachFromEngine?.();
|
|
103
|
+
detachFromEngine = void 0;
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
destroy() {
|
|
107
|
+
listeners.clear();
|
|
108
|
+
detachFromEngine?.();
|
|
109
|
+
detachFromEngine = void 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createDocumentStateStore };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabricjs-document-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Turn an existing Fabric.js canvas into a dependable editable document: stable object ids, versioned save and load, undo and redo, safe autosave with conflict protection, custom objects and clear errors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fabric",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"editor",
|
|
20
20
|
"typescript",
|
|
21
21
|
"storage",
|
|
22
|
-
"recovery"
|
|
22
|
+
"recovery",
|
|
23
|
+
"react",
|
|
24
|
+
"react hooks"
|
|
23
25
|
],
|
|
24
26
|
"license": "MIT",
|
|
25
27
|
"author": "Sohail Khan (https://me.jscrate.dev)",
|
|
@@ -64,15 +66,25 @@
|
|
|
64
66
|
"default": "./dist/recovery.cjs"
|
|
65
67
|
}
|
|
66
68
|
},
|
|
69
|
+
"./react": {
|
|
70
|
+
"import": {
|
|
71
|
+
"types": "./dist/react.d.ts",
|
|
72
|
+
"default": "./dist/react.js"
|
|
73
|
+
},
|
|
74
|
+
"require": {
|
|
75
|
+
"types": "./dist/react.d.cts",
|
|
76
|
+
"default": "./dist/react.cjs"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
67
79
|
"./package.json": "./package.json"
|
|
68
80
|
},
|
|
69
81
|
"scripts": {
|
|
70
|
-
"build": "tsdown && npm run lint:publint && npm run lint:attw",
|
|
82
|
+
"build": "tsdown && node scripts/verify-build.mjs && npm run lint:publint && npm run lint:attw",
|
|
71
83
|
"dev": "tsdown --watch",
|
|
72
84
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.tools.json",
|
|
73
85
|
"test": "vitest run",
|
|
74
86
|
"test:unit": "vitest run --project unit",
|
|
75
|
-
"test:browser": "vitest run --project browser-fabric7 --project browser-fabric6",
|
|
87
|
+
"test:browser": "vitest run --project browser-fabric7 --project browser-fabric6 --project browser-firefox --project browser-webkit",
|
|
76
88
|
"test:watch": "vitest",
|
|
77
89
|
"lint:publint": "publint --strict",
|
|
78
90
|
"lint:attw": "attw --pack . --profile node16",
|
|
@@ -80,7 +92,8 @@
|
|
|
80
92
|
"release": "changeset publish"
|
|
81
93
|
},
|
|
82
94
|
"peerDependencies": {
|
|
83
|
-
"fabric": "^6.0.0 || ^7.0.0"
|
|
95
|
+
"fabric": "^6.0.0 || ^7.0.0",
|
|
96
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
84
97
|
},
|
|
85
98
|
"publishConfig": {
|
|
86
99
|
"access": "public",
|
|
@@ -101,13 +114,22 @@
|
|
|
101
114
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
102
115
|
"@changesets/cli": "^3.0.3",
|
|
103
116
|
"@types/node": "^22.20.4",
|
|
117
|
+
"@types/react": "^19.3.0",
|
|
118
|
+
"@types/react-dom": "^19.3.0",
|
|
104
119
|
"@vitest/browser-playwright": "^5.0.2",
|
|
105
120
|
"fabric": "^7.4.0",
|
|
106
121
|
"fabric6": "npm:fabric@^6.9.1",
|
|
107
122
|
"playwright": "^1.63.0",
|
|
108
123
|
"publint": "^0.3.24",
|
|
124
|
+
"react": "^19.3.0",
|
|
125
|
+
"react-dom": "^19.3.0",
|
|
109
126
|
"tsdown": "^0.23.0",
|
|
110
127
|
"typescript": "^5.9.3",
|
|
111
128
|
"vitest": "^5.0.2"
|
|
129
|
+
},
|
|
130
|
+
"peerDependenciesMeta": {
|
|
131
|
+
"react": {
|
|
132
|
+
"optional": true
|
|
133
|
+
}
|
|
112
134
|
}
|
|
113
135
|
}
|