@embedpdf/core 2.15.0 → 3.0.0-next.1
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/LICENSE +192 -21
- package/dist/index.cjs +864 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +313 -0
- package/dist/index.d.ts +313 -1
- package/dist/index.js +816 -2124
- package/dist/index.js.map +1 -1
- package/package.json +22 -52
- package/README.md +0 -40
- package/dist/lib/base/base-plugin.d.ts +0 -190
- package/dist/lib/index.d.ts +0 -17
- package/dist/lib/plugin/builder.d.ts +0 -11
- package/dist/lib/registry/plugin-registry.d.ts +0 -104
- package/dist/lib/store/actions.d.ts +0 -154
- package/dist/lib/store/index.d.ts +0 -6
- package/dist/lib/store/initial-state.d.ts +0 -31
- package/dist/lib/store/plugin-store.d.ts +0 -48
- package/dist/lib/store/reducer-helpers.d.ts +0 -11
- package/dist/lib/store/reducer.d.ts +0 -4
- package/dist/lib/store/selectors.d.ts +0 -41
- package/dist/lib/store/store.d.ts +0 -106
- package/dist/lib/store/types.d.ts +0 -28
- package/dist/lib/types/errors.d.ts +0 -21
- package/dist/lib/types/permissions.d.ts +0 -55
- package/dist/lib/types/plugin.d.ts +0 -75
- package/dist/lib/utils/dependency-resolver.d.ts +0 -6
- package/dist/lib/utils/event-control.d.ts +0 -58
- package/dist/lib/utils/eventing.d.ts +0 -19
- package/dist/lib/utils/math.d.ts +0 -22
- package/dist/lib/utils/plugin-helpers.d.ts +0 -6
- package/dist/lib/utils/scoped-eventing.d.ts +0 -71
- package/dist/lib/utils/typed-object.d.ts +0 -12
- package/dist/preact/adapter.d.ts +0 -11
- package/dist/preact/index.cjs +0 -2
- package/dist/preact/index.cjs.map +0 -1
- package/dist/preact/index.d.ts +0 -1
- package/dist/preact/index.js +0 -323
- package/dist/preact/index.js.map +0 -1
- package/dist/react/adapter.d.ts +0 -2
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -322
- package/dist/react/index.js.map +0 -1
- package/dist/shared/components/auto-mount.d.ts +0 -8
- package/dist/shared/components/embed-pdf.d.ts +0 -37
- package/dist/shared/components/index.d.ts +0 -1
- package/dist/shared/context.d.ts +0 -12
- package/dist/shared/hooks/index.d.ts +0 -7
- package/dist/shared/hooks/use-capability.d.ts +0 -16
- package/dist/shared/hooks/use-core-state.d.ts +0 -8
- package/dist/shared/hooks/use-document-permissions.d.ts +0 -35
- package/dist/shared/hooks/use-document-state.d.ts +0 -8
- package/dist/shared/hooks/use-plugin.d.ts +0 -16
- package/dist/shared/hooks/use-registry.d.ts +0 -6
- package/dist/shared/hooks/use-store-state.d.ts +0 -6
- package/dist/shared/index.d.ts +0 -3
- package/dist/shared-preact/components/auto-mount.d.ts +0 -8
- package/dist/shared-preact/components/embed-pdf.d.ts +0 -37
- package/dist/shared-preact/components/index.d.ts +0 -1
- package/dist/shared-preact/context.d.ts +0 -12
- package/dist/shared-preact/hooks/index.d.ts +0 -7
- package/dist/shared-preact/hooks/use-capability.d.ts +0 -16
- package/dist/shared-preact/hooks/use-core-state.d.ts +0 -8
- package/dist/shared-preact/hooks/use-document-permissions.d.ts +0 -35
- package/dist/shared-preact/hooks/use-document-state.d.ts +0 -8
- package/dist/shared-preact/hooks/use-plugin.d.ts +0 -16
- package/dist/shared-preact/hooks/use-registry.d.ts +0 -6
- package/dist/shared-preact/hooks/use-store-state.d.ts +0 -6
- package/dist/shared-preact/index.d.ts +0 -3
- package/dist/shared-react/components/auto-mount.d.ts +0 -8
- package/dist/shared-react/components/embed-pdf.d.ts +0 -37
- package/dist/shared-react/components/index.d.ts +0 -1
- package/dist/shared-react/context.d.ts +0 -12
- package/dist/shared-react/hooks/index.d.ts +0 -7
- package/dist/shared-react/hooks/use-capability.d.ts +0 -16
- package/dist/shared-react/hooks/use-core-state.d.ts +0 -8
- package/dist/shared-react/hooks/use-document-permissions.d.ts +0 -35
- package/dist/shared-react/hooks/use-document-state.d.ts +0 -8
- package/dist/shared-react/hooks/use-plugin.d.ts +0 -16
- package/dist/shared-react/hooks/use-registry.d.ts +0 -6
- package/dist/shared-react/hooks/use-store-state.d.ts +0 -6
- package/dist/shared-react/index.d.ts +0 -3
- package/dist/svelte/components/AutoMount.svelte.d.ts +0 -9
- package/dist/svelte/components/EmbedPDF.svelte.d.ts +0 -40
- package/dist/svelte/components/NestedWrapper.svelte.d.ts +0 -10
- package/dist/svelte/components/index.d.ts +0 -1
- package/dist/svelte/hooks/index.d.ts +0 -6
- package/dist/svelte/hooks/use-capability.svelte.d.ts +0 -14
- package/dist/svelte/hooks/use-core-state.svelte.d.ts +0 -9
- package/dist/svelte/hooks/use-document-permissions.svelte.d.ts +0 -35
- package/dist/svelte/hooks/use-document-state.svelte.d.ts +0 -9
- package/dist/svelte/hooks/use-plugin.svelte.d.ts +0 -14
- package/dist/svelte/hooks/use-registry.svelte.d.ts +0 -17
- package/dist/svelte/index.cjs +0 -2
- package/dist/svelte/index.cjs.map +0 -1
- package/dist/svelte/index.d.ts +0 -2
- package/dist/svelte/index.js +0 -430
- package/dist/svelte/index.js.map +0 -1
- package/dist/vue/components/auto-mount.vue.d.ts +0 -19
- package/dist/vue/components/embed-pdf.vue.d.ts +0 -104
- package/dist/vue/components/index.d.ts +0 -1
- package/dist/vue/components/nested-wrapper.vue.d.ts +0 -19
- package/dist/vue/composables/index.d.ts +0 -7
- package/dist/vue/composables/use-capability.d.ts +0 -15
- package/dist/vue/composables/use-core-state.d.ts +0 -9
- package/dist/vue/composables/use-document-permissions.d.ts +0 -36
- package/dist/vue/composables/use-document-state.d.ts +0 -8
- package/dist/vue/composables/use-plugin.d.ts +0 -8
- package/dist/vue/composables/use-registry.d.ts +0 -1
- package/dist/vue/composables/use-store-state.d.ts +0 -10
- package/dist/vue/context.d.ts +0 -13
- package/dist/vue/index.cjs +0 -2
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.ts +0 -2
- package/dist/vue/index.js +0 -384
- package/dist/vue/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2126 +1,818 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
1
|
+
import { AbortablePromise, CONTINUOUS_RENDER_POLICY, CONTINUOUS_RENDER_POLICY as CONTINUOUS_RENDER_POLICY$1, snapAppearanceScale, snapFullPageViewport, snapTileScale } from "@embedpdf/engine-core/runtime";
|
|
2
|
+
//#region src/types.ts
|
|
3
|
+
/** Kernel-emitted document-lifecycle actions; plugins may react via `onAction`. */
|
|
4
|
+
const CORE_DOCUMENT_ADDED = "@@core/document-added";
|
|
5
|
+
const CORE_DOCUMENT_REMOVED = "@@core/document-removed";
|
|
6
|
+
const CORE_ACTIVE_CHANGED = "@@core/active-changed";
|
|
7
|
+
const CORE_ORDER_CHANGED = "@@core/order-changed";
|
|
8
|
+
/** A document's page registry was replaced by a mutation event (rotate/move/delete). */
|
|
9
|
+
const CORE_DOCUMENT_PAGES_UPDATED = "@@core/document-pages-updated";
|
|
10
|
+
/** A tab slot was reserved: `open()` was called; the document is loading. */
|
|
11
|
+
const CORE_DOCUMENT_OPENING = "@@core/document-opening";
|
|
12
|
+
/** The engine reports the document needs a password (`documents.unlock`). */
|
|
13
|
+
const CORE_DOCUMENT_LOCKED = "@@core/document-locked";
|
|
14
|
+
/** The open failed; the tab stays with `status: 'error'` until closed. */
|
|
15
|
+
const CORE_DOCUMENT_OPEN_FAILED = "@@core/document-open-failed";
|
|
16
|
+
/** Field-wise DocInfo equality — the ONE definition every adapter's reactive
|
|
17
|
+
* `docs` read keys on, so a new lifecycle field can never silently stop
|
|
18
|
+
* re-rendering one framework's tab bar. */
|
|
19
|
+
const docInfoEquals = (a, b) => a.id === b.id && a.name === b.name && a.status === b.status && a.pageCount === b.pageCount && a.passwordProvided === b.passwordProvided;
|
|
20
|
+
const docInfoListEquals = (a, b) => a === b || a.length === b.length && a.every((d, i) => docInfoEquals(d, b[i]));
|
|
21
|
+
/** Built-in token for the document registry capability (provided by the kernel). */
|
|
22
|
+
const DocumentsToken = { name: "documents" };
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/store.ts
|
|
25
|
+
function createStore(report = console.error) {
|
|
26
|
+
let core = {
|
|
27
|
+
documents: {},
|
|
28
|
+
pending: {},
|
|
29
|
+
order: [],
|
|
30
|
+
activeId: null
|
|
31
|
+
};
|
|
32
|
+
const slices = {};
|
|
33
|
+
const reducers = {};
|
|
34
|
+
const changeListeners = /* @__PURE__ */ new Set();
|
|
35
|
+
const actionListeners = /* @__PURE__ */ new Set();
|
|
36
|
+
const guarded = (fn) => {
|
|
37
|
+
try {
|
|
38
|
+
fn();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
report(error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
let emitting = false;
|
|
44
|
+
let pending = false;
|
|
45
|
+
const emitChange = () => {
|
|
46
|
+
if (emitting) {
|
|
47
|
+
pending = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
emitting = true;
|
|
51
|
+
try {
|
|
52
|
+
do {
|
|
53
|
+
pending = false;
|
|
54
|
+
changeListeners.forEach((listener) => guarded(listener));
|
|
55
|
+
} while (pending);
|
|
56
|
+
} finally {
|
|
57
|
+
emitting = false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const emitAction = (action) => actionListeners.forEach((listener) => guarded(() => listener(action)));
|
|
61
|
+
return {
|
|
62
|
+
registerSlice(key, reducer, initial) {
|
|
63
|
+
reducers[key] = reducer;
|
|
64
|
+
slices[key] = initial;
|
|
65
|
+
},
|
|
66
|
+
removeSlice(key) {
|
|
67
|
+
delete reducers[key];
|
|
68
|
+
delete slices[key];
|
|
69
|
+
emitChange();
|
|
70
|
+
},
|
|
71
|
+
getSlice: (key) => slices[key],
|
|
72
|
+
getCore: () => core,
|
|
73
|
+
getState: () => ({
|
|
74
|
+
core,
|
|
75
|
+
plugins: slices
|
|
76
|
+
}),
|
|
77
|
+
dispatchTo(key, action) {
|
|
78
|
+
const r = reducers[key];
|
|
79
|
+
if (!r) return;
|
|
80
|
+
const next = r(slices[key], action);
|
|
81
|
+
if (next !== slices[key]) {
|
|
82
|
+
slices[key] = next;
|
|
83
|
+
emitChange();
|
|
84
|
+
}
|
|
85
|
+
emitAction(action);
|
|
86
|
+
},
|
|
87
|
+
setCore(patch, action) {
|
|
88
|
+
core = {
|
|
89
|
+
...core,
|
|
90
|
+
...patch
|
|
91
|
+
};
|
|
92
|
+
emitChange();
|
|
93
|
+
emitAction(action);
|
|
94
|
+
},
|
|
95
|
+
subscribe(listener) {
|
|
96
|
+
changeListeners.add(listener);
|
|
97
|
+
return () => void changeListeners.delete(listener);
|
|
98
|
+
},
|
|
99
|
+
subscribeAction(listener) {
|
|
100
|
+
actionListeners.add(listener);
|
|
101
|
+
return () => void actionListeners.delete(listener);
|
|
102
|
+
},
|
|
103
|
+
destroy() {
|
|
104
|
+
core = {
|
|
105
|
+
documents: {},
|
|
106
|
+
pending: {},
|
|
107
|
+
order: [],
|
|
108
|
+
activeId: null
|
|
109
|
+
};
|
|
110
|
+
for (const key of Object.keys(slices)) delete slices[key];
|
|
111
|
+
for (const key of Object.keys(reducers)) delete reducers[key];
|
|
112
|
+
changeListeners.clear();
|
|
113
|
+
actionListeners.clear();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/context.ts
|
|
119
|
+
/** A plugin's slice key. Workspace plugins use their id; document-scoped plugins are per-document. */
|
|
120
|
+
const sliceKey = (pluginId, documentId) => documentId ? `${pluginId}::${documentId}` : pluginId;
|
|
121
|
+
/**
|
|
122
|
+
* Build the context a plugin sees. When `session` is given the context is bound
|
|
123
|
+
* to that document — `getState`/`dispatch` target its slice, `document()` returns
|
|
124
|
+
* it, and `get()` resolves document-scoped capabilities for it.
|
|
125
|
+
*/
|
|
126
|
+
function createPluginContext(services, plugin, session) {
|
|
127
|
+
const { engine, store } = services;
|
|
128
|
+
const documentId = session?.id;
|
|
129
|
+
const key = sliceKey(plugin.id, documentId);
|
|
130
|
+
const ownScope = session?.scope ?? services.workspaceScope;
|
|
131
|
+
return {
|
|
132
|
+
id: plugin.id,
|
|
133
|
+
engine,
|
|
134
|
+
documentId,
|
|
135
|
+
doc: session ? session.handle : services.documentHandle(void 0),
|
|
136
|
+
documentHandle: (requestedDocumentId) => requestedDocumentId ? services.documentHandle(requestedDocumentId) : session ? session.handle : services.documentHandle(void 0),
|
|
137
|
+
getState: () => store.getSlice(key),
|
|
138
|
+
dispatch: (action) => store.dispatchTo(key, action),
|
|
139
|
+
subscribe: store.subscribe,
|
|
140
|
+
core: store.getCore,
|
|
141
|
+
document: () => {
|
|
142
|
+
if (session) return store.getCore().documents[session.id] ?? session.stagedMeta;
|
|
143
|
+
const id = store.getCore().activeId;
|
|
144
|
+
return id ? store.getCore().documents[id] ?? null : null;
|
|
145
|
+
},
|
|
146
|
+
get: (token) => services.resolveCapability(token, documentId),
|
|
147
|
+
forDocument: (token, otherDocumentId) => services.resolveCapability(token, otherDocumentId),
|
|
148
|
+
tryGet: (token) => services.tryResolveCapability(token, documentId),
|
|
149
|
+
cleanup: (teardown) => ownScope.defer(teardown)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** A plugin context plus the side-effect primitives (watch / onAction / cleanup). */
|
|
153
|
+
function createEffectContext(services, plugin, session) {
|
|
154
|
+
const { store } = services;
|
|
155
|
+
const ownScope = session?.scope ?? services.workspaceScope;
|
|
156
|
+
return {
|
|
157
|
+
...createPluginContext(services, plugin, session),
|
|
158
|
+
watch: (select, handler, isEqual = Object.is) => {
|
|
159
|
+
let previous = select();
|
|
160
|
+
const unsubscribe = store.subscribe(() => {
|
|
161
|
+
const next = select();
|
|
162
|
+
if (!isEqual(previous, next)) {
|
|
163
|
+
const prior = previous;
|
|
164
|
+
previous = next;
|
|
165
|
+
handler(next, prior);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
ownScope.defer(unsubscribe);
|
|
169
|
+
return unsubscribe;
|
|
170
|
+
},
|
|
171
|
+
onAction: (type, handler) => {
|
|
172
|
+
const unsubscribe = store.subscribeAction((action) => {
|
|
173
|
+
if (action.type === type) handler(action);
|
|
174
|
+
});
|
|
175
|
+
ownScope.defer(unsubscribe);
|
|
176
|
+
return unsubscribe;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/order.ts
|
|
182
|
+
/**
|
|
183
|
+
* Validate `requires` (fail fast) and topologically sort plugins so dependencies
|
|
184
|
+
* are initialised before dependents.
|
|
185
|
+
*/
|
|
186
|
+
function planPlugins(plugins) {
|
|
187
|
+
const providerByToken = /* @__PURE__ */ new Map();
|
|
188
|
+
const scopeByToken = /* @__PURE__ */ new Map([[DocumentsToken, "workspace"]]);
|
|
189
|
+
for (const plugin of plugins) if (plugin.token && plugin.capability) {
|
|
190
|
+
providerByToken.set(plugin.token, plugin);
|
|
191
|
+
scopeByToken.set(plugin.token, plugin.scope ?? "workspace");
|
|
192
|
+
}
|
|
193
|
+
for (const plugin of plugins) for (const required of plugin.requires ?? []) if (required !== DocumentsToken && !providerByToken.has(required)) throw new Error(`Plugin "${plugin.id}" requires capability "${required.name}", which no plugin provides.`);
|
|
194
|
+
const ordered = [];
|
|
195
|
+
const status = /* @__PURE__ */ new Map();
|
|
196
|
+
const visit = (plugin) => {
|
|
197
|
+
const state = status.get(plugin.id);
|
|
198
|
+
if (state === "done") return;
|
|
199
|
+
if (state === "visiting") throw new Error(`Dependency cycle involving plugin "${plugin.id}".`);
|
|
200
|
+
status.set(plugin.id, "visiting");
|
|
201
|
+
for (const token of [...plugin.requires ?? [], ...plugin.optional ?? []]) {
|
|
202
|
+
const dependency = providerByToken.get(token);
|
|
203
|
+
if (dependency && dependency !== plugin) visit(dependency);
|
|
204
|
+
}
|
|
205
|
+
status.set(plugin.id, "done");
|
|
206
|
+
ordered.push(plugin);
|
|
207
|
+
};
|
|
208
|
+
for (const plugin of plugins) visit(plugin);
|
|
209
|
+
return {
|
|
210
|
+
ordered,
|
|
211
|
+
providerOf: (token) => providerByToken.get(token),
|
|
212
|
+
scopeOf: (token) => scopeByToken.get(token) ?? "workspace"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/scope.ts
|
|
217
|
+
/**
|
|
218
|
+
* A lifecycle operation was cancelled — the tab was closed mid-open, or the
|
|
219
|
+
* kernel was destroyed mid-start. NOT a failure: callers that race `open()`
|
|
220
|
+
* against `close()` should treat this rejection as "the close won".
|
|
221
|
+
*/
|
|
222
|
+
var CancelledError = class extends Error {
|
|
223
|
+
constructor(what) {
|
|
224
|
+
super(`[kernel] cancelled: ${what}`);
|
|
225
|
+
this.name = "CancelledError";
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const isCancelled = (error) => error instanceof CancelledError;
|
|
229
|
+
function createScope(report) {
|
|
230
|
+
const teardowns = [];
|
|
231
|
+
let disposal = null;
|
|
232
|
+
return {
|
|
233
|
+
get disposed() {
|
|
234
|
+
return disposal !== null;
|
|
235
|
+
},
|
|
236
|
+
defer(teardown) {
|
|
237
|
+
if (disposal) disposal.then(() => Promise.resolve().then(teardown).catch(report));
|
|
238
|
+
else teardowns.push(teardown);
|
|
239
|
+
},
|
|
240
|
+
dispose() {
|
|
241
|
+
return disposal ?? (disposal = (async () => {
|
|
242
|
+
while (teardowns.length) try {
|
|
243
|
+
await teardowns.pop()();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
report(error);
|
|
246
|
+
}
|
|
247
|
+
})());
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/kernel.ts
|
|
253
|
+
/** The new page registry a document mutation carries, or null for events that
|
|
254
|
+
* don't change page structure (annotations, metadata). The snapshot is the
|
|
255
|
+
* same shape `pages.list()` returns, so callers swap it in directly. */
|
|
256
|
+
function layoutFromEvent(event) {
|
|
257
|
+
switch (event.type) {
|
|
258
|
+
case "pages.moved":
|
|
259
|
+
case "pages.rotated":
|
|
260
|
+
case "pages.deleted":
|
|
261
|
+
case "pages.inserted": return event.layout;
|
|
262
|
+
default: return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const isDocumentScoped = (plugin) => plugin.scope === "document";
|
|
266
|
+
const initialStateOf = (plugin) => typeof plugin.initialState === "function" ? plugin.initialState() : plugin.initialState ?? {};
|
|
267
|
+
const reducerOf = (plugin) => plugin.reduce ?? ((state) => state);
|
|
268
|
+
const toDocInfo = (meta) => ({
|
|
269
|
+
id: meta.id,
|
|
270
|
+
name: meta.name,
|
|
271
|
+
status: "ready",
|
|
272
|
+
pageCount: meta.pageCount
|
|
267
273
|
});
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
constructor(reducer, initialCoreState2) {
|
|
275
|
-
this.initialCoreState = initialCoreState2;
|
|
276
|
-
this.pluginReducers = {};
|
|
277
|
-
this.listeners = [];
|
|
278
|
-
this.pluginListeners = {};
|
|
279
|
-
this.isDispatching = false;
|
|
280
|
-
this.state = { core: initialCoreState2, plugins: {} };
|
|
281
|
-
this.coreReducer = reducer;
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Adds a reducer for a plugin-specific state.
|
|
285
|
-
* @param pluginId The unique identifier for the plugin.
|
|
286
|
-
* @param reducer The reducer function for the plugin state.
|
|
287
|
-
* @param initialState The initial state for the plugin.
|
|
288
|
-
*/
|
|
289
|
-
addPluginReducer(pluginId, reducer, initialState) {
|
|
290
|
-
this.state.plugins[pluginId] = initialState;
|
|
291
|
-
this.pluginReducers[pluginId] = reducer;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Dispatches an action *only* to the core reducer.
|
|
295
|
-
* Notifies the global store listeners with (action, newState, oldState).
|
|
296
|
-
*
|
|
297
|
-
* @param action The action to dispatch, typed as CoreAction
|
|
298
|
-
* @returns The updated *global* store state
|
|
299
|
-
*/
|
|
300
|
-
dispatchToCore(action) {
|
|
301
|
-
if (!this.coreReducer) {
|
|
302
|
-
return this.getState();
|
|
303
|
-
}
|
|
304
|
-
if (this.isDispatching) {
|
|
305
|
-
throw new Error(
|
|
306
|
-
"Reducers may not dispatch actions. To trigger cascading actions, dispatch from a listener callback instead."
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
const oldState = this.getState();
|
|
310
|
-
try {
|
|
311
|
-
this.isDispatching = true;
|
|
312
|
-
this.state.core = this.coreReducer(this.state.core, action);
|
|
313
|
-
} finally {
|
|
314
|
-
this.isDispatching = false;
|
|
315
|
-
}
|
|
316
|
-
this.listeners.forEach((listener) => {
|
|
317
|
-
const currentState = this.getState();
|
|
318
|
-
listener(action, currentState, oldState);
|
|
319
|
-
});
|
|
320
|
-
return this.getState();
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Dispatches an action *only* to a specific plugin.
|
|
324
|
-
* Optionally notifies global store listeners if `notifyGlobal` is true.
|
|
325
|
-
* Always notifies plugin-specific listeners with (action, newPluginState, oldPluginState).
|
|
326
|
-
*
|
|
327
|
-
* @param pluginId The plugin identifier
|
|
328
|
-
* @param action The plugin action to dispatch
|
|
329
|
-
* @param notifyGlobal Whether to also notify global store listeners
|
|
330
|
-
* @returns The updated plugin state
|
|
331
|
-
*/
|
|
332
|
-
dispatchToPlugin(pluginId, action, notifyGlobal = true) {
|
|
333
|
-
if (this.isDispatching) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
"Reducers may not dispatch actions. To trigger cascading actions, dispatch from a listener callback instead."
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
const oldGlobalState = this.getState();
|
|
339
|
-
const reducer = this.pluginReducers[pluginId];
|
|
340
|
-
if (!reducer) {
|
|
341
|
-
return oldGlobalState.plugins[pluginId];
|
|
342
|
-
}
|
|
343
|
-
const oldPluginState = oldGlobalState.plugins[pluginId];
|
|
344
|
-
try {
|
|
345
|
-
this.isDispatching = true;
|
|
346
|
-
const newPluginState = reducer(oldPluginState, action);
|
|
347
|
-
this.state.plugins[pluginId] = newPluginState;
|
|
348
|
-
} finally {
|
|
349
|
-
this.isDispatching = false;
|
|
350
|
-
}
|
|
351
|
-
if (notifyGlobal) {
|
|
352
|
-
this.listeners.forEach((listener) => {
|
|
353
|
-
const currentGlobalState = this.getState();
|
|
354
|
-
listener(action, currentGlobalState, oldGlobalState);
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
if (this.pluginListeners[pluginId]) {
|
|
358
|
-
this.pluginListeners[pluginId].forEach((listener) => {
|
|
359
|
-
const currentPluginState = this.getState().plugins[pluginId];
|
|
360
|
-
listener(action, currentPluginState, oldPluginState);
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
return this.getState().plugins[pluginId];
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Dispatches an action to update the state using:
|
|
367
|
-
* - the core reducer (if it's a CoreAction)
|
|
368
|
-
* - *all* plugin reducers (regardless of action type), with no global notify for each plugin
|
|
369
|
-
*
|
|
370
|
-
* Returns the new *global* store state after all reducers have processed the action.
|
|
371
|
-
*
|
|
372
|
-
* @param action The action to dispatch (can be CoreAction or any Action).
|
|
373
|
-
*/
|
|
374
|
-
dispatch(action) {
|
|
375
|
-
if (this.isDispatching) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
"Reducers may not dispatch actions. To trigger cascading actions, dispatch from a listener callback instead."
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
const oldState = this.getState();
|
|
381
|
-
try {
|
|
382
|
-
this.isDispatching = true;
|
|
383
|
-
if (this.isCoreAction(action)) {
|
|
384
|
-
this.state.core = this.coreReducer(this.state.core, action);
|
|
385
|
-
}
|
|
386
|
-
for (const pluginId in this.pluginReducers) {
|
|
387
|
-
const reducer = this.pluginReducers[pluginId];
|
|
388
|
-
const oldPluginState = oldState.plugins[pluginId];
|
|
389
|
-
if (reducer) {
|
|
390
|
-
this.state.plugins[pluginId] = reducer(oldPluginState, action);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
} finally {
|
|
394
|
-
this.isDispatching = false;
|
|
395
|
-
}
|
|
396
|
-
this.listeners.forEach((listener) => {
|
|
397
|
-
const currentState = this.getState();
|
|
398
|
-
listener(action, currentState, oldState);
|
|
399
|
-
});
|
|
400
|
-
return this.getState();
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Returns a shallow copy of the current state.
|
|
404
|
-
* @returns The current store state.
|
|
405
|
-
*/
|
|
406
|
-
getState() {
|
|
407
|
-
if (this.isDispatching) {
|
|
408
|
-
throw new Error(
|
|
409
|
-
"You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store."
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
core: { ...this.state.core },
|
|
414
|
-
plugins: { ...this.state.plugins }
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Subscribes a listener to *global* state changes.
|
|
419
|
-
* The callback signature is now (action, newState, oldState).
|
|
420
|
-
*
|
|
421
|
-
* @param listener The callback to invoke on state changes
|
|
422
|
-
* @returns A function to unsubscribe the listener
|
|
423
|
-
*/
|
|
424
|
-
subscribe(listener) {
|
|
425
|
-
if (this.isDispatching) {
|
|
426
|
-
throw new Error(
|
|
427
|
-
"You may not call store.subscribe() while the reducer is executing. If you would like to be notified after the store has been updated, subscribe from a component and invoke store.getState() in the callback to access the latest state."
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
this.listeners.push(listener);
|
|
431
|
-
return () => {
|
|
432
|
-
if (this.isDispatching) {
|
|
433
|
-
throw new Error(
|
|
434
|
-
"You may not unsubscribe from a store listener while the reducer is executing."
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Subscribes a listener to *plugin-specific* state changes.
|
|
442
|
-
* The callback signature is now (action, newPluginState, oldPluginState).
|
|
443
|
-
*
|
|
444
|
-
* @param pluginId The unique identifier for the plugin.
|
|
445
|
-
* @param listener The callback to invoke on plugin state changes.
|
|
446
|
-
* @returns A function to unsubscribe the listener.
|
|
447
|
-
*/
|
|
448
|
-
subscribeToPlugin(pluginId, listener) {
|
|
449
|
-
if (!(pluginId in this.state.plugins)) {
|
|
450
|
-
throw new Error(
|
|
451
|
-
`Plugin state not found for plugin "${pluginId}". Did you forget to call addPluginReducer?`
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
if (this.isDispatching) {
|
|
455
|
-
throw new Error("You may not call store.subscribeToPlugin() while the reducer is executing.");
|
|
456
|
-
}
|
|
457
|
-
if (!this.pluginListeners[pluginId]) {
|
|
458
|
-
this.pluginListeners[pluginId] = [];
|
|
459
|
-
}
|
|
460
|
-
this.pluginListeners[pluginId].push(listener);
|
|
461
|
-
return () => {
|
|
462
|
-
if (this.isDispatching) {
|
|
463
|
-
throw new Error(
|
|
464
|
-
"You may not unsubscribe from a store listener while the reducer is executing."
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
this.pluginListeners[pluginId] = this.pluginListeners[pluginId].filter((l) => l !== listener);
|
|
468
|
-
if (this.pluginListeners[pluginId].length === 0) {
|
|
469
|
-
delete this.pluginListeners[pluginId];
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Subscribes to a specific action type (only from the core's action union).
|
|
475
|
-
* The callback signature is (action, newState, oldState).
|
|
476
|
-
*
|
|
477
|
-
* @param type The action type to listen for.
|
|
478
|
-
* @param handler The callback to invoke when the action occurs.
|
|
479
|
-
* @returns A function to unsubscribe the handler.
|
|
480
|
-
*/
|
|
481
|
-
onAction(type, handler) {
|
|
482
|
-
return this.subscribe((action, newState, oldState) => {
|
|
483
|
-
if (action.type === type) {
|
|
484
|
-
handler(action, newState, oldState);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Gets a PluginStore handle for a specific plugin.
|
|
490
|
-
* @param pluginId The unique identifier for the plugin.
|
|
491
|
-
* @returns A PluginStore instance for the plugin.
|
|
492
|
-
*/
|
|
493
|
-
getPluginStore(pluginId) {
|
|
494
|
-
if (!(pluginId in this.state.plugins)) {
|
|
495
|
-
throw new Error(
|
|
496
|
-
`Plugin state not found for plugin "${pluginId}". Did you forget to call addPluginReducer?`
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
return new PluginStore(this, pluginId);
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Helper method to check if an action is a CoreAction.
|
|
503
|
-
* Adjust if you have a more refined way to differentiate CoreAction vs. any other Action.
|
|
504
|
-
*/
|
|
505
|
-
isCoreAction(action) {
|
|
506
|
-
return CORE_ACTION_TYPES.includes(action.type);
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Destroy the store: drop every listener and plugin reducer
|
|
510
|
-
*/
|
|
511
|
-
destroy() {
|
|
512
|
-
var _a, _b;
|
|
513
|
-
this.listeners.length = 0;
|
|
514
|
-
for (const id in this.pluginListeners) {
|
|
515
|
-
(_b = (_a = this.pluginListeners[id]) == null ? void 0 : _a.splice) == null ? void 0 : _b.call(_a, 0);
|
|
516
|
-
}
|
|
517
|
-
this.pluginListeners = {};
|
|
518
|
-
this.pluginReducers = {};
|
|
519
|
-
this.state.plugins = {};
|
|
520
|
-
this.state.core = { ...this.initialCoreState };
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
const initialCoreState = (config) => ({
|
|
524
|
-
documents: {},
|
|
525
|
-
documentOrder: [],
|
|
526
|
-
activeDocumentId: null,
|
|
527
|
-
defaultScale: (config == null ? void 0 : config.defaultScale) ?? 1,
|
|
528
|
-
defaultRotation: (config == null ? void 0 : config.defaultRotation) ?? Rotation.Degree0,
|
|
529
|
-
globalPermissions: config == null ? void 0 : config.permissions
|
|
274
|
+
const pendingToDocInfo = (meta) => ({
|
|
275
|
+
id: meta.id,
|
|
276
|
+
name: meta.name,
|
|
277
|
+
status: meta.status,
|
|
278
|
+
pageCount: 0,
|
|
279
|
+
passwordProvided: meta.passwordProvided
|
|
530
280
|
});
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
throw error;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
getPluginConfig(pluginId) {
|
|
1073
|
-
const config = this.configurations.get(pluginId);
|
|
1074
|
-
if (!config) {
|
|
1075
|
-
throw new PluginNotFoundError(`Configuration for plugin ${pluginId} not found`);
|
|
1076
|
-
}
|
|
1077
|
-
return config;
|
|
1078
|
-
}
|
|
1079
|
-
validateConfig(pluginId, config, defaultConfig) {
|
|
1080
|
-
const requiredKeys = Object.keys(defaultConfig);
|
|
1081
|
-
const missingKeys = requiredKeys.filter((key) => !config.hasOwnProperty(key));
|
|
1082
|
-
if (missingKeys.length > 0) {
|
|
1083
|
-
throw new PluginConfigurationError(
|
|
1084
|
-
`Missing required configuration keys for plugin ${pluginId}: ${missingKeys.join(", ")}`
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
async updatePluginConfig(pluginId, config) {
|
|
1089
|
-
const plugin = this.getPlugin(pluginId);
|
|
1090
|
-
if (!plugin) {
|
|
1091
|
-
throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
|
|
1092
|
-
}
|
|
1093
|
-
const manifest = this.manifests.get(pluginId);
|
|
1094
|
-
const currentConfig = this.configurations.get(pluginId);
|
|
1095
|
-
if (!manifest || !currentConfig) {
|
|
1096
|
-
throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
|
|
1097
|
-
}
|
|
1098
|
-
const newConfig = {
|
|
1099
|
-
...currentConfig,
|
|
1100
|
-
...config
|
|
1101
|
-
};
|
|
1102
|
-
this.validateConfig(pluginId, newConfig, manifest.defaultConfig);
|
|
1103
|
-
this.configurations.set(pluginId, newConfig);
|
|
1104
|
-
if (plugin.initialize) {
|
|
1105
|
-
await plugin.initialize(newConfig);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Register multiple plugins at once
|
|
1110
|
-
*/
|
|
1111
|
-
registerPluginBatch(registrations) {
|
|
1112
|
-
for (const reg of registrations) {
|
|
1113
|
-
this.registerPlugin(reg.package, reg.config);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Unregister a plugin
|
|
1118
|
-
*/
|
|
1119
|
-
async unregisterPlugin(pluginId) {
|
|
1120
|
-
const plugin = this.plugins.get(pluginId);
|
|
1121
|
-
if (!plugin) {
|
|
1122
|
-
throw new PluginNotFoundError(`Plugin ${pluginId} is not registered`);
|
|
1123
|
-
}
|
|
1124
|
-
const manifest = this.manifests.get(pluginId);
|
|
1125
|
-
if (!manifest) {
|
|
1126
|
-
throw new PluginNotFoundError(`Manifest for plugin ${pluginId} not found`);
|
|
1127
|
-
}
|
|
1128
|
-
for (const [otherId, otherManifest] of this.manifests.entries()) {
|
|
1129
|
-
if (otherId === pluginId) continue;
|
|
1130
|
-
const dependsOnThis = [...otherManifest.requires, ...otherManifest.optional].some(
|
|
1131
|
-
(cap) => manifest.provides.includes(cap)
|
|
1132
|
-
);
|
|
1133
|
-
if (dependsOnThis) {
|
|
1134
|
-
throw new PluginRegistrationError(
|
|
1135
|
-
`Cannot unregister plugin ${pluginId}: plugin ${otherId} depends on it`
|
|
1136
|
-
);
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
try {
|
|
1140
|
-
if (plugin.destroy) {
|
|
1141
|
-
await plugin.destroy();
|
|
1142
|
-
}
|
|
1143
|
-
for (const capability of manifest.provides) {
|
|
1144
|
-
this.capabilities.delete(capability);
|
|
1145
|
-
}
|
|
1146
|
-
this.plugins.delete(pluginId);
|
|
1147
|
-
this.manifests.delete(pluginId);
|
|
1148
|
-
this.status.delete(pluginId);
|
|
1149
|
-
} catch (error) {
|
|
1150
|
-
if (error instanceof Error) {
|
|
1151
|
-
throw new Error(`Failed to unregister plugin ${pluginId}: ${error.message}`);
|
|
1152
|
-
}
|
|
1153
|
-
throw error;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
/**
|
|
1157
|
-
* Get a plugin instance
|
|
1158
|
-
* @param pluginId The ID of the plugin to get
|
|
1159
|
-
* @returns The plugin instance or null if not found
|
|
1160
|
-
*/
|
|
1161
|
-
getPlugin(pluginId) {
|
|
1162
|
-
const plugin = this.plugins.get(pluginId);
|
|
1163
|
-
if (!plugin) {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
return plugin;
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Get a plugin that provides a specific capability
|
|
1170
|
-
* @param capability The capability to get a provider for
|
|
1171
|
-
* @returns The plugin providing the capability or null if not found
|
|
1172
|
-
*/
|
|
1173
|
-
getCapabilityProvider(capability) {
|
|
1174
|
-
const pluginId = this.capabilities.get(capability);
|
|
1175
|
-
if (!pluginId) {
|
|
1176
|
-
return null;
|
|
1177
|
-
}
|
|
1178
|
-
return this.getPlugin(pluginId);
|
|
1179
|
-
}
|
|
1180
|
-
/**
|
|
1181
|
-
* Check if a capability is available
|
|
1182
|
-
*/
|
|
1183
|
-
hasCapability(capability) {
|
|
1184
|
-
return this.capabilities.has(capability);
|
|
1185
|
-
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Get all registered plugins
|
|
1188
|
-
*/
|
|
1189
|
-
getAllPlugins() {
|
|
1190
|
-
return Array.from(this.plugins.values());
|
|
1191
|
-
}
|
|
1192
|
-
/**
|
|
1193
|
-
* Get plugin status
|
|
1194
|
-
*/
|
|
1195
|
-
getPluginStatus(pluginId) {
|
|
1196
|
-
const status = this.status.get(pluginId);
|
|
1197
|
-
if (!status) {
|
|
1198
|
-
throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
|
|
1199
|
-
}
|
|
1200
|
-
return status;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Validate plugin object
|
|
1204
|
-
*/
|
|
1205
|
-
validatePlugin(plugin) {
|
|
1206
|
-
if (!plugin.id) {
|
|
1207
|
-
throw new PluginRegistrationError("Plugin must have an id");
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Validate plugin manifest
|
|
1212
|
-
*/
|
|
1213
|
-
validateManifest(manifest) {
|
|
1214
|
-
if (!manifest.id) {
|
|
1215
|
-
throw new PluginRegistrationError("Manifest must have an id");
|
|
1216
|
-
}
|
|
1217
|
-
if (!manifest.name) {
|
|
1218
|
-
throw new PluginRegistrationError("Manifest must have a name");
|
|
1219
|
-
}
|
|
1220
|
-
if (!manifest.version) {
|
|
1221
|
-
throw new PluginRegistrationError("Manifest must have a version");
|
|
1222
|
-
}
|
|
1223
|
-
if (!Array.isArray(manifest.provides)) {
|
|
1224
|
-
throw new PluginRegistrationError("Manifest must have a provides array");
|
|
1225
|
-
}
|
|
1226
|
-
if (!Array.isArray(manifest.requires)) {
|
|
1227
|
-
throw new PluginRegistrationError("Manifest must have a requires array");
|
|
1228
|
-
}
|
|
1229
|
-
if (!Array.isArray(manifest.optional)) {
|
|
1230
|
-
throw new PluginRegistrationError("Manifest must have an optional array");
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
isDestroyed() {
|
|
1234
|
-
return this.destroyed;
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* DESTROY EVERYTHING – waits for any ongoing initialise(), once *
|
|
1238
|
-
*/
|
|
1239
|
-
async destroy() {
|
|
1240
|
-
var _a;
|
|
1241
|
-
if (this.destroyed) throw new PluginRegistrationError("Registry has already been destroyed");
|
|
1242
|
-
this.destroyed = true;
|
|
1243
|
-
try {
|
|
1244
|
-
await this.initPromise;
|
|
1245
|
-
} catch {
|
|
1246
|
-
}
|
|
1247
|
-
for (const plugin of Array.from(this.plugins.values()).reverse()) {
|
|
1248
|
-
await ((_a = plugin.destroy) == null ? void 0 : _a.call(plugin));
|
|
1249
|
-
}
|
|
1250
|
-
this.store.destroy();
|
|
1251
|
-
this.plugins.clear();
|
|
1252
|
-
this.manifests.clear();
|
|
1253
|
-
this.capabilities.clear();
|
|
1254
|
-
this.status.clear();
|
|
1255
|
-
this.pendingRegistrations.length = 0;
|
|
1256
|
-
this.processingRegistrations.length = 0;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
function createPluginRegistration(pluginPackage, config) {
|
|
1260
|
-
return {
|
|
1261
|
-
package: pluginPackage,
|
|
1262
|
-
config
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
function hasAutoMountElements(pkg) {
|
|
1266
|
-
return "autoMountElements" in pkg && typeof pkg.autoMountElements === "function";
|
|
1267
|
-
}
|
|
1268
|
-
class BasePlugin {
|
|
1269
|
-
constructor(id, registry) {
|
|
1270
|
-
this.id = id;
|
|
1271
|
-
this.registry = registry;
|
|
1272
|
-
this.cooldownActions = {};
|
|
1273
|
-
this.debouncedTimeouts = {};
|
|
1274
|
-
this.unsubscribeFromState = null;
|
|
1275
|
-
this.unsubscribeFromCoreStore = null;
|
|
1276
|
-
this.unsubscribeFromStartLoadingDocument = null;
|
|
1277
|
-
this.unsubscribeFromSetDocumentLoaded = null;
|
|
1278
|
-
this.unsubscribeFromCloseDocument = null;
|
|
1279
|
-
this.unsubscribeFromSetScale = null;
|
|
1280
|
-
this.unsubscribeFromSetRotation = null;
|
|
1281
|
-
if (id !== this.constructor.id) {
|
|
1282
|
-
throw new Error(
|
|
1283
|
-
`Plugin ID mismatch: ${id} !== ${this.constructor.id}`
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
this.engine = this.registry.getEngine();
|
|
1287
|
-
this.logger = this.registry.getLogger();
|
|
1288
|
-
this.coreStore = this.registry.getStore();
|
|
1289
|
-
this.pluginStore = this.coreStore.getPluginStore(this.id);
|
|
1290
|
-
this.unsubscribeFromState = this.pluginStore.subscribeToState((action, newState, oldState) => {
|
|
1291
|
-
this.onStoreUpdated(oldState, newState);
|
|
1292
|
-
});
|
|
1293
|
-
this.unsubscribeFromCoreStore = this.coreStore.subscribe((action, newState, oldState) => {
|
|
1294
|
-
this.onCoreStoreUpdated(oldState, newState);
|
|
1295
|
-
if (newState.core.activeDocumentId !== oldState.core.activeDocumentId) {
|
|
1296
|
-
this.onActiveDocumentChanged(
|
|
1297
|
-
oldState.core.activeDocumentId,
|
|
1298
|
-
newState.core.activeDocumentId
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1302
|
-
this.unsubscribeFromStartLoadingDocument = this.coreStore.onAction(
|
|
1303
|
-
START_LOADING_DOCUMENT,
|
|
1304
|
-
(action) => {
|
|
1305
|
-
this.onDocumentLoadingStarted(action.payload.documentId);
|
|
1306
|
-
}
|
|
1307
|
-
);
|
|
1308
|
-
this.unsubscribeFromSetDocumentLoaded = this.coreStore.onAction(
|
|
1309
|
-
SET_DOCUMENT_LOADED,
|
|
1310
|
-
(action) => {
|
|
1311
|
-
this.onDocumentLoaded(action.payload.documentId);
|
|
1312
|
-
}
|
|
1313
|
-
);
|
|
1314
|
-
this.unsubscribeFromCloseDocument = this.coreStore.onAction(CLOSE_DOCUMENT, (action) => {
|
|
1315
|
-
this.onDocumentClosed(action.payload.documentId);
|
|
1316
|
-
});
|
|
1317
|
-
this.unsubscribeFromSetScale = this.coreStore.onAction(SET_SCALE, (action, state) => {
|
|
1318
|
-
const targetId = action.payload.documentId ?? state.core.activeDocumentId;
|
|
1319
|
-
if (targetId) {
|
|
1320
|
-
this.onScaleChanged(targetId, action.payload.scale);
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
this.unsubscribeFromSetRotation = this.coreStore.onAction(SET_ROTATION, (action, state) => {
|
|
1324
|
-
const targetId = action.payload.documentId ?? state.core.activeDocumentId;
|
|
1325
|
-
if (targetId) {
|
|
1326
|
-
this.onRotationChanged(targetId, action.payload.rotation);
|
|
1327
|
-
}
|
|
1328
|
-
});
|
|
1329
|
-
this.readyPromise = new Promise((resolve) => {
|
|
1330
|
-
this.readyResolve = resolve;
|
|
1331
|
-
});
|
|
1332
|
-
this.readyResolve();
|
|
1333
|
-
}
|
|
1334
|
-
provides() {
|
|
1335
|
-
if (!this._capability) {
|
|
1336
|
-
const cap = this.buildCapability();
|
|
1337
|
-
this._capability = Object.freeze(cap);
|
|
1338
|
-
}
|
|
1339
|
-
return this._capability;
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Get a copy of the current state
|
|
1343
|
-
*/
|
|
1344
|
-
get state() {
|
|
1345
|
-
return this.pluginStore.getState();
|
|
1346
|
-
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Get a copy of the current core state
|
|
1349
|
-
*/
|
|
1350
|
-
get coreState() {
|
|
1351
|
-
return this.coreStore.getState();
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* @deprecated use `this.state` Get a copy of the current state
|
|
1355
|
-
*/
|
|
1356
|
-
getState() {
|
|
1357
|
-
return this.pluginStore.getState();
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* @deprecated use `this.coreState` Get a copy of the current core state
|
|
1361
|
-
*/
|
|
1362
|
-
getCoreState() {
|
|
1363
|
-
return this.coreStore.getState();
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Core Dispatch
|
|
1367
|
-
*/
|
|
1368
|
-
dispatchCoreAction(action) {
|
|
1369
|
-
return this.coreStore.dispatchToCore(action);
|
|
1370
|
-
}
|
|
1371
|
-
/**
|
|
1372
|
-
* Dispatch an action to all plugins
|
|
1373
|
-
*/
|
|
1374
|
-
dispatchToAllPlugins(action) {
|
|
1375
|
-
return this.coreStore.dispatch(action);
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* Dispatch an action
|
|
1379
|
-
*/
|
|
1380
|
-
dispatch(action) {
|
|
1381
|
-
return this.pluginStore.dispatch(action);
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Dispatch an action with a cooldown to prevent rapid repeated calls
|
|
1385
|
-
* This executes immediately if cooldown has expired, then blocks subsequent calls
|
|
1386
|
-
* @param action The action to dispatch
|
|
1387
|
-
* @param cooldownTime Time in ms for cooldown (default: 100ms)
|
|
1388
|
-
* @returns boolean indicating whether the action was dispatched or blocked
|
|
1389
|
-
*/
|
|
1390
|
-
cooldownDispatch(action, cooldownTime = 100) {
|
|
1391
|
-
const now = Date.now();
|
|
1392
|
-
const lastActionTime = this.cooldownActions[action.type] || 0;
|
|
1393
|
-
if (now - lastActionTime >= cooldownTime) {
|
|
1394
|
-
this.cooldownActions[action.type] = now;
|
|
1395
|
-
this.dispatch(action);
|
|
1396
|
-
return true;
|
|
1397
|
-
}
|
|
1398
|
-
return false;
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Dispatch an action with true debouncing - waits for the delay after the last call
|
|
1402
|
-
* Each new call resets the timer. Action only executes after no calls for the specified time.
|
|
1403
|
-
* @param action The action to dispatch
|
|
1404
|
-
* @param debounceTime Time in ms to wait after the last call
|
|
1405
|
-
*/
|
|
1406
|
-
debouncedDispatch(action, debounceTime = 100) {
|
|
1407
|
-
const actionKey = action.type;
|
|
1408
|
-
if (this.debouncedTimeouts[actionKey]) {
|
|
1409
|
-
clearTimeout(this.debouncedTimeouts[actionKey]);
|
|
1410
|
-
}
|
|
1411
|
-
this.debouncedTimeouts[actionKey] = setTimeout(() => {
|
|
1412
|
-
this.dispatch(action);
|
|
1413
|
-
delete this.debouncedTimeouts[actionKey];
|
|
1414
|
-
}, debounceTime);
|
|
1415
|
-
}
|
|
1416
|
-
/**
|
|
1417
|
-
* Cancel a pending debounced action
|
|
1418
|
-
* @param actionType The action type to cancel
|
|
1419
|
-
*/
|
|
1420
|
-
cancelDebouncedDispatch(actionType) {
|
|
1421
|
-
if (this.debouncedTimeouts[actionType]) {
|
|
1422
|
-
clearTimeout(this.debouncedTimeouts[actionType]);
|
|
1423
|
-
delete this.debouncedTimeouts[actionType];
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Subscribe to state changes
|
|
1428
|
-
*/
|
|
1429
|
-
subscribe(listener) {
|
|
1430
|
-
return this.pluginStore.subscribeToState(listener);
|
|
1431
|
-
}
|
|
1432
|
-
/**
|
|
1433
|
-
* Subscribe to core store changes
|
|
1434
|
-
*/
|
|
1435
|
-
subscribeToCoreStore(listener) {
|
|
1436
|
-
return this.coreStore.subscribe(listener);
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Called when the plugin store state is updated
|
|
1440
|
-
* @param oldState Previous state
|
|
1441
|
-
* @param newState New state
|
|
1442
|
-
*/
|
|
1443
|
-
onStoreUpdated(oldState, newState) {
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Called when the core store state is updated
|
|
1447
|
-
* @param oldState Previous state
|
|
1448
|
-
* @param newState New state
|
|
1449
|
-
*/
|
|
1450
|
-
onCoreStoreUpdated(oldState, newState) {
|
|
1451
|
-
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Called when a document is opened
|
|
1454
|
-
* Override to initialize per-document state
|
|
1455
|
-
* @param documentId The ID of the document that was opened
|
|
1456
|
-
*/
|
|
1457
|
-
onDocumentLoadingStarted(documentId) {
|
|
1458
|
-
}
|
|
1459
|
-
/**
|
|
1460
|
-
* Called when a document is loaded
|
|
1461
|
-
* @param documentId The ID of the document that is loaded
|
|
1462
|
-
*/
|
|
1463
|
-
onDocumentLoaded(documentId) {
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Called when a document is closed
|
|
1467
|
-
* Override to cleanup per-document state
|
|
1468
|
-
* @param documentId The ID of the document that was closed
|
|
1469
|
-
*/
|
|
1470
|
-
onDocumentClosed(documentId) {
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* Called when the active document changes
|
|
1474
|
-
* @param previousId The ID of the previous active document
|
|
1475
|
-
* @param currentId The ID of the new active document
|
|
1476
|
-
*/
|
|
1477
|
-
onActiveDocumentChanged(previousId, currentId) {
|
|
1478
|
-
}
|
|
1479
|
-
onScaleChanged(documentId, scale) {
|
|
1480
|
-
}
|
|
1481
|
-
onRotationChanged(documentId, rotation) {
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Cleanup method to be called when plugin is being destroyed
|
|
1485
|
-
*/
|
|
1486
|
-
destroy() {
|
|
1487
|
-
Object.values(this.debouncedTimeouts).forEach((timeout) => {
|
|
1488
|
-
clearTimeout(timeout);
|
|
1489
|
-
});
|
|
1490
|
-
this.debouncedTimeouts = {};
|
|
1491
|
-
if (this.unsubscribeFromState) {
|
|
1492
|
-
this.unsubscribeFromState();
|
|
1493
|
-
this.unsubscribeFromState = null;
|
|
1494
|
-
}
|
|
1495
|
-
if (this.unsubscribeFromCoreStore) {
|
|
1496
|
-
this.unsubscribeFromCoreStore();
|
|
1497
|
-
this.unsubscribeFromCoreStore = null;
|
|
1498
|
-
}
|
|
1499
|
-
if (this.unsubscribeFromStartLoadingDocument) {
|
|
1500
|
-
this.unsubscribeFromStartLoadingDocument();
|
|
1501
|
-
this.unsubscribeFromStartLoadingDocument = null;
|
|
1502
|
-
}
|
|
1503
|
-
if (this.unsubscribeFromSetDocumentLoaded) {
|
|
1504
|
-
this.unsubscribeFromSetDocumentLoaded();
|
|
1505
|
-
this.unsubscribeFromSetDocumentLoaded = null;
|
|
1506
|
-
}
|
|
1507
|
-
if (this.unsubscribeFromCloseDocument) {
|
|
1508
|
-
this.unsubscribeFromCloseDocument();
|
|
1509
|
-
this.unsubscribeFromCloseDocument = null;
|
|
1510
|
-
}
|
|
1511
|
-
if (this.unsubscribeFromSetScale) {
|
|
1512
|
-
this.unsubscribeFromSetScale();
|
|
1513
|
-
this.unsubscribeFromSetScale = null;
|
|
1514
|
-
}
|
|
1515
|
-
if (this.unsubscribeFromSetRotation) {
|
|
1516
|
-
this.unsubscribeFromSetRotation();
|
|
1517
|
-
this.unsubscribeFromSetRotation = null;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Returns a promise that resolves when the plugin is ready
|
|
1522
|
-
*/
|
|
1523
|
-
ready() {
|
|
1524
|
-
return this.readyPromise;
|
|
1525
|
-
}
|
|
1526
|
-
/**
|
|
1527
|
-
* Mark the plugin as ready
|
|
1528
|
-
*/
|
|
1529
|
-
markReady() {
|
|
1530
|
-
this.readyResolve();
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Reset the ready state (useful for plugins that need to reinitialize)
|
|
1534
|
-
*/
|
|
1535
|
-
resetReady() {
|
|
1536
|
-
this.readyPromise = new Promise((resolve) => {
|
|
1537
|
-
this.readyResolve = resolve;
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Get the active document ID
|
|
1542
|
-
* @throws Error if no active document exists
|
|
1543
|
-
*/
|
|
1544
|
-
getActiveDocumentId() {
|
|
1545
|
-
const id = this.coreState.core.activeDocumentId;
|
|
1546
|
-
if (!id) {
|
|
1547
|
-
throw new Error("No active document");
|
|
1548
|
-
}
|
|
1549
|
-
return id;
|
|
1550
|
-
}
|
|
1551
|
-
/**
|
|
1552
|
-
* Get the active document ID or null if none exists
|
|
1553
|
-
*/
|
|
1554
|
-
getActiveDocumentIdOrNull() {
|
|
1555
|
-
return this.coreState.core.activeDocumentId;
|
|
1556
|
-
}
|
|
1557
|
-
/**
|
|
1558
|
-
* Get core document state for a specific document
|
|
1559
|
-
* @param documentId Document ID (optional, defaults to active document)
|
|
1560
|
-
* @returns Document state or null if not found
|
|
1561
|
-
*/
|
|
1562
|
-
getCoreDocument(documentId) {
|
|
1563
|
-
const id = documentId ?? this.getActiveDocumentIdOrNull();
|
|
1564
|
-
if (!id) return null;
|
|
1565
|
-
return this.coreState.core.documents[id] ?? null;
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Get core document state for a specific document
|
|
1569
|
-
* @param documentId Document ID (optional, defaults to active document)
|
|
1570
|
-
* @throws Error if document not found
|
|
1571
|
-
*/
|
|
1572
|
-
getCoreDocumentOrThrow(documentId) {
|
|
1573
|
-
const doc = this.getCoreDocument(documentId);
|
|
1574
|
-
if (!doc) {
|
|
1575
|
-
throw new Error(`Document not found: ${documentId ?? "active"}`);
|
|
1576
|
-
}
|
|
1577
|
-
return doc;
|
|
1578
|
-
}
|
|
1579
|
-
// ─────────────────────────────────────────────────────────
|
|
1580
|
-
// Permission Helpers
|
|
1581
|
-
// ─────────────────────────────────────────────────────────
|
|
1582
|
-
/**
|
|
1583
|
-
* Get the effective permission flags for a document.
|
|
1584
|
-
* Applies layered resolution: per-document override → global override → PDF permission.
|
|
1585
|
-
* Returns AllowAll if document not found.
|
|
1586
|
-
* @param documentId Document ID (optional, defaults to active document)
|
|
1587
|
-
*/
|
|
1588
|
-
getDocumentPermissions(documentId) {
|
|
1589
|
-
const docId = documentId ?? this.coreState.core.activeDocumentId;
|
|
1590
|
-
if (!docId) return PdfPermissionFlag.AllowAll;
|
|
1591
|
-
return getEffectivePermissions(this.coreState.core, docId);
|
|
1592
|
-
}
|
|
1593
|
-
/**
|
|
1594
|
-
* Check if a document has the required permissions (returns boolean).
|
|
1595
|
-
* Applies layered resolution: per-document override → global override → PDF permission.
|
|
1596
|
-
* Useful for conditional UI logic.
|
|
1597
|
-
* @param documentId Document ID (optional, defaults to active document)
|
|
1598
|
-
* @param flags Permission flags to check
|
|
1599
|
-
*/
|
|
1600
|
-
checkPermission(documentId, ...flags) {
|
|
1601
|
-
const docId = documentId ?? this.coreState.core.activeDocumentId;
|
|
1602
|
-
if (!docId) return true;
|
|
1603
|
-
return flags.every((flag) => getEffectivePermission(this.coreState.core, docId, flag));
|
|
1604
|
-
}
|
|
1605
|
-
/**
|
|
1606
|
-
* Assert that a document has the required permissions.
|
|
1607
|
-
* Applies layered resolution: per-document override → global override → PDF permission.
|
|
1608
|
-
* Throws PermissionDeniedError if any flag is missing.
|
|
1609
|
-
* @param documentId Document ID (optional, defaults to active document)
|
|
1610
|
-
* @param flags Permission flags to require
|
|
1611
|
-
*/
|
|
1612
|
-
requirePermission(documentId, ...flags) {
|
|
1613
|
-
const docId = documentId ?? this.coreState.core.activeDocumentId;
|
|
1614
|
-
if (!docId) return;
|
|
1615
|
-
const missingFlags = [];
|
|
1616
|
-
for (const flag of flags) {
|
|
1617
|
-
if (!getEffectivePermission(this.coreState.core, docId, flag)) {
|
|
1618
|
-
missingFlags.push(flag);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
if (missingFlags.length > 0) {
|
|
1622
|
-
const effectivePermissions = getEffectivePermissions(this.coreState.core, docId);
|
|
1623
|
-
throw new PermissionDeniedError(missingFlags, effectivePermissions);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
class EventControl {
|
|
1628
|
-
constructor(handler, options) {
|
|
1629
|
-
this.handler = handler;
|
|
1630
|
-
this.options = options;
|
|
1631
|
-
this.lastRun = 0;
|
|
1632
|
-
this.handle = (data) => {
|
|
1633
|
-
if (this.options.mode === "debounce") {
|
|
1634
|
-
this.debounce(data);
|
|
1635
|
-
} else {
|
|
1636
|
-
this.throttle(data);
|
|
1637
|
-
}
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
debounce(data) {
|
|
1641
|
-
if (this.timeoutId) {
|
|
1642
|
-
window.clearTimeout(this.timeoutId);
|
|
1643
|
-
}
|
|
1644
|
-
this.timeoutId = window.setTimeout(() => {
|
|
1645
|
-
this.handler(data);
|
|
1646
|
-
this.timeoutId = void 0;
|
|
1647
|
-
}, this.options.wait);
|
|
1648
|
-
}
|
|
1649
|
-
throttle(data) {
|
|
1650
|
-
if (this.options.mode === "debounce") return;
|
|
1651
|
-
const now = Date.now();
|
|
1652
|
-
const throttleMode = this.options.throttleMode || "leading-trailing";
|
|
1653
|
-
if (now - this.lastRun >= this.options.wait) {
|
|
1654
|
-
if (throttleMode === "leading-trailing") {
|
|
1655
|
-
this.handler(data);
|
|
1656
|
-
}
|
|
1657
|
-
this.lastRun = now;
|
|
1658
|
-
}
|
|
1659
|
-
if (this.timeoutId) {
|
|
1660
|
-
window.clearTimeout(this.timeoutId);
|
|
1661
|
-
}
|
|
1662
|
-
this.timeoutId = window.setTimeout(
|
|
1663
|
-
() => {
|
|
1664
|
-
this.handler(data);
|
|
1665
|
-
this.lastRun = Date.now();
|
|
1666
|
-
this.timeoutId = void 0;
|
|
1667
|
-
},
|
|
1668
|
-
this.options.wait - (now - this.lastRun)
|
|
1669
|
-
);
|
|
1670
|
-
}
|
|
1671
|
-
destroy() {
|
|
1672
|
-
if (this.timeoutId) {
|
|
1673
|
-
window.clearTimeout(this.timeoutId);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
class KeyedEventControl {
|
|
1678
|
-
constructor(handler, options) {
|
|
1679
|
-
this.handler = handler;
|
|
1680
|
-
this.options = options;
|
|
1681
|
-
this.controls = /* @__PURE__ */ new Map();
|
|
1682
|
-
this.handle = (data) => {
|
|
1683
|
-
const key = String(this.options.keyExtractor(data));
|
|
1684
|
-
let control = this.controls.get(key);
|
|
1685
|
-
if (!control) {
|
|
1686
|
-
control = new EventControl(this.handler, this.baseOptions);
|
|
1687
|
-
this.controls.set(key, control);
|
|
1688
|
-
}
|
|
1689
|
-
control.handle(data);
|
|
1690
|
-
};
|
|
1691
|
-
this.baseOptions = {
|
|
1692
|
-
mode: options.mode,
|
|
1693
|
-
wait: options.wait,
|
|
1694
|
-
...options.mode === "throttle" && "throttleMode" in options ? { throttleMode: options.throttleMode } : {}
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
destroy() {
|
|
1698
|
-
for (const control of this.controls.values()) {
|
|
1699
|
-
control.destroy();
|
|
1700
|
-
}
|
|
1701
|
-
this.controls.clear();
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
function isKeyedOptions(options) {
|
|
1705
|
-
return "keyExtractor" in options;
|
|
1706
|
-
}
|
|
1707
|
-
function clamp(value, min, max) {
|
|
1708
|
-
return value < min ? min : value > max ? max : value;
|
|
1709
|
-
}
|
|
1710
|
-
function arePropsEqual(a, b, visited) {
|
|
1711
|
-
if (a === b) {
|
|
1712
|
-
return true;
|
|
1713
|
-
}
|
|
1714
|
-
if (a == null || b == null) {
|
|
1715
|
-
return a === b;
|
|
1716
|
-
}
|
|
1717
|
-
const aType = typeof a;
|
|
1718
|
-
const bType = typeof b;
|
|
1719
|
-
if (aType !== bType) return false;
|
|
1720
|
-
if (aType === "object") {
|
|
1721
|
-
if (!visited) visited = /* @__PURE__ */ new Set();
|
|
1722
|
-
const pairId = getPairId(a, b);
|
|
1723
|
-
if (visited.has(pairId)) {
|
|
1724
|
-
return true;
|
|
1725
|
-
}
|
|
1726
|
-
visited.add(pairId);
|
|
1727
|
-
const aIsArray = Array.isArray(a);
|
|
1728
|
-
const bIsArray = Array.isArray(b);
|
|
1729
|
-
if (aIsArray && bIsArray) {
|
|
1730
|
-
return arraysEqualUnordered(a, b, visited);
|
|
1731
|
-
} else if (!aIsArray && !bIsArray) {
|
|
1732
|
-
return objectsEqual(a, b, visited);
|
|
1733
|
-
} else {
|
|
1734
|
-
return false;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
return false;
|
|
1738
|
-
}
|
|
1739
|
-
function getPairId(a, b) {
|
|
1740
|
-
return `${objectId(a)}__${objectId(b)}`;
|
|
1741
|
-
}
|
|
1742
|
-
let objectIdCounter = 0;
|
|
1743
|
-
const objectIds = /* @__PURE__ */ new WeakMap();
|
|
1744
|
-
function objectId(obj) {
|
|
1745
|
-
if (!objectIds.has(obj)) {
|
|
1746
|
-
objectIds.set(obj, ++objectIdCounter);
|
|
1747
|
-
}
|
|
1748
|
-
return objectIds.get(obj);
|
|
1749
|
-
}
|
|
1750
|
-
function arraysEqualUnordered(a, b, visited) {
|
|
1751
|
-
if (a.length !== b.length) return false;
|
|
1752
|
-
const used = new Array(b.length).fill(false);
|
|
1753
|
-
outer: for (let i = 0; i < a.length; i++) {
|
|
1754
|
-
const elemA = a[i];
|
|
1755
|
-
for (let j = 0; j < b.length; j++) {
|
|
1756
|
-
if (used[j]) continue;
|
|
1757
|
-
if (arePropsEqual(elemA, b[j], visited)) {
|
|
1758
|
-
used[j] = true;
|
|
1759
|
-
continue outer;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
return false;
|
|
1763
|
-
}
|
|
1764
|
-
return true;
|
|
1765
|
-
}
|
|
1766
|
-
function objectsEqual(a, b, visited) {
|
|
1767
|
-
const aKeys = Object.keys(a).sort();
|
|
1768
|
-
const bKeys = Object.keys(b).sort();
|
|
1769
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
1770
|
-
for (let i = 0; i < aKeys.length; i++) {
|
|
1771
|
-
if (aKeys[i] !== bKeys[i]) return false;
|
|
1772
|
-
}
|
|
1773
|
-
for (const key of aKeys) {
|
|
1774
|
-
const valA = a[key];
|
|
1775
|
-
const valB = b[key];
|
|
1776
|
-
if (!arePropsEqual(valA, valB, visited)) {
|
|
1777
|
-
return false;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
return true;
|
|
1781
|
-
}
|
|
1782
|
-
function createEmitter() {
|
|
1783
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1784
|
-
const on = (l) => {
|
|
1785
|
-
listeners.add(l);
|
|
1786
|
-
return () => listeners.delete(l);
|
|
1787
|
-
};
|
|
1788
|
-
return {
|
|
1789
|
-
emit: (v = void 0) => listeners.forEach((l) => l(v)),
|
|
1790
|
-
on,
|
|
1791
|
-
off: (l) => listeners.delete(l),
|
|
1792
|
-
clear: () => listeners.clear()
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
function createBehaviorEmitter(initial, equality = arePropsEqual) {
|
|
1796
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1797
|
-
const proxyMap = /* @__PURE__ */ new Map();
|
|
1798
|
-
let _value = initial;
|
|
1799
|
-
const notify = (v) => listeners.forEach((l) => l(v));
|
|
1800
|
-
const baseOn = (listener, options) => {
|
|
1801
|
-
let realListener = listener;
|
|
1802
|
-
let destroy = () => {
|
|
1803
|
-
};
|
|
1804
|
-
if (options) {
|
|
1805
|
-
if (isKeyedOptions(options)) {
|
|
1806
|
-
const ctl = new KeyedEventControl(listener, options);
|
|
1807
|
-
realListener = ctl.handle;
|
|
1808
|
-
destroy = () => ctl.destroy();
|
|
1809
|
-
} else {
|
|
1810
|
-
const ctl = new EventControl(listener, options);
|
|
1811
|
-
realListener = ctl.handle;
|
|
1812
|
-
destroy = () => ctl.destroy();
|
|
1813
|
-
}
|
|
1814
|
-
proxyMap.set(listener, { wrapped: realListener, destroy });
|
|
1815
|
-
}
|
|
1816
|
-
if (_value !== void 0) realListener(_value);
|
|
1817
|
-
listeners.add(realListener);
|
|
1818
|
-
return () => {
|
|
1819
|
-
listeners.delete(realListener);
|
|
1820
|
-
destroy();
|
|
1821
|
-
proxyMap.delete(listener);
|
|
1822
|
-
};
|
|
1823
|
-
};
|
|
1824
|
-
return {
|
|
1825
|
-
/* emitter behaviour ---------------------------------------- */
|
|
1826
|
-
get value() {
|
|
1827
|
-
return _value;
|
|
1828
|
-
},
|
|
1829
|
-
emit(v = void 0) {
|
|
1830
|
-
if (_value === void 0 || !equality(_value, v)) {
|
|
1831
|
-
_value = v;
|
|
1832
|
-
notify(v);
|
|
1833
|
-
}
|
|
1834
|
-
},
|
|
1835
|
-
on: baseOn,
|
|
1836
|
-
off(listener) {
|
|
1837
|
-
const proxy = proxyMap.get(listener);
|
|
1838
|
-
if (proxy) {
|
|
1839
|
-
listeners.delete(proxy.wrapped);
|
|
1840
|
-
proxy.destroy();
|
|
1841
|
-
proxyMap.delete(listener);
|
|
1842
|
-
} else {
|
|
1843
|
-
listeners.delete(listener);
|
|
1844
|
-
}
|
|
1845
|
-
},
|
|
1846
|
-
clear() {
|
|
1847
|
-
listeners.clear();
|
|
1848
|
-
proxyMap.forEach((p) => p.destroy());
|
|
1849
|
-
proxyMap.clear();
|
|
1850
|
-
},
|
|
1851
|
-
/* derived hook --------------------------------------------- */
|
|
1852
|
-
select(selector, eq = arePropsEqual) {
|
|
1853
|
-
return (listener, options) => {
|
|
1854
|
-
let prev;
|
|
1855
|
-
if (_value !== void 0) {
|
|
1856
|
-
const mapped = selector(_value);
|
|
1857
|
-
prev = mapped;
|
|
1858
|
-
listener(mapped);
|
|
1859
|
-
}
|
|
1860
|
-
return baseOn(
|
|
1861
|
-
(next) => {
|
|
1862
|
-
const mapped = selector(next);
|
|
1863
|
-
if (prev === void 0 || !eq(prev, mapped)) {
|
|
1864
|
-
prev = mapped;
|
|
1865
|
-
listener(mapped);
|
|
1866
|
-
}
|
|
1867
|
-
},
|
|
1868
|
-
options
|
|
1869
|
-
);
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
function createScopedEmitter(toGlobalEvent, options) {
|
|
1875
|
-
const shouldCache = (options == null ? void 0 : options.cache) ?? true;
|
|
1876
|
-
const equality = (options == null ? void 0 : options.equality) ?? arePropsEqual;
|
|
1877
|
-
const scopeCaches = /* @__PURE__ */ new Map();
|
|
1878
|
-
const scopeListeners = /* @__PURE__ */ new Map();
|
|
1879
|
-
const scopeProxyMaps = /* @__PURE__ */ new Map();
|
|
1880
|
-
const globalListeners = /* @__PURE__ */ new Set();
|
|
1881
|
-
const globalProxyMap = /* @__PURE__ */ new Map();
|
|
1882
|
-
const normalizeKey = (key) => String(key);
|
|
1883
|
-
const getOrCreateListeners = (key) => {
|
|
1884
|
-
let listeners = scopeListeners.get(key);
|
|
1885
|
-
if (!listeners) {
|
|
1886
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1887
|
-
scopeListeners.set(key, listeners);
|
|
1888
|
-
}
|
|
1889
|
-
return listeners;
|
|
1890
|
-
};
|
|
1891
|
-
const getOrCreateProxyMap = (key) => {
|
|
1892
|
-
let proxyMap = scopeProxyMaps.get(key);
|
|
1893
|
-
if (!proxyMap) {
|
|
1894
|
-
proxyMap = /* @__PURE__ */ new Map();
|
|
1895
|
-
scopeProxyMaps.set(key, proxyMap);
|
|
1896
|
-
}
|
|
1897
|
-
return proxyMap;
|
|
1898
|
-
};
|
|
1899
|
-
const onGlobal = (listener, options2) => {
|
|
1900
|
-
let realListener = listener;
|
|
1901
|
-
let destroy = () => {
|
|
1902
|
-
};
|
|
1903
|
-
if (options2) {
|
|
1904
|
-
if (isKeyedOptions(options2)) {
|
|
1905
|
-
const ctl = new KeyedEventControl(listener, options2);
|
|
1906
|
-
realListener = ctl.handle;
|
|
1907
|
-
destroy = () => ctl.destroy();
|
|
1908
|
-
} else {
|
|
1909
|
-
const ctl = new EventControl(listener, options2);
|
|
1910
|
-
realListener = ctl.handle;
|
|
1911
|
-
destroy = () => ctl.destroy();
|
|
1912
|
-
}
|
|
1913
|
-
globalProxyMap.set(listener, { wrapped: realListener, destroy });
|
|
1914
|
-
}
|
|
1915
|
-
globalListeners.add(realListener);
|
|
1916
|
-
return () => {
|
|
1917
|
-
globalListeners.delete(realListener);
|
|
1918
|
-
destroy();
|
|
1919
|
-
globalProxyMap.delete(listener);
|
|
1920
|
-
};
|
|
1921
|
-
};
|
|
1922
|
-
return {
|
|
1923
|
-
emit(key, data) {
|
|
1924
|
-
const normalizedKey = normalizeKey(key);
|
|
1925
|
-
if (shouldCache) {
|
|
1926
|
-
const cached = scopeCaches.get(normalizedKey);
|
|
1927
|
-
if (cached !== void 0 && equality(cached, data)) {
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
1930
|
-
scopeCaches.set(normalizedKey, data);
|
|
1931
|
-
}
|
|
1932
|
-
const listeners = scopeListeners.get(normalizedKey);
|
|
1933
|
-
if (listeners) {
|
|
1934
|
-
listeners.forEach((l) => l(data));
|
|
1935
|
-
}
|
|
1936
|
-
const globalEvent = toGlobalEvent(key, data);
|
|
1937
|
-
globalListeners.forEach((l) => l(globalEvent));
|
|
1938
|
-
},
|
|
1939
|
-
forScope(key) {
|
|
1940
|
-
const normalizedKey = normalizeKey(key);
|
|
1941
|
-
return (listener, options2) => {
|
|
1942
|
-
const listeners = getOrCreateListeners(normalizedKey);
|
|
1943
|
-
const proxyMap = getOrCreateProxyMap(normalizedKey);
|
|
1944
|
-
let realListener = listener;
|
|
1945
|
-
let destroy = () => {
|
|
1946
|
-
};
|
|
1947
|
-
if (options2) {
|
|
1948
|
-
if (isKeyedOptions(options2)) {
|
|
1949
|
-
const ctl = new KeyedEventControl(listener, options2);
|
|
1950
|
-
realListener = ctl.handle;
|
|
1951
|
-
destroy = () => ctl.destroy();
|
|
1952
|
-
} else {
|
|
1953
|
-
const ctl = new EventControl(listener, options2);
|
|
1954
|
-
realListener = ctl.handle;
|
|
1955
|
-
destroy = () => ctl.destroy();
|
|
1956
|
-
}
|
|
1957
|
-
proxyMap.set(listener, { wrapped: realListener, destroy });
|
|
1958
|
-
}
|
|
1959
|
-
if (shouldCache) {
|
|
1960
|
-
const cached = scopeCaches.get(normalizedKey);
|
|
1961
|
-
if (cached !== void 0) {
|
|
1962
|
-
realListener(cached);
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
listeners.add(realListener);
|
|
1966
|
-
return () => {
|
|
1967
|
-
listeners.delete(realListener);
|
|
1968
|
-
destroy();
|
|
1969
|
-
proxyMap.delete(listener);
|
|
1970
|
-
if (listeners.size === 0) {
|
|
1971
|
-
scopeListeners.delete(normalizedKey);
|
|
1972
|
-
}
|
|
1973
|
-
if (proxyMap.size === 0) {
|
|
1974
|
-
scopeProxyMaps.delete(normalizedKey);
|
|
1975
|
-
}
|
|
1976
|
-
};
|
|
1977
|
-
};
|
|
1978
|
-
},
|
|
1979
|
-
onGlobal,
|
|
1980
|
-
getValue(key) {
|
|
1981
|
-
return shouldCache ? scopeCaches.get(normalizeKey(key)) : void 0;
|
|
1982
|
-
},
|
|
1983
|
-
getScopes() {
|
|
1984
|
-
if (shouldCache) {
|
|
1985
|
-
return Array.from(scopeCaches.keys());
|
|
1986
|
-
}
|
|
1987
|
-
return Array.from(scopeListeners.keys());
|
|
1988
|
-
},
|
|
1989
|
-
clearScope(key) {
|
|
1990
|
-
const normalizedKey = normalizeKey(key);
|
|
1991
|
-
if (shouldCache) {
|
|
1992
|
-
scopeCaches.delete(normalizedKey);
|
|
1993
|
-
}
|
|
1994
|
-
const listeners = scopeListeners.get(normalizedKey);
|
|
1995
|
-
if (listeners) {
|
|
1996
|
-
listeners.clear();
|
|
1997
|
-
scopeListeners.delete(normalizedKey);
|
|
1998
|
-
}
|
|
1999
|
-
const proxyMap = scopeProxyMaps.get(normalizedKey);
|
|
2000
|
-
if (proxyMap) {
|
|
2001
|
-
proxyMap.forEach((p) => p.destroy());
|
|
2002
|
-
proxyMap.clear();
|
|
2003
|
-
scopeProxyMaps.delete(normalizedKey);
|
|
2004
|
-
}
|
|
2005
|
-
},
|
|
2006
|
-
clear() {
|
|
2007
|
-
if (shouldCache) {
|
|
2008
|
-
scopeCaches.clear();
|
|
2009
|
-
}
|
|
2010
|
-
scopeListeners.forEach((set) => set.clear());
|
|
2011
|
-
scopeListeners.clear();
|
|
2012
|
-
scopeProxyMaps.forEach((map) => {
|
|
2013
|
-
map.forEach((p) => p.destroy());
|
|
2014
|
-
map.clear();
|
|
2015
|
-
});
|
|
2016
|
-
scopeProxyMaps.clear();
|
|
2017
|
-
globalListeners.clear();
|
|
2018
|
-
globalProxyMap.forEach((p) => p.destroy());
|
|
2019
|
-
globalProxyMap.clear();
|
|
2020
|
-
}
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
function enumEntries(record) {
|
|
2024
|
-
return Object.entries(record).map(([k, v]) => {
|
|
2025
|
-
const maybeNum = Number(k);
|
|
2026
|
-
const typedKey = Number.isFinite(maybeNum) && k.trim() !== "" ? maybeNum : k;
|
|
2027
|
-
return [typedKey, v];
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
class PluginPackageBuilder {
|
|
2031
|
-
constructor(basePackage) {
|
|
2032
|
-
this.autoMountElements = [];
|
|
2033
|
-
this.package = basePackage;
|
|
2034
|
-
}
|
|
2035
|
-
addUtility(component) {
|
|
2036
|
-
this.autoMountElements.push({ component, type: "utility" });
|
|
2037
|
-
return this;
|
|
2038
|
-
}
|
|
2039
|
-
addWrapper(component) {
|
|
2040
|
-
this.autoMountElements.push({ component, type: "wrapper" });
|
|
2041
|
-
return this;
|
|
2042
|
-
}
|
|
2043
|
-
build() {
|
|
2044
|
-
return {
|
|
2045
|
-
...this.package,
|
|
2046
|
-
autoMountElements: () => this.autoMountElements
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
function createPluginPackage(basePackage) {
|
|
2051
|
-
return new PluginPackageBuilder(basePackage);
|
|
2052
|
-
}
|
|
2053
|
-
export {
|
|
2054
|
-
ALL_PERMISSION_FLAGS,
|
|
2055
|
-
ALL_PERMISSION_NAMES,
|
|
2056
|
-
BasePlugin,
|
|
2057
|
-
CLOSE_DOCUMENT,
|
|
2058
|
-
CORE_ACTION_TYPES,
|
|
2059
|
-
CapabilityConflictError,
|
|
2060
|
-
CapabilityNotFoundError,
|
|
2061
|
-
CircularDependencyError,
|
|
2062
|
-
DependencyResolver,
|
|
2063
|
-
EventControl,
|
|
2064
|
-
KeyedEventControl,
|
|
2065
|
-
MOVE_DOCUMENT,
|
|
2066
|
-
PERMISSION_FLAG_TO_NAME,
|
|
2067
|
-
PERMISSION_NAME_TO_FLAG,
|
|
2068
|
-
PluginConfigurationError,
|
|
2069
|
-
PluginInitializationError,
|
|
2070
|
-
PluginNotFoundError,
|
|
2071
|
-
PluginPackageBuilder,
|
|
2072
|
-
PluginRegistrationError,
|
|
2073
|
-
PluginRegistry,
|
|
2074
|
-
REFRESH_DOCUMENT,
|
|
2075
|
-
REFRESH_PAGES,
|
|
2076
|
-
REORDER_DOCUMENTS,
|
|
2077
|
-
RETRY_LOADING_DOCUMENT,
|
|
2078
|
-
SET_ACTIVE_DOCUMENT,
|
|
2079
|
-
SET_DEFAULT_ROTATION,
|
|
2080
|
-
SET_DEFAULT_SCALE,
|
|
2081
|
-
SET_DOCUMENT_ERROR,
|
|
2082
|
-
SET_DOCUMENT_LOADED,
|
|
2083
|
-
SET_PAGES,
|
|
2084
|
-
SET_ROTATION,
|
|
2085
|
-
SET_SCALE,
|
|
2086
|
-
START_LOADING_DOCUMENT,
|
|
2087
|
-
UPDATE_DOCUMENT_LOADING_PROGRESS,
|
|
2088
|
-
UPDATE_DOCUMENT_SECURITY,
|
|
2089
|
-
arePropsEqual,
|
|
2090
|
-
clamp,
|
|
2091
|
-
closeDocument,
|
|
2092
|
-
createBehaviorEmitter,
|
|
2093
|
-
createEmitter,
|
|
2094
|
-
createPluginPackage,
|
|
2095
|
-
createPluginRegistration,
|
|
2096
|
-
createScopedEmitter,
|
|
2097
|
-
enumEntries,
|
|
2098
|
-
getActiveDocumentState,
|
|
2099
|
-
getDocumentCount,
|
|
2100
|
-
getDocumentIds,
|
|
2101
|
-
getDocumentState,
|
|
2102
|
-
getEffectivePermission,
|
|
2103
|
-
getEffectivePermissions,
|
|
2104
|
-
getPermissionOverride,
|
|
2105
|
-
hasAutoMountElements,
|
|
2106
|
-
initialCoreState,
|
|
2107
|
-
isDocumentLoaded,
|
|
2108
|
-
isKeyedOptions,
|
|
2109
|
-
moveDocument,
|
|
2110
|
-
refreshDocument,
|
|
2111
|
-
refreshPages,
|
|
2112
|
-
reorderDocuments,
|
|
2113
|
-
retryLoadingDocument,
|
|
2114
|
-
setActiveDocument,
|
|
2115
|
-
setDefaultRotation,
|
|
2116
|
-
setDefaultScale,
|
|
2117
|
-
setDocumentError,
|
|
2118
|
-
setDocumentLoaded,
|
|
2119
|
-
setPages,
|
|
2120
|
-
setRotation,
|
|
2121
|
-
setScale,
|
|
2122
|
-
startLoadingDocument,
|
|
2123
|
-
updateDocumentLoadingProgress,
|
|
2124
|
-
updateDocumentSecurity
|
|
2125
|
-
};
|
|
2126
|
-
//# sourceMappingURL=index.js.map
|
|
281
|
+
/** The stable id an input implies, if it carries one ('bytes'/'layerBytes'/'id'). */
|
|
282
|
+
const idOfInput = (input) => "id" in input && typeof input.id === "string" ? input.id : null;
|
|
283
|
+
const passwordOfInput = (input) => "password" in input ? input.password : void 0;
|
|
284
|
+
/** Engine rejections carry AbortError when close() aborts the live call;
|
|
285
|
+
* match structurally so test fakes with plain promises still work. */
|
|
286
|
+
const isAbortLike = (error) => error instanceof Error && error.name === "AbortError";
|
|
287
|
+
/**
|
|
288
|
+
* Assemble a kernel from an engine + plugins.
|
|
289
|
+
*
|
|
290
|
+
* planPlugins — validate dependencies, order them
|
|
291
|
+
* resolveCapability — workspace singletons, or per-document instances built lazily
|
|
292
|
+
* document lifecycle — one DocumentSession per document: transactional open
|
|
293
|
+
* (publish-last), one idempotent close for every phase
|
|
294
|
+
* start / destroy — explicit status machine; destroy closes everything
|
|
295
|
+
*
|
|
296
|
+
* The kernel closes every handle it opened; it never destroys the engine —
|
|
297
|
+
* ownership follows acquisition, and the engine was handed in by the caller.
|
|
298
|
+
*/
|
|
299
|
+
function createKernel(opts) {
|
|
300
|
+
const { engine, plugins } = opts;
|
|
301
|
+
const report = opts.report ?? ((error) => console.error("[kernel]", error));
|
|
302
|
+
const store = createStore(report);
|
|
303
|
+
const plan = planPlugins(plugins);
|
|
304
|
+
const documentScopedPlugins = plan.ordered.filter(isDocumentScoped);
|
|
305
|
+
const workspaceCapabilities = /* @__PURE__ */ new Map();
|
|
306
|
+
const workspaceScope = createScope(report);
|
|
307
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
308
|
+
let status = "created";
|
|
309
|
+
let startPromise = null;
|
|
310
|
+
let destroyPromise = null;
|
|
311
|
+
const guardUsable = (what) => {
|
|
312
|
+
if (status === "failed" || status === "destroying" || status === "destroyed") throw new Error(`[kernel] ${what} on a ${status} kernel`);
|
|
313
|
+
};
|
|
314
|
+
const checkpoint = (session) => {
|
|
315
|
+
if (session.cancel.signal.aborted) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Await an engine/network call under the session's cancellation:
|
|
319
|
+
* - the call is retained so close() can abort real worker-side work
|
|
320
|
+
* (`AbortablePromise`), and
|
|
321
|
+
* - the await RACES the cancellation, so close()'s join never blocks on a
|
|
322
|
+
* call that cannot be aborted (a plain-promise engine, a stuck fetch).
|
|
323
|
+
* When cancellation wins but the call later lands anyway, `onLateResult`
|
|
324
|
+
* routes the result into the session scope — whose late-defer rule runs it
|
|
325
|
+
* immediately after disposal — so a late-arriving resource cannot leak.
|
|
326
|
+
* (Plugin inits are deliberately NOT raced: they are first-party code that
|
|
327
|
+
* close() joins to completion; only unbounded external waits are raced.)
|
|
328
|
+
*/
|
|
329
|
+
async function engineCall(session, call, onLateResult) {
|
|
330
|
+
const abortable = call;
|
|
331
|
+
session.engineOp = typeof abortable.abort === "function" ? abortable : null;
|
|
332
|
+
const signal = session.cancel.signal;
|
|
333
|
+
let onAbort;
|
|
334
|
+
const cancellation = new Promise((_, reject) => {
|
|
335
|
+
onAbort = () => reject(new CancelledError(`closed while opening: ${session.id}`));
|
|
336
|
+
if (signal.aborted) onAbort();
|
|
337
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
338
|
+
});
|
|
339
|
+
try {
|
|
340
|
+
return await Promise.race([call, cancellation]);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (isCancelled(error)) call.then((value) => {
|
|
343
|
+
if (onLateResult) session.scope.defer(() => onLateResult(value));
|
|
344
|
+
}, () => {});
|
|
345
|
+
throw error;
|
|
346
|
+
} finally {
|
|
347
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
348
|
+
session.engineOp = null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function createSession(id, name) {
|
|
352
|
+
const session = {
|
|
353
|
+
id,
|
|
354
|
+
name,
|
|
355
|
+
phase: "opening",
|
|
356
|
+
handle: null,
|
|
357
|
+
stagedMeta: null,
|
|
358
|
+
scope: createScope(report),
|
|
359
|
+
operation: null,
|
|
360
|
+
engineOp: null,
|
|
361
|
+
cancel: new AbortController(),
|
|
362
|
+
capabilities: /* @__PURE__ */ new Map(),
|
|
363
|
+
close: () => closeSession(session)
|
|
364
|
+
};
|
|
365
|
+
return session;
|
|
366
|
+
}
|
|
367
|
+
/** One transition at a time per session (open, then possibly unlock). */
|
|
368
|
+
function beginOperation(session, run) {
|
|
369
|
+
if (session.operation) throw new Error(`[documents] "${session.id}" already has an active transition`);
|
|
370
|
+
const operation = run().finally(() => {
|
|
371
|
+
if (session.operation === operation) session.operation = null;
|
|
372
|
+
});
|
|
373
|
+
session.operation = operation;
|
|
374
|
+
return operation;
|
|
375
|
+
}
|
|
376
|
+
/** Atomic ticket → real-id reconciliation: the sessions map, the store slot,
|
|
377
|
+
* order position, and activation all move together, or not at all. */
|
|
378
|
+
function rekeySession(session, nextId) {
|
|
379
|
+
const previousId = session.id;
|
|
380
|
+
if (previousId === nextId) return;
|
|
381
|
+
const core = store.getCore();
|
|
382
|
+
if (sessions.has(nextId) || core.documents[nextId] || core.pending[nextId]) throw new Error(`[documents] duplicate document id: ${nextId}`);
|
|
383
|
+
sessions.delete(previousId);
|
|
384
|
+
session.id = nextId;
|
|
385
|
+
sessions.set(nextId, session);
|
|
386
|
+
const slot = core.pending[previousId];
|
|
387
|
+
if (slot) {
|
|
388
|
+
const { [previousId]: _moved, ...pending } = core.pending;
|
|
389
|
+
store.setCore({
|
|
390
|
+
pending: {
|
|
391
|
+
...pending,
|
|
392
|
+
[nextId]: {
|
|
393
|
+
...slot,
|
|
394
|
+
id: nextId
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
order: core.order.map((id) => id === previousId ? nextId : id),
|
|
398
|
+
activeId: core.activeId === previousId ? nextId : core.activeId
|
|
399
|
+
}, { type: CORE_ORDER_CHANGED });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const closingSessions = /* @__PURE__ */ new WeakMap();
|
|
403
|
+
function closeSession(session) {
|
|
404
|
+
const inFlight = closingSessions.get(session);
|
|
405
|
+
if (inFlight) return inFlight;
|
|
406
|
+
const closing = (async () => {
|
|
407
|
+
session.phase = "closing";
|
|
408
|
+
unpublishSlot(session.id);
|
|
409
|
+
const reason = new CancelledError(`closed while opening: ${session.id}`);
|
|
410
|
+
session.cancel.abort(reason);
|
|
411
|
+
session.engineOp?.abort(reason);
|
|
412
|
+
await session.operation?.catch((error) => {
|
|
413
|
+
if (!isCancelled(error) && !isAbortLike(error)) report(error);
|
|
414
|
+
});
|
|
415
|
+
await session.scope.dispose();
|
|
416
|
+
if (sessions.get(session.id) === session) sessions.delete(session.id);
|
|
417
|
+
})();
|
|
418
|
+
closingSessions.set(session, closing);
|
|
419
|
+
return closing;
|
|
420
|
+
}
|
|
421
|
+
function nextActiveDocument(core, removedId) {
|
|
422
|
+
if (core.activeId !== removedId) return core.activeId;
|
|
423
|
+
const index = core.order.indexOf(removedId);
|
|
424
|
+
const remaining = core.order.filter((id) => id !== removedId);
|
|
425
|
+
return remaining.length === 0 ? null : remaining[Math.max(0, index - 1)] ?? remaining[0];
|
|
426
|
+
}
|
|
427
|
+
function publishPendingSlot(session, activate) {
|
|
428
|
+
const core = store.getCore();
|
|
429
|
+
store.setCore({
|
|
430
|
+
pending: {
|
|
431
|
+
...core.pending,
|
|
432
|
+
[session.id]: {
|
|
433
|
+
id: session.id,
|
|
434
|
+
name: session.name,
|
|
435
|
+
status: "loading"
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
order: [...core.order, session.id],
|
|
439
|
+
activeId: activate || core.activeId === null ? session.id : core.activeId
|
|
440
|
+
}, { type: CORE_DOCUMENT_OPENING });
|
|
441
|
+
}
|
|
442
|
+
function publishLocked(session, passwordProvided) {
|
|
443
|
+
const core = store.getCore();
|
|
444
|
+
store.setCore({ pending: {
|
|
445
|
+
...core.pending,
|
|
446
|
+
[session.id]: {
|
|
447
|
+
id: session.id,
|
|
448
|
+
name: session.name,
|
|
449
|
+
status: "locked",
|
|
450
|
+
passwordProvided
|
|
451
|
+
}
|
|
452
|
+
} }, { type: CORE_DOCUMENT_LOCKED });
|
|
453
|
+
}
|
|
454
|
+
function publishError(session, error) {
|
|
455
|
+
const core = store.getCore();
|
|
456
|
+
store.setCore({ pending: {
|
|
457
|
+
...core.pending,
|
|
458
|
+
[session.id]: {
|
|
459
|
+
id: session.id,
|
|
460
|
+
name: session.name,
|
|
461
|
+
status: "error",
|
|
462
|
+
error
|
|
463
|
+
}
|
|
464
|
+
} }, { type: CORE_DOCUMENT_OPEN_FAILED });
|
|
465
|
+
}
|
|
466
|
+
function unpublishSlot(id) {
|
|
467
|
+
const core = store.getCore();
|
|
468
|
+
if (!core.pending[id] && !core.documents[id]) return;
|
|
469
|
+
const { [id]: _pending, ...pending } = core.pending;
|
|
470
|
+
const { [id]: _document, ...documents } = core.documents;
|
|
471
|
+
store.setCore({
|
|
472
|
+
pending,
|
|
473
|
+
documents,
|
|
474
|
+
order: core.order.filter((other) => other !== id),
|
|
475
|
+
activeId: nextActiveDocument(core, id)
|
|
476
|
+
}, { type: CORE_DOCUMENT_REMOVED });
|
|
477
|
+
}
|
|
478
|
+
/** The ONE ready transition: swap the pending slot for the staged meta and
|
|
479
|
+
* fire CORE_DOCUMENT_ADDED. Everything before this is unpublished and rolls
|
|
480
|
+
* back by disposing the session scope; nothing after this can fail. */
|
|
481
|
+
function commitReady(session) {
|
|
482
|
+
session.phase = "ready";
|
|
483
|
+
const meta = session.stagedMeta;
|
|
484
|
+
const core = store.getCore();
|
|
485
|
+
const { [session.id]: _resolved, ...pending } = core.pending;
|
|
486
|
+
store.setCore({
|
|
487
|
+
documents: {
|
|
488
|
+
...core.documents,
|
|
489
|
+
[session.id]: meta
|
|
490
|
+
},
|
|
491
|
+
pending
|
|
492
|
+
}, { type: CORE_DOCUMENT_ADDED });
|
|
493
|
+
}
|
|
494
|
+
const sessionOf = (documentId) => {
|
|
495
|
+
const id = documentId ?? store.getCore().activeId;
|
|
496
|
+
return id ? sessions.get(id) ?? null : null;
|
|
497
|
+
};
|
|
498
|
+
/** The handle plugins may touch: bring-up or ready — never locked, never
|
|
499
|
+
* a handle whose close already won. */
|
|
500
|
+
const documentHandle = (documentId) => {
|
|
501
|
+
const session = sessionOf(documentId);
|
|
502
|
+
if (!session) return null;
|
|
503
|
+
return session.phase === "bringup" || session.phase === "ready" ? session.handle : null;
|
|
504
|
+
};
|
|
505
|
+
function buildDocumentCapability(plugin, session) {
|
|
506
|
+
let capability = session.capabilities.get(plugin);
|
|
507
|
+
if (!capability) {
|
|
508
|
+
capability = plugin.capability(createPluginContext(services, plugin, session));
|
|
509
|
+
session.capabilities.set(plugin, capability);
|
|
510
|
+
}
|
|
511
|
+
return capability;
|
|
512
|
+
}
|
|
513
|
+
function resolveCapability(token, documentId) {
|
|
514
|
+
guardUsable(`capability("${token.name}")`);
|
|
515
|
+
const workspaceCapability = workspaceCapabilities.get(token);
|
|
516
|
+
if (workspaceCapability) return workspaceCapability;
|
|
517
|
+
const provider = plan.providerOf(token);
|
|
518
|
+
if (!provider) throw new Error(`No capability "${token.name}".`);
|
|
519
|
+
const id = documentId ?? store.getCore().activeId;
|
|
520
|
+
if (!id) throw new Error(`Capability "${token.name}" requires an active document.`);
|
|
521
|
+
const session = sessions.get(id);
|
|
522
|
+
if (!session) throw new Error(`Capability "${token.name}" unavailable: no document "${id}".`);
|
|
523
|
+
if (session.phase === "bringup" || session.phase === "ready") return buildDocumentCapability(provider, session);
|
|
524
|
+
const shown = session.phase === "opening" ? "loading" : session.phase;
|
|
525
|
+
throw new Error(`Capability "${token.name}" unavailable: document "${id}" is ${shown}.`);
|
|
526
|
+
}
|
|
527
|
+
/** Internal total resolver: bring-up counts, so a plugin's `ctx.tryGet`
|
|
528
|
+
* works during its own document's init. */
|
|
529
|
+
function tryResolveInternal(token, documentId) {
|
|
530
|
+
if (status === "destroying" || status === "destroyed") return null;
|
|
531
|
+
const workspaceCapability = workspaceCapabilities.get(token);
|
|
532
|
+
if (workspaceCapability) return workspaceCapability;
|
|
533
|
+
const provider = plan.providerOf(token);
|
|
534
|
+
if (!provider) return null;
|
|
535
|
+
const session = sessionOf(documentId);
|
|
536
|
+
if (!session || session.phase !== "bringup" && session.phase !== "ready") return null;
|
|
537
|
+
return buildDocumentCapability(provider, session);
|
|
538
|
+
}
|
|
539
|
+
/** Public total resolver — see `Kernel.tryCapability`. `ready` only: the
|
|
540
|
+
* null→instance flip at commit time IS the adapters' re-render signal. */
|
|
541
|
+
function tryResolveCapability(token, documentId) {
|
|
542
|
+
if (status === "destroying" || status === "destroyed") return null;
|
|
543
|
+
const workspaceCapability = workspaceCapabilities.get(token);
|
|
544
|
+
if (workspaceCapability) return workspaceCapability;
|
|
545
|
+
const provider = plan.providerOf(token);
|
|
546
|
+
if (!provider) return null;
|
|
547
|
+
const session = sessionOf(documentId);
|
|
548
|
+
if (!session || session.phase !== "ready") return null;
|
|
549
|
+
return buildDocumentCapability(provider, session);
|
|
550
|
+
}
|
|
551
|
+
const services = {
|
|
552
|
+
engine,
|
|
553
|
+
store,
|
|
554
|
+
workspaceScope,
|
|
555
|
+
resolveCapability,
|
|
556
|
+
tryResolveCapability: tryResolveInternal,
|
|
557
|
+
documentHandle
|
|
558
|
+
};
|
|
559
|
+
let ticketCounter = 0;
|
|
560
|
+
const nextTicket = () => `pending:${++ticketCounter}`;
|
|
561
|
+
/** Slices + event subscription + plugin inits + effect SETUP — every step's
|
|
562
|
+
* release deferred into the session scope, every await followed by a
|
|
563
|
+
* checkpoint. Runs entirely pre-commit: a failure anywhere rolls the whole
|
|
564
|
+
* session back and the document was never `ready`. */
|
|
565
|
+
async function bringUp(session, snapshot) {
|
|
566
|
+
session.phase = "bringup";
|
|
567
|
+
let renderPolicy = CONTINUOUS_RENDER_POLICY$1;
|
|
568
|
+
try {
|
|
569
|
+
renderPolicy = await session.handle.render?.policy() ?? CONTINUOUS_RENDER_POLICY$1;
|
|
570
|
+
} catch {}
|
|
571
|
+
checkpoint(session);
|
|
572
|
+
session.stagedMeta = {
|
|
573
|
+
id: session.id,
|
|
574
|
+
name: session.name,
|
|
575
|
+
pageCount: snapshot.pageCount,
|
|
576
|
+
pages: snapshot.pages,
|
|
577
|
+
revision: 0,
|
|
578
|
+
renderPolicy
|
|
579
|
+
};
|
|
580
|
+
const unsubscribeEvents = session.handle.events.subscribe((event) => {
|
|
581
|
+
const layout = layoutFromEvent(event);
|
|
582
|
+
if (!layout) return;
|
|
583
|
+
const now = store.getCore();
|
|
584
|
+
const existing = now.documents[session.id];
|
|
585
|
+
if (!existing) return;
|
|
586
|
+
const updated = {
|
|
587
|
+
...existing,
|
|
588
|
+
pageCount: layout.pageCount,
|
|
589
|
+
pages: layout.pages,
|
|
590
|
+
revision: existing.revision + 1
|
|
591
|
+
};
|
|
592
|
+
store.setCore({ documents: {
|
|
593
|
+
...now.documents,
|
|
594
|
+
[session.id]: updated
|
|
595
|
+
} }, { type: CORE_DOCUMENT_PAGES_UPDATED });
|
|
596
|
+
});
|
|
597
|
+
session.scope.defer(unsubscribeEvents);
|
|
598
|
+
for (const plugin of documentScopedPlugins) {
|
|
599
|
+
const key = sliceKey(plugin.id, session.id);
|
|
600
|
+
store.registerSlice(key, reducerOf(plugin), initialStateOf(plugin));
|
|
601
|
+
session.scope.defer(() => store.removeSlice(key));
|
|
602
|
+
}
|
|
603
|
+
for (const plugin of documentScopedPlugins) {
|
|
604
|
+
await plugin.init?.(createPluginContext(services, plugin, session));
|
|
605
|
+
checkpoint(session);
|
|
606
|
+
}
|
|
607
|
+
for (const plugin of documentScopedPlugins) plugin.effects?.(createEffectContext(services, plugin, session));
|
|
608
|
+
checkpoint(session);
|
|
609
|
+
}
|
|
610
|
+
async function openDocument(input, options) {
|
|
611
|
+
guardUsable("documents.open()");
|
|
612
|
+
const { activate, name, ...engineOptions } = options ?? {};
|
|
613
|
+
const requestedId = typeof input === "function" ? nextTicket() : idOfInput(input) ?? nextTicket();
|
|
614
|
+
if (sessions.has(requestedId)) throw new Error(`[documents] document already open: ${requestedId}`);
|
|
615
|
+
const session = createSession(requestedId, name);
|
|
616
|
+
sessions.set(session.id, session);
|
|
617
|
+
publishPendingSlot(session, activate ?? true);
|
|
618
|
+
return beginOperation(session, async () => {
|
|
619
|
+
try {
|
|
620
|
+
const source = typeof input === "function" ? await engineCall(session, Promise.resolve(input(session.cancel.signal))) : input;
|
|
621
|
+
checkpoint(session);
|
|
622
|
+
const sourceId = idOfInput(source);
|
|
623
|
+
if (sourceId) rekeySession(session, sourceId);
|
|
624
|
+
const handle = await engineCall(session, engine.open(source, engineOptions), (late) => late.close());
|
|
625
|
+
session.handle = handle;
|
|
626
|
+
session.scope.defer(() => handle.close());
|
|
627
|
+
checkpoint(session);
|
|
628
|
+
if (handle.id !== session.id) rekeySession(session, handle.id);
|
|
629
|
+
if (handle.security?.passwordPrompt?.state === "required") {
|
|
630
|
+
const passwordProvided = "password" in engineOptions && engineOptions.password != null || passwordOfInput(source) != null;
|
|
631
|
+
session.phase = "locked";
|
|
632
|
+
publishLocked(session, passwordProvided);
|
|
633
|
+
return session.id;
|
|
634
|
+
}
|
|
635
|
+
const snapshot = await engineCall(session, handle.pages.list());
|
|
636
|
+
checkpoint(session);
|
|
637
|
+
await bringUp(session, snapshot);
|
|
638
|
+
commitReady(session);
|
|
639
|
+
return session.id;
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (isCancelled(error)) throw error;
|
|
642
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
643
|
+
await session.scope.dispose();
|
|
644
|
+
session.phase = "error";
|
|
645
|
+
publishError(session, error);
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async function unlockDocument(id, input) {
|
|
651
|
+
guardUsable("documents.unlock()");
|
|
652
|
+
const session = sessions.get(id);
|
|
653
|
+
if (!session || session.phase !== "locked" || !session.handle) throw new Error(`[documents] document is not locked: ${id}`);
|
|
654
|
+
const handle = session.handle;
|
|
655
|
+
return beginOperation(session, async () => {
|
|
656
|
+
try {
|
|
657
|
+
await engineCall(session, handle.security.unlock({ password: input.password }));
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
checkpoint(session);
|
|
663
|
+
try {
|
|
664
|
+
const snapshot = await engineCall(session, handle.pages.list());
|
|
665
|
+
checkpoint(session);
|
|
666
|
+
await bringUp(session, snapshot);
|
|
667
|
+
commitReady(session);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (isCancelled(error)) throw error;
|
|
670
|
+
if (session.phase === "closing" || isAbortLike(error)) throw new CancelledError(`closed while opening: ${session.id}`);
|
|
671
|
+
await session.scope.dispose();
|
|
672
|
+
session.phase = "error";
|
|
673
|
+
publishError(session, error);
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
async function closeDocument(documentId) {
|
|
679
|
+
const session = sessions.get(documentId);
|
|
680
|
+
if (!session) return;
|
|
681
|
+
await session.close();
|
|
682
|
+
}
|
|
683
|
+
function reorder(next) {
|
|
684
|
+
store.setCore({ order: next }, { type: CORE_ORDER_CHANGED });
|
|
685
|
+
}
|
|
686
|
+
const documents = {
|
|
687
|
+
open: openDocument,
|
|
688
|
+
openAll: (docs) => {
|
|
689
|
+
guardUsable("documents.openAll()");
|
|
690
|
+
const activeIndex = Math.max(0, docs.findIndex((d) => d.active));
|
|
691
|
+
docs.forEach(({ source, active: _active, ...options }, index) => {
|
|
692
|
+
openDocument(source, {
|
|
693
|
+
...options,
|
|
694
|
+
activate: index === activeIndex
|
|
695
|
+
}).catch(() => {});
|
|
696
|
+
});
|
|
697
|
+
},
|
|
698
|
+
unlock: unlockDocument,
|
|
699
|
+
close: closeDocument,
|
|
700
|
+
closeAll: async () => {
|
|
701
|
+
for (const id of [...store.getCore().order]) await closeDocument(id);
|
|
702
|
+
},
|
|
703
|
+
setActive: (id) => {
|
|
704
|
+
const core = store.getCore();
|
|
705
|
+
if (core.documents[id] || core.pending[id]) store.setCore({ activeId: id }, { type: CORE_ACTIVE_CHANGED });
|
|
706
|
+
},
|
|
707
|
+
activeId: () => store.getCore().activeId,
|
|
708
|
+
list: () => store.getCore().order.map((id) => {
|
|
709
|
+
const core = store.getCore();
|
|
710
|
+
const meta = core.documents[id];
|
|
711
|
+
return meta ? toDocInfo(meta) : pendingToDocInfo(core.pending[id]);
|
|
712
|
+
}),
|
|
713
|
+
get: (id) => {
|
|
714
|
+
const core = store.getCore();
|
|
715
|
+
const meta = core.documents[id];
|
|
716
|
+
if (meta) return toDocInfo(meta);
|
|
717
|
+
const pending = core.pending[id];
|
|
718
|
+
return pending ? pendingToDocInfo(pending) : null;
|
|
719
|
+
},
|
|
720
|
+
has: (id) => {
|
|
721
|
+
const core = store.getCore();
|
|
722
|
+
return core.documents[id] !== void 0 || core.pending[id] !== void 0;
|
|
723
|
+
},
|
|
724
|
+
count: () => store.getCore().order.length,
|
|
725
|
+
order: () => [...store.getCore().order],
|
|
726
|
+
move: (id, toIndex) => {
|
|
727
|
+
const core = store.getCore();
|
|
728
|
+
if (!core.documents[id] && !core.pending[id]) return;
|
|
729
|
+
const without = core.order.filter((x) => x !== id);
|
|
730
|
+
const clamped = Math.max(0, Math.min(toIndex, without.length));
|
|
731
|
+
without.splice(clamped, 0, id);
|
|
732
|
+
reorder(without);
|
|
733
|
+
},
|
|
734
|
+
swap: (a, b) => {
|
|
735
|
+
const core = store.getCore();
|
|
736
|
+
const indexA = core.order.indexOf(a);
|
|
737
|
+
const indexB = core.order.indexOf(b);
|
|
738
|
+
if (indexA < 0 || indexB < 0) return;
|
|
739
|
+
const next = [...core.order];
|
|
740
|
+
next[indexA] = b;
|
|
741
|
+
next[indexB] = a;
|
|
742
|
+
reorder(next);
|
|
743
|
+
},
|
|
744
|
+
download: (id, opts) => {
|
|
745
|
+
const handle = documentHandle(id);
|
|
746
|
+
if (!handle) return Promise.reject(/* @__PURE__ */ new Error("[documents] no document to download"));
|
|
747
|
+
return handle.download(opts);
|
|
748
|
+
},
|
|
749
|
+
downloadLayer: (id) => {
|
|
750
|
+
const handle = documentHandle(id);
|
|
751
|
+
if (!handle) return Promise.reject(/* @__PURE__ */ new Error("[documents] no document to download"));
|
|
752
|
+
if (!handle.downloadLayer) return Promise.reject(/* @__PURE__ */ new Error("[documents] this engine cannot export a layer (open with a layer on the local engine)"));
|
|
753
|
+
return handle.downloadLayer();
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
workspaceCapabilities.set(DocumentsToken, documents);
|
|
757
|
+
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin)) store.registerSlice(plugin.id, reducerOf(plugin), initialStateOf(plugin));
|
|
758
|
+
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin) && plugin.token && plugin.capability) workspaceCapabilities.set(plugin.token, plugin.capability(createPluginContext(services, plugin)));
|
|
759
|
+
return {
|
|
760
|
+
engine,
|
|
761
|
+
documents,
|
|
762
|
+
capability: resolveCapability,
|
|
763
|
+
tryCapability: tryResolveCapability,
|
|
764
|
+
scopeOf: plan.scopeOf,
|
|
765
|
+
subscribe: store.subscribe,
|
|
766
|
+
getState: store.getState,
|
|
767
|
+
status: () => status,
|
|
768
|
+
start: () => {
|
|
769
|
+
if (status === "destroying" || status === "destroyed" || status === "failed") return Promise.reject(/* @__PURE__ */ new Error(`[kernel] start() on a ${status} kernel`));
|
|
770
|
+
return startPromise ?? (startPromise = (async () => {
|
|
771
|
+
status = "starting";
|
|
772
|
+
try {
|
|
773
|
+
for (const plugin of plan.ordered) {
|
|
774
|
+
if (status !== "starting") return;
|
|
775
|
+
if (!isDocumentScoped(plugin)) await plugin.init?.(createPluginContext(services, plugin));
|
|
776
|
+
}
|
|
777
|
+
if (status !== "starting") return;
|
|
778
|
+
for (const plugin of plan.ordered) if (!isDocumentScoped(plugin)) plugin.effects?.(createEffectContext(services, plugin));
|
|
779
|
+
status = "started";
|
|
780
|
+
} catch (error) {
|
|
781
|
+
status = "failed";
|
|
782
|
+
await workspaceScope.dispose();
|
|
783
|
+
workspaceCapabilities.clear();
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
})());
|
|
787
|
+
},
|
|
788
|
+
destroy: () => {
|
|
789
|
+
return destroyPromise ?? (destroyPromise = (async () => {
|
|
790
|
+
const wasFailed = status === "failed";
|
|
791
|
+
status = "destroying";
|
|
792
|
+
await startPromise?.catch((error) => {
|
|
793
|
+
if (!isCancelled(error) && !wasFailed) report(error);
|
|
794
|
+
});
|
|
795
|
+
await Promise.allSettled([...sessions.values()].map((session) => session.close()));
|
|
796
|
+
sessions.clear();
|
|
797
|
+
await workspaceScope.dispose();
|
|
798
|
+
workspaceCapabilities.clear();
|
|
799
|
+
store.destroy();
|
|
800
|
+
status = "destroyed";
|
|
801
|
+
})());
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region src/index.ts
|
|
807
|
+
/** Create a typed capability token. `name` is for debugging only. */
|
|
808
|
+
function createCapabilityToken(name) {
|
|
809
|
+
return { name };
|
|
810
|
+
}
|
|
811
|
+
/** Identity helper that pins a plugin's generics. The real win is inference. */
|
|
812
|
+
function definePlugin(def) {
|
|
813
|
+
return def;
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
816
|
+
export { AbortablePromise, CONTINUOUS_RENDER_POLICY, CORE_ACTIVE_CHANGED, CORE_DOCUMENT_ADDED, CORE_DOCUMENT_LOCKED, CORE_DOCUMENT_OPENING, CORE_DOCUMENT_OPEN_FAILED, CORE_DOCUMENT_PAGES_UPDATED, CORE_DOCUMENT_REMOVED, CORE_ORDER_CHANGED, CancelledError, DocumentsToken, createCapabilityToken, createKernel, definePlugin, docInfoEquals, docInfoListEquals, isCancelled, snapAppearanceScale, snapFullPageViewport, snapTileScale };
|
|
817
|
+
|
|
818
|
+
//# sourceMappingURL=index.js.map
|