@openpresentation/opf-editor 0.28.0 → 0.29.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/README.md +20 -6
- package/dist/canvas.d.ts +13 -1
- package/dist/canvas.js +39 -9
- package/dist/chart-options-panel.js +16 -16
- package/dist/codemirror-guard.js +69 -0
- package/dist/content-controls.js +127 -74
- package/dist/data-grid.js +15 -7
- package/dist/design-controls.d.ts +13 -0
- package/dist/design-controls.js +134 -124
- package/dist/exact-source.d.ts +30 -0
- package/dist/font-gate.js +20 -1
- package/dist/json-editor.js +8 -2
- package/dist/json-options.d.ts +2 -1
- package/dist/json-options.js +7 -6
- package/dist/layout-templates.d.ts +8 -3
- package/dist/layout-templates.js +30 -16
- package/dist/numbering-panel.js +2 -3
- package/dist/react.d.ts +12 -4
- package/dist/react.js +39 -5
- package/dist/slide-adapter.d.ts +49 -0
- package/dist/slide-adapter.js +289 -0
- package/dist/slide-edits.d.ts +46 -0
- package/dist/slide-edits.js +629 -0
- package/dist/slide-manager.js +2 -1
- package/dist/slide-scene.d.ts +96 -0
- package/dist/slide-scene.js +413 -0
- package/dist/source-checker.js +45 -6
- package/dist/source-completions.js +6 -4
- package/dist/source-pane.d.ts +18 -6
- package/dist/source-pane.js +45 -0
- package/dist/source-worker.js +8 -4
- package/dist/studio-canvas.d.ts +34 -0
- package/dist/studio-canvas.js +586 -0
- package/dist/studio-commands.d.ts +15 -0
- package/dist/studio-commands.js +155 -0
- package/dist/studio-design.d.ts +16 -0
- package/dist/studio-design.js +354 -0
- package/dist/studio-kit/dom.d.ts +16 -0
- package/dist/studio-kit/dom.js +42 -6
- package/dist/studio-layers.d.ts +12 -0
- package/dist/studio-layers.js +218 -0
- package/dist/studio.d.ts +31 -0
- package/dist/studio.js +1608 -0
- package/dist/styles.css +1546 -117
- package/dist/thumbnail-cache.d.ts +2 -0
- package/dist/thumbnail-cache.js +10 -1
- package/dist/workspace-measure.js +6 -4
- package/dist/workspace-parts.js +20 -0
- package/dist/workspace-shell.js +25 -15
- package/dist/workspace-theme.js +3 -0
- package/dist/workspace.d.ts +97 -9
- package/dist/workspace.js +325 -59
- package/package.json +29 -18
package/dist/workspace.js
CHANGED
|
@@ -7,29 +7,13 @@
|
|
|
7
7
|
// A host picks a layout: `"full"` (a bar, the source, the slide, the slide row and a side panel are built for it), `"canvas"`, `"source"`,
|
|
8
8
|
// or `slots`, an object of its own elements, one per part, for a host that arranges the page itself. Each part is optional (`panels`).
|
|
9
9
|
// Nothing here fetches anything, stores anything on its own or talks to a service; the host supplies fonts, catalogs and storage.
|
|
10
|
-
import { toSvg } from "@openpresentation/opf-render/svg";
|
|
11
10
|
import { OPFEditorError, createEditorSession, mergeCatalogs } from "./index.js";
|
|
12
|
-
import { createCanvasEditor } from "./canvas.js";
|
|
13
11
|
import { whenFontsReady } from "./font-gate.js";
|
|
14
12
|
import { createSourcePane } from "./source-pane.js";
|
|
15
|
-
import { createSlideNavigator } from "./slide-navigator.js";
|
|
16
|
-
import { createThumbnailStrip } from "./thumbnail-strip.js";
|
|
17
|
-
import { createThumbnailCache } from "./thumbnail-cache.js";
|
|
18
|
-
import { createSlideManager } from "./slide-manager.js";
|
|
19
|
-
import { createOutlineView } from "./outline-view.js";
|
|
20
|
-
import { createExportMenu } from "./export-menu.js";
|
|
21
|
-
import { bindPresentShortcut, createPresentButton, createPresenter } from "./presenter.js";
|
|
22
|
-
import { createReviewPanel } from "./review-panel.js";
|
|
23
|
-
import { createFindPanel, installFindShortcuts } from "./find-panel.js";
|
|
24
|
-
import { createDesignControls } from "./design-controls.js";
|
|
25
13
|
import { createPersistence } from "./persistence.js";
|
|
26
14
|
import { createPersistenceUi } from "./persistence-ui.js";
|
|
27
|
-
import { prepareDimensionSwitch } from "./switches.js";
|
|
28
|
-
import { mergeSlides, splitSlideByBlocks } from "./content-actions.js";
|
|
29
|
-
import { installCanvasVariableEditing } from "./canvas-variables.js";
|
|
30
|
-
import { createLayoutStudio } from "./workspace-layouts.js";
|
|
31
|
-
import { fontSubstitutionNotes, layoutOptionsFor } from "./workspace-measure.js";
|
|
32
15
|
import { buildWorkspaceShell } from "./workspace-shell.js";
|
|
16
|
+
import { fontSubstitutionNotes, layoutOptionsFor } from "./workspace-measure.js";
|
|
33
17
|
import { SOURCE_CHECKS, copyText, downloadSourceFile, lf, reportSource } from "./workspace-source.js";
|
|
34
18
|
import { createThemeController } from "./workspace-theme.js";
|
|
35
19
|
import { retainedSlideIndex } from "./slide-retention.js";
|
|
@@ -39,6 +23,34 @@ export { THEME_TOKENS, createThemeController, normalizeTheme, normalizeThemeToke
|
|
|
39
23
|
// The script-aware measurement options for composing one slide: a host that calls `composeSlide` or `paginateSlide` on the session passes these (the playground does).
|
|
40
24
|
export { layoutOptionsFor } from "./workspace-measure.js";
|
|
41
25
|
|
|
26
|
+
// The slide and everything around it (the renderer, the canvas, the slide row, the presenter, Export, Review, Find, Design and the views) load
|
|
27
|
+
// on demand, in one chunk (`workspace-parts.js`): a host that mounts only the source pane never loads the renderer. Its factories are bound
|
|
28
|
+
// here once the chunk has arrived, before any part is mounted.
|
|
29
|
+
let toSvg, createCanvasEditor, createSlideNavigator, createThumbnailStrip, createThumbnailCache, createSlideManager, createOutlineView, createExportMenu;
|
|
30
|
+
let bindPresentShortcut, createPresentButton, createPresenter, createReviewPanel, createFindPanel, installFindShortcuts, createDesignControls;
|
|
31
|
+
let prepareDimensionSwitch, mergeSlides, splitSlideByBlocks, installCanvasVariableEditing, createLayoutStudio, buildStudioShell, createStudio;
|
|
32
|
+
let partsModule;
|
|
33
|
+
let partsLoaded = false;
|
|
34
|
+
function loadParts() {
|
|
35
|
+
partsModule ??= import("./workspace-parts.js").then((parts) => {
|
|
36
|
+
({ toSvg, createCanvasEditor, createSlideNavigator, createThumbnailStrip, createThumbnailCache, createSlideManager, createOutlineView, createExportMenu } = parts);
|
|
37
|
+
({ bindPresentShortcut, createPresentButton, createPresenter, createReviewPanel, createFindPanel, installFindShortcuts, createDesignControls } = parts);
|
|
38
|
+
({ prepareDimensionSwitch, mergeSlides, splitSlideByBlocks, installCanvasVariableEditing, createLayoutStudio, buildStudioShell, createStudio } = parts);
|
|
39
|
+
partsLoaded = true;
|
|
40
|
+
});
|
|
41
|
+
// A chunk that failed to load is asked for again by the next editor.
|
|
42
|
+
partsModule.catch(() => {
|
|
43
|
+
partsModule = undefined;
|
|
44
|
+
});
|
|
45
|
+
return partsModule;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether the parts to mount need the slide chunk: any part but the source pane, its autosave and the history buttons, that has an element to
|
|
49
|
+
* mount in (the presenter mounts in the overlay).
|
|
50
|
+
*/
|
|
51
|
+
const SOURCE_ONLY_PARTS = new Set(["source", "persistence", "history"]);
|
|
52
|
+
const needsSlideParts = (parts, slots) => Object.entries(parts).some(([name, on]) => on && !SOURCE_ONLY_PARTS.has(name) && (name === "present" || Boolean(slots[name])));
|
|
53
|
+
|
|
42
54
|
const VIEWS = Object.freeze(["slide", "sorter", "outline", "layouts"]);
|
|
43
55
|
const MUTATORS = new Set(["set", "setGroupComposition", "setCatalog", "setCatalogs", "applyPatch", "replaceDocument", "restoreState", "undo", "redo", "setComposition", "paginateSlide"]);
|
|
44
56
|
const STARTER = Object.freeze({ name: "Untitled presentation", slides: [{ id: "slide-1", title: "Untitled slide" }] });
|
|
@@ -57,7 +69,11 @@ const LAYOUT_PARTS = {
|
|
|
57
69
|
full: { source: true, navigator: true, thumbnails: true, history: true, find: true, present: true, export: true, review: true, layouts: true, sorter: true, outline: true },
|
|
58
70
|
canvas: { navigator: true, thumbnails: true, history: true },
|
|
59
71
|
source: { source: true },
|
|
72
|
+
// CE-20: the Studio and its Playground preset arrange the same parts; the Studio mounts its own Design panel.
|
|
73
|
+
studio: { source: true, navigator: true, thumbnails: true, history: true, find: true, present: true, export: true, review: true, layouts: true, slides: true },
|
|
74
|
+
playground: { source: true, navigator: true, thumbnails: true, history: true, find: true, present: true, export: true, review: true, layouts: true, slides: true },
|
|
60
75
|
};
|
|
76
|
+
const STUDIO_LAYOUTS = new Set(["studio", "playground"]);
|
|
61
77
|
|
|
62
78
|
const isThenable = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
|
|
63
79
|
|
|
@@ -110,7 +126,9 @@ function resolveParts(options, layout, slots) {
|
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
/**
|
|
113
|
-
* Mount the editor in `element`. Returns the handle at once;
|
|
129
|
+
* Mount the editor in `element`. Returns the handle at once; the parts mount once a `fonts` promise has resolved and, for a layout that shows
|
|
130
|
+
* slides, once the slide chunk has loaded (`mounted` is emitted then, and `ready` settles after the first update). A source-only editor whose
|
|
131
|
+
* fonts are at hand mounts before this returns.
|
|
114
132
|
* See `workspace.d.ts` for the options and the handle.
|
|
115
133
|
*/
|
|
116
134
|
export function createOpfEditor(element, options = {}) {
|
|
@@ -165,8 +183,9 @@ export function createOpfEditor(element, options = {}) {
|
|
|
165
183
|
|
|
166
184
|
// ---- layout, parts and slots ------------------------------------------------------------------------------------------------
|
|
167
185
|
const given = options.slots ?? {};
|
|
168
|
-
const layout = options.slots ? "slots" : (options.layout ?? "full");
|
|
169
|
-
if (!["full", "canvas", "source", "slots"].includes(layout)) throw new RangeError('layout must be "full", "canvas" or "
|
|
186
|
+
const layout = options.slots && !STUDIO_LAYOUTS.has(options.layout) ? "slots" : (options.layout ?? "full");
|
|
187
|
+
if (!["full", "canvas", "source", "slots", "studio", "playground"].includes(layout)) throw new RangeError('layout must be "full", "canvas", "source", "studio" or "playground", or pass `slots`.');
|
|
188
|
+
const studioLayout = STUDIO_LAYOUTS.has(layout);
|
|
170
189
|
const parts = resolveParts(options, layout, given);
|
|
171
190
|
const views = VIEWS.filter((view) => view === "slide" || parts[view]);
|
|
172
191
|
if (options.view !== undefined && !views.includes(options.view)) throw new RangeError(`view "${options.view}" is not available in this layout (${views.join(", ")}).`);
|
|
@@ -183,13 +202,27 @@ export function createOpfEditor(element, options = {}) {
|
|
|
183
202
|
const themeController = createThemeController(element, options.theme);
|
|
184
203
|
let slots = { ...given };
|
|
185
204
|
let controls = {};
|
|
205
|
+
let studioShell = null;
|
|
206
|
+
let overlay = root;
|
|
207
|
+
// CE-20: the Studio builds its own shell; a host's slots add parts to it (`topbarEnd`, `overlay`). Its code is in the slide chunk, so the Studio
|
|
208
|
+
// shell is built once that has loaded, right before the parts mount.
|
|
209
|
+
function buildStudio() {
|
|
210
|
+
const built = buildStudioShell(root, { parts, layout, labels: options.labels, studio: options.studio, slots: given });
|
|
211
|
+
const { topbarEnd: _placed, ...rest } = given;
|
|
212
|
+
slots = { ...built.slots, ...rest };
|
|
213
|
+
controls = built.controls;
|
|
214
|
+
studioShell = built.shell;
|
|
215
|
+
overlay = slots.overlay ?? root;
|
|
216
|
+
}
|
|
186
217
|
if (layout === "slots") root.setAttribute("data-opf-layout", "slots");
|
|
187
|
-
else {
|
|
218
|
+
else if (!studioLayout) {
|
|
188
219
|
const built = buildWorkspaceShell(root, { parts, views, layout, labels: options.labels });
|
|
189
220
|
slots = { ...built.slots, ...given };
|
|
190
221
|
controls = built.controls;
|
|
191
222
|
}
|
|
192
|
-
|
|
223
|
+
overlay = slots.overlay ?? root;
|
|
224
|
+
// The slide chunk is loaded before the parts mount, unless only the source pane (with its autosave) is mounted. The Studio always needs it.
|
|
225
|
+
const slideParts = studioLayout || needsSlideParts(parts, slots);
|
|
193
226
|
|
|
194
227
|
// ---- state ------------------------------------------------------------------------------------------------------------------
|
|
195
228
|
let fonts;
|
|
@@ -201,6 +234,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
201
234
|
let sorter;
|
|
202
235
|
let outline;
|
|
203
236
|
let studio;
|
|
237
|
+
let studioCtl;
|
|
238
|
+
let fontsPending = false;
|
|
204
239
|
let presenter;
|
|
205
240
|
let presentButton;
|
|
206
241
|
let exportMenu;
|
|
@@ -231,10 +266,35 @@ export function createOpfEditor(element, options = {}) {
|
|
|
231
266
|
let statusText = "";
|
|
232
267
|
let density = "comfortable";
|
|
233
268
|
let exportMode = "menu";
|
|
269
|
+
// The parts are bound once the slide chunk has arrived and the fonts are at hand; `partsReady` is true from then on. A call on the handle made
|
|
270
|
+
// before that (a React host's effects run right after `createOpfEditor` returns) must neither reach a factory that is not bound yet nor be lost:
|
|
271
|
+
// a setting is remembered in `requested` and read when the parts mount; a call that acts on a mounted part waits in `queued` and runs, in order,
|
|
272
|
+
// once the editor is mounted (a later call with the same key replaces the earlier one).
|
|
273
|
+
let partsReady = false;
|
|
274
|
+
const requested = {};
|
|
275
|
+
const queued = [];
|
|
276
|
+
const waiting = () => !mounted && !destroyed;
|
|
277
|
+
const queue = (key, task) => {
|
|
278
|
+
const at = queued.findIndex((entry) => entry.key === key);
|
|
279
|
+
if (at >= 0) queued.splice(at, 1);
|
|
280
|
+
const entry = { key, task };
|
|
281
|
+
queued.push(entry);
|
|
282
|
+
return entry;
|
|
283
|
+
};
|
|
284
|
+
function flushQueued() {
|
|
285
|
+
for (const { task } of queued.splice(0)) {
|
|
286
|
+
if (destroyed) return;
|
|
287
|
+
try {
|
|
288
|
+
task();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
report(error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
234
294
|
let frameSize = { width: 0, height: 0 };
|
|
235
295
|
const draftMemory = createSourceMemory();
|
|
236
296
|
const variablesMode = options.variables === "resolved" ? "resolved" : "authored";
|
|
237
|
-
const fitMode = options.fit === undefined ? (layout === "slots" ? false : "contain") : options.fit;
|
|
297
|
+
const fitMode = studioLayout ? false : options.fit === undefined ? (layout === "slots" ? false : "contain") : options.fit;
|
|
238
298
|
|
|
239
299
|
const say = (message) => {
|
|
240
300
|
statusText = message;
|
|
@@ -255,7 +315,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
255
315
|
|
|
256
316
|
// ---- navigation and the refresh loop ----------------------------------------------------------------------------------------
|
|
257
317
|
function renderViews(deck) {
|
|
258
|
-
|
|
318
|
+
// The Studio's filmstrip draws the thumbnails it shows itself, as they scroll in.
|
|
319
|
+
if (!studioLayout) thumbnailCache?.prepare(deck);
|
|
259
320
|
slideManager?.render();
|
|
260
321
|
if (activeView === "sorter") sorter?.render();
|
|
261
322
|
if (activeView === "outline") outline?.render();
|
|
@@ -283,7 +344,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
283
344
|
}
|
|
284
345
|
function sync() {
|
|
285
346
|
fonts?.registry?.clearSubstitutions?.();
|
|
286
|
-
|
|
347
|
+
// The frozen snapshot (one object per version): the session's `presentation` copies the whole deck on every read.
|
|
348
|
+
const deck = session.snapshot().presentation;
|
|
287
349
|
slideIndex = Math.max(0, Math.min(slideIndex, deck.slides.length - 1));
|
|
288
350
|
renderViews(deck);
|
|
289
351
|
slideNav?.setSlide(slideIndex);
|
|
@@ -300,16 +362,18 @@ export function createOpfEditor(element, options = {}) {
|
|
|
300
362
|
if (!mounted || destroyed) return Promise.resolve();
|
|
301
363
|
const token = ++refreshToken;
|
|
302
364
|
let loaded = false;
|
|
303
|
-
lastRefresh = whenFontsReady(fonts, session.presentation, {
|
|
365
|
+
lastRefresh = whenFontsReady(fonts, session.snapshot().presentation, {
|
|
304
366
|
renderOptions: renderOptions(),
|
|
305
367
|
isCurrent: () => token === refreshToken && !destroyed,
|
|
306
368
|
loading: () => {
|
|
307
369
|
loaded = true;
|
|
370
|
+
fontsPending = true;
|
|
308
371
|
emit("fonts", { state: "loading" });
|
|
309
372
|
say(MESSAGES.loadingFonts);
|
|
310
373
|
},
|
|
311
374
|
ready: () => {
|
|
312
375
|
fontsFailure = undefined;
|
|
376
|
+
fontsPending = false;
|
|
313
377
|
if (loaded) {
|
|
314
378
|
thumbnailCache?.clear();
|
|
315
379
|
emit("fonts", { state: "ready" });
|
|
@@ -353,8 +417,14 @@ export function createOpfEditor(element, options = {}) {
|
|
|
353
417
|
return false;
|
|
354
418
|
}
|
|
355
419
|
function goTo(index, path) {
|
|
420
|
+
if (waiting()) {
|
|
421
|
+
go(index);
|
|
422
|
+
queue("goTo", () => goTo(index, path));
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
356
425
|
if (index !== slideIndex && !go(index)) return false;
|
|
357
|
-
if (
|
|
426
|
+
if (studioCtl) studioCtl.revealPath(path ?? `slides.${index}`);
|
|
427
|
+
else if (path) canvas?.reveal(path);
|
|
358
428
|
return true;
|
|
359
429
|
}
|
|
360
430
|
|
|
@@ -367,7 +437,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
367
437
|
if (controls.redo) controls.redo.disabled = !inSync || readOnly || !session.canRedo;
|
|
368
438
|
// Text in the source pane that is not a valid deck: the slide shows the last valid one and is not editable until the text is fixed.
|
|
369
439
|
const frame = slots.canvas && (slots.slide ?? slots.canvas);
|
|
370
|
-
|
|
440
|
+
// The Studio keeps selection, zoom and Present while read-only (its commands say why they are off); only invalid text locks the slide.
|
|
441
|
+
if (frame && (!inSync || (readOnly && !studioLayout))) frame.setAttribute("inert", "");
|
|
371
442
|
else frame?.removeAttribute("inert");
|
|
372
443
|
}
|
|
373
444
|
function applyFit() {
|
|
@@ -393,7 +464,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
393
464
|
setSlideIndex: (index) => {
|
|
394
465
|
go(index);
|
|
395
466
|
},
|
|
396
|
-
|
|
467
|
+
// The Studio's filmstrip draws only the thumbnails it shows (an IntersectionObserver fills the rest as they scroll in).
|
|
468
|
+
renderThumbnail: studioLayout ? (deck, index) => thumbnailCache.peek(deck, index) ?? "" : (deck, index) => thumbnailCache.html(deck, index),
|
|
397
469
|
renderLayoutPreview: (deck, index, choice) => {
|
|
398
470
|
const candidate = prepareDimensionSwitch(deck, "layouts", choice.id, { slideIndex: index, catalogs: catalogs() }).presentation;
|
|
399
471
|
return toSvg(candidate, index + 1, { ...renderOptions(), fonts, text: "system", trace: false });
|
|
@@ -412,6 +484,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
412
484
|
if (!views.includes(view)) return false;
|
|
413
485
|
if (!mounted) {
|
|
414
486
|
activeView = view;
|
|
487
|
+
requested.view = view;
|
|
415
488
|
return true;
|
|
416
489
|
}
|
|
417
490
|
if (view !== "slide" && canvas && !canvas.commit()) return false;
|
|
@@ -454,6 +527,16 @@ export function createOpfEditor(element, options = {}) {
|
|
|
454
527
|
return true;
|
|
455
528
|
}
|
|
456
529
|
function showPanel(name, focus = false) {
|
|
530
|
+
if (!mounted) {
|
|
531
|
+
// The tabs of the shell are known now, the Studio's when it mounts; the panel is shown after the default one.
|
|
532
|
+
if (destroyed || (!studioLayout && !controls.tabs?.[name])) return false;
|
|
533
|
+
queue("panel", () => showPanel(name, focus));
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
return showPanelNow(name, focus);
|
|
537
|
+
}
|
|
538
|
+
function showPanelNow(name, focus = false) {
|
|
539
|
+
if (studioCtl) return studioCtl.showTab(name, { focus });
|
|
457
540
|
const tabs = controls.tabs;
|
|
458
541
|
if (!tabs?.[name]) return false;
|
|
459
542
|
for (const [tabName, tab] of Object.entries(tabs)) {
|
|
@@ -470,11 +553,16 @@ export function createOpfEditor(element, options = {}) {
|
|
|
470
553
|
|
|
471
554
|
// ---- the parts --------------------------------------------------------------------------------------------------------------
|
|
472
555
|
function mountSlideRow() {
|
|
556
|
+
// The row is built from the slide chunk's factories: before they are bound (and after destroy) there is nothing to rebuild; the first
|
|
557
|
+
// mount reads the density and the layout as they are by then.
|
|
558
|
+
if (!partsReady || destroyed) return;
|
|
473
559
|
unbindKeys?.();
|
|
474
560
|
slideNav?.destroy();
|
|
475
561
|
strip?.destroy();
|
|
476
562
|
slideNav = undefined;
|
|
477
563
|
strip = undefined;
|
|
564
|
+
// The Studio shows its filmstrip; the slide row is the Playground preset's, mounted only while that preset is on.
|
|
565
|
+
if (studioLayout && (studioCtl?.layout ?? layout) !== "playground") return;
|
|
478
566
|
if (parts.navigator && slots.navigator) {
|
|
479
567
|
slideNav = createSlideNavigator(slots.navigator, {
|
|
480
568
|
editor: ui,
|
|
@@ -545,7 +633,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
545
633
|
...(configured.cursorDelay !== undefined ? { cursorDelay: configured.cursorDelay } : {}),
|
|
546
634
|
...(configured.completions !== undefined ? { completions: configured.completions } : {}),
|
|
547
635
|
...(configured.catalogs !== undefined ? { catalogs: configured.catalogs } : {}),
|
|
548
|
-
...(configured.name !== undefined ? { name: configured.name } : {}),
|
|
636
|
+
...((requested.name ?? configured.name) !== undefined ? { name: requested.name ?? configured.name } : {}),
|
|
549
637
|
...(text !== undefined ? { source: text } : {}),
|
|
550
638
|
editor: ui,
|
|
551
639
|
format: configured.format ?? "json",
|
|
@@ -625,6 +713,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
625
713
|
);
|
|
626
714
|
canvas = createCanvasEditor(slots.canvas, {
|
|
627
715
|
...configured,
|
|
716
|
+
...(studioLayout ? { textEntry: layout === "playground" ? "click" : "dblclick", targets: layout === "playground" ? "interactive" : "passive", escape: "commit" } : {}),
|
|
628
717
|
editor: ui,
|
|
629
718
|
fonts,
|
|
630
719
|
renderOptions: renderOptions(),
|
|
@@ -635,17 +724,22 @@ export function createOpfEditor(element, options = {}) {
|
|
|
635
724
|
if (event.state === "error") {
|
|
636
725
|
fontsFailure = event.error;
|
|
637
726
|
say(event.error.message);
|
|
638
|
-
} else if (event.state === "loading")
|
|
727
|
+
} else if (event.state === "loading") {
|
|
728
|
+
fontsPending = true;
|
|
729
|
+
say(MESSAGES.loadingFonts);
|
|
730
|
+
}
|
|
639
731
|
else if (fontsFailure) {
|
|
640
732
|
fontsFailure = undefined;
|
|
641
733
|
refresh();
|
|
642
734
|
}
|
|
735
|
+
if (event.state === "ready") fontsPending = false;
|
|
643
736
|
emit("fonts", event);
|
|
644
737
|
configured.onFonts?.(event);
|
|
645
738
|
},
|
|
646
739
|
onSelect: (value) => {
|
|
647
740
|
selection = { path: value.path, value: value.value };
|
|
648
|
-
|
|
741
|
+
// The Studio emits its own selection (a list of nodes); the canvas's single selection is internal there.
|
|
742
|
+
if (!studioLayout) emit("selection", { path: value.path, value: value.value, slideIndex, element: value.element });
|
|
649
743
|
design?.refresh();
|
|
650
744
|
configured.onSelect?.(value);
|
|
651
745
|
},
|
|
@@ -783,7 +877,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
783
877
|
onFocusField: ({ fix, target }) => emit("review-focus", { fix, target }),
|
|
784
878
|
onStatus: say,
|
|
785
879
|
onChange: (counts) => {
|
|
786
|
-
const tab = controls.tabs?.review;
|
|
880
|
+
const tab = controls.tabs?.review ?? studioShell?.tabs.review?.tab;
|
|
787
881
|
if (tab) {
|
|
788
882
|
const name = options.labels?.review ?? "Review";
|
|
789
883
|
const found = !counts.unavailable && counts.total;
|
|
@@ -795,13 +889,17 @@ export function createOpfEditor(element, options = {}) {
|
|
|
795
889
|
});
|
|
796
890
|
}
|
|
797
891
|
|
|
798
|
-
function present() {
|
|
892
|
+
function present(from) {
|
|
893
|
+
if (waiting() && parts.present) {
|
|
894
|
+
queue("present", () => present(from));
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
799
897
|
if (destroyed || !mounted || !presenter || presenter.isRunning() || !inSync || slides.length === 0) return false;
|
|
800
898
|
// An open canvas edit is committed, and text typed a moment ago is applied, before the document is shown; a source that is not a valid deck waits.
|
|
801
899
|
if (canvas?.commit() === false) return false;
|
|
802
900
|
pane?.getText();
|
|
803
901
|
if (draftInvalid) return false;
|
|
804
|
-
void presenter.start(canvas?.slideIndex ?? slideIndex);
|
|
902
|
+
void presenter.start(Number.isInteger(from) ? from : (canvas?.slideIndex ?? slideIndex));
|
|
805
903
|
return true;
|
|
806
904
|
}
|
|
807
905
|
|
|
@@ -809,7 +907,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
809
907
|
slides = session.presentation.slides ?? [];
|
|
810
908
|
authoredTemplate = session.presentation.template === true;
|
|
811
909
|
if (parts.canvas && slots.canvas) mountCanvas();
|
|
812
|
-
thumbnailCache = createThumbnailCache({ toSvg, getOptions: () => ({ ...renderOptions(), fonts, text: "system", trace: false }) });
|
|
910
|
+
if (slideParts) thumbnailCache = createThumbnailCache({ toSvg, getOptions: () => ({ ...renderOptions(), fonts, text: "system", trace: false }) });
|
|
813
911
|
mountSlideRow();
|
|
814
912
|
if (parts.slides && slots.slides) slideManager = createSlideManager(slots.slides, { ...slideManagerOptions(), ...(slots.slideToolbar ? { toolbar: slots.slideToolbar } : {}) });
|
|
815
913
|
if (parts.source && slots.source) {
|
|
@@ -867,6 +965,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
867
965
|
if (parts.persistence) mountPersistence();
|
|
868
966
|
mountControls();
|
|
869
967
|
mountEnvironment();
|
|
968
|
+
if (studioShell && canvas) mountStudio();
|
|
870
969
|
// These come last, so a host hears a change after every part has. The refresh runs for every event, the catalogs' included.
|
|
871
970
|
cleanups.push(
|
|
872
971
|
session.subscribe((event) => {
|
|
@@ -890,23 +989,83 @@ export function createOpfEditor(element, options = {}) {
|
|
|
890
989
|
);
|
|
891
990
|
}
|
|
892
991
|
|
|
992
|
+
// CE-20: the Studio over the parts mounted above (src/studio.js).
|
|
993
|
+
function mountStudio() {
|
|
994
|
+
studioCtl = createStudio({
|
|
995
|
+
root,
|
|
996
|
+
element,
|
|
997
|
+
shell: studioShell,
|
|
998
|
+
slots,
|
|
999
|
+
session,
|
|
1000
|
+
ui,
|
|
1001
|
+
canvas,
|
|
1002
|
+
options,
|
|
1003
|
+
layout,
|
|
1004
|
+
hostCatalogs,
|
|
1005
|
+
get pane() {
|
|
1006
|
+
return pane;
|
|
1007
|
+
},
|
|
1008
|
+
get find() {
|
|
1009
|
+
return find;
|
|
1010
|
+
},
|
|
1011
|
+
get exportMenu() {
|
|
1012
|
+
return exportMenu;
|
|
1013
|
+
},
|
|
1014
|
+
get slideManager() {
|
|
1015
|
+
return slideManager;
|
|
1016
|
+
},
|
|
1017
|
+
get thumbnailCache() {
|
|
1018
|
+
return thumbnailCache;
|
|
1019
|
+
},
|
|
1020
|
+
get persistence() {
|
|
1021
|
+
return persistence;
|
|
1022
|
+
},
|
|
1023
|
+
get handle() {
|
|
1024
|
+
return handle;
|
|
1025
|
+
},
|
|
1026
|
+
slideIndex: () => slideIndex,
|
|
1027
|
+
go: (index) => go(index),
|
|
1028
|
+
present: (from) => present(from),
|
|
1029
|
+
say,
|
|
1030
|
+
emit,
|
|
1031
|
+
report,
|
|
1032
|
+
on,
|
|
1033
|
+
setView: (view) => setView(view),
|
|
1034
|
+
readOnly: () => readOnly,
|
|
1035
|
+
inSync: () => inSync,
|
|
1036
|
+
fontsLoading: () => fontsPending,
|
|
1037
|
+
catalogs,
|
|
1038
|
+
layoutFor: (deck, index) => layoutFor(deck, index),
|
|
1039
|
+
setDocument: (document, openOptions) => setDocument(document, openOptions),
|
|
1040
|
+
openFind: (openOptions) => {
|
|
1041
|
+
if (!find) return false;
|
|
1042
|
+
find.openPanel(openOptions ?? {});
|
|
1043
|
+
return true;
|
|
1044
|
+
},
|
|
1045
|
+
getName: () => pane?.getName(),
|
|
1046
|
+
setName: (name) => pane?.setName(name),
|
|
1047
|
+
layoutStudio: () => studio,
|
|
1048
|
+
mountSlideRow: () => mountSlideRow(),
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
893
1052
|
// The shell's own controls: history, views, tabs.
|
|
894
1053
|
function mountControls() {
|
|
895
1054
|
controls.undo?.addEventListener("click", () => undo());
|
|
896
1055
|
controls.redo?.addEventListener("click", () => redo());
|
|
897
1056
|
for (const [name, button] of Object.entries(controls.views ?? {})) button.addEventListener("click", () => setView(name, { focus: true }));
|
|
898
1057
|
for (const [name, tab] of Object.entries(controls.tabs ?? {})) {
|
|
899
|
-
tab.addEventListener("click", () =>
|
|
1058
|
+
tab.addEventListener("click", () => showPanelNow(name));
|
|
900
1059
|
tab.addEventListener("keydown", (event) => {
|
|
901
1060
|
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
902
1061
|
event.preventDefault();
|
|
903
1062
|
const names = Object.keys(controls.tabs);
|
|
904
1063
|
const at = names.indexOf(name);
|
|
905
1064
|
const next = event.key === "Home" ? 0 : event.key === "End" ? names.length - 1 : (at + (event.key === "ArrowRight" ? 1 : -1) + names.length) % names.length;
|
|
906
|
-
|
|
1065
|
+
showPanelNow(names[next], true);
|
|
907
1066
|
});
|
|
908
1067
|
}
|
|
909
|
-
if (controls.tabs)
|
|
1068
|
+
if (controls.tabs) showPanelNow(options.panel && controls.tabs[options.panel] ? options.panel : Object.keys(controls.tabs)[0]);
|
|
910
1069
|
if (options.shortcuts !== false) {
|
|
911
1070
|
// Ctrl/Cmd+Z and +Shift+Z undo and redo when nothing that edits text has focus (the code editor and the canvas's inputs keep their own).
|
|
912
1071
|
const onKey = (event) => {
|
|
@@ -943,8 +1102,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
943
1102
|
media.addEventListener("change", change);
|
|
944
1103
|
cleanups.push(() => media.removeEventListener("change", change));
|
|
945
1104
|
};
|
|
946
|
-
follow(options.density, "(pointer: coarse)", setDensity);
|
|
947
|
-
follow(options.exportMode, "(max-width: 900px)", setExportMode);
|
|
1105
|
+
follow("density" in requested ? requested.density : options.density, "(pointer: coarse)", setDensity);
|
|
1106
|
+
follow("exportMode" in requested ? requested.exportMode : options.exportMode, "(max-width: 900px)", setExportMode);
|
|
948
1107
|
}
|
|
949
1108
|
|
|
950
1109
|
// ---- the handle -------------------------------------------------------------------------------------------------------------
|
|
@@ -977,8 +1136,17 @@ export function createOpfEditor(element, options = {}) {
|
|
|
977
1136
|
studio?.setReadOnly(readOnly);
|
|
978
1137
|
root.toggleAttribute("data-readonly", readOnly);
|
|
979
1138
|
syncChrome();
|
|
1139
|
+
studioCtl?.refresh();
|
|
980
1140
|
}
|
|
981
1141
|
function setDensity(setting) {
|
|
1142
|
+
if (destroyed) return;
|
|
1143
|
+
// Before the parts mount the setting is kept; the mount reads it instead of the `density` option.
|
|
1144
|
+
if (!partsReady) {
|
|
1145
|
+
requested.density = setting;
|
|
1146
|
+
density = densityFor(setting);
|
|
1147
|
+
root.setAttribute("data-density", density);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
982
1150
|
const next = densityFor(setting);
|
|
983
1151
|
if (next === density) return;
|
|
984
1152
|
density = next;
|
|
@@ -990,6 +1158,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
990
1158
|
mountSlideRow();
|
|
991
1159
|
}
|
|
992
1160
|
function setExportMode(setting) {
|
|
1161
|
+
if (destroyed) return;
|
|
1162
|
+
if (!partsReady) requested.exportMode = setting;
|
|
993
1163
|
exportMode = exportModeFor(setting);
|
|
994
1164
|
exportMenu?.setMode(exportMode);
|
|
995
1165
|
}
|
|
@@ -1014,17 +1184,50 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1014
1184
|
} catch (error) {
|
|
1015
1185
|
if (error?.code !== "invalid-opf-edit") throw error;
|
|
1016
1186
|
// A document the session refuses is no document to open: show its text with its findings, as a draft the person can fix.
|
|
1017
|
-
if (pane && text !== undefined) {
|
|
1018
|
-
if (openOptions.format && pane.getFormat() !== openOptions.format) pane.setFormat(openOptions.format);
|
|
1019
|
-
const api = pane.editor.api;
|
|
1020
|
-
api.replace(0, api.getValue().length, text);
|
|
1021
|
-
pane.getText();
|
|
1022
|
-
if (openOptions.name !== undefined) pane.setName(openOptions.name);
|
|
1023
|
-
}
|
|
1187
|
+
if (pane && text !== undefined) pane.setSourceText(text, { ...(openOptions.format ? { format: openOptions.format } : {}), ...(openOptions.name !== undefined ? { name: openOptions.name } : {}) });
|
|
1024
1188
|
return false;
|
|
1025
1189
|
}
|
|
1026
1190
|
}
|
|
1191
|
+
// Raw text in the source pane (a file as it was read, a draft that does not parse), without converting it: what the person would get by
|
|
1192
|
+
// pasting it over everything. True when the session now holds its document; false when it stays a draft with its problems.
|
|
1193
|
+
function setSourceText(text, openOptions = {}) {
|
|
1194
|
+
if (destroyed || !mounted) throw new OPFEditorError("not-ready", "The editor is not mounted yet. Await `ready` first.");
|
|
1195
|
+
if (!pane) throw new OPFEditorError("no-source-pane", "This editor has no source pane.");
|
|
1196
|
+
if (readOnly) throw new OPFEditorError("read-only", "This editor is read-only.");
|
|
1197
|
+
if (typeof text !== "string") throw new TypeError("setSourceText needs the source text as a string.");
|
|
1198
|
+
// An open canvas edit is committed first; the text then replaces it.
|
|
1199
|
+
if (canvas?.commit() === false) return false;
|
|
1200
|
+
drafting = false;
|
|
1201
|
+
openedText = undefined;
|
|
1202
|
+
const applied = pane.setSourceText(text, {
|
|
1203
|
+
...(openOptions.format ? { format: openOptions.format } : {}),
|
|
1204
|
+
...(openOptions.name !== undefined ? { name: openOptions.name } : {}),
|
|
1205
|
+
});
|
|
1206
|
+
// Text the host loaded is not the person's work: it is not autosaved or warned about until it is edited.
|
|
1207
|
+
if (applied && openOptions.rebase !== false) persistence?.rebase();
|
|
1208
|
+
return applied;
|
|
1209
|
+
}
|
|
1027
1210
|
|
|
1211
|
+
// The Studio's command registry is not there before mount: `register` keeps the command and adds it when the Studio mounts (the function it
|
|
1212
|
+
// returns removes it either way); `list` is empty and `run` finds nothing yet.
|
|
1213
|
+
const preMountCommands = {
|
|
1214
|
+
list: () => [],
|
|
1215
|
+
run: () => false,
|
|
1216
|
+
register(command) {
|
|
1217
|
+
if (!waiting() || !studioLayout) return () => {};
|
|
1218
|
+
let stop;
|
|
1219
|
+
let withdrawn = false;
|
|
1220
|
+
const entry = queue(Symbol("command"), () => {
|
|
1221
|
+
if (!withdrawn) stop = studioCtl?.commands.register(command);
|
|
1222
|
+
});
|
|
1223
|
+
return () => {
|
|
1224
|
+
withdrawn = true;
|
|
1225
|
+
const at = queued.indexOf(entry);
|
|
1226
|
+
if (at >= 0) queued.splice(at, 1);
|
|
1227
|
+
stop?.();
|
|
1228
|
+
};
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1028
1231
|
let fontsRef = { value: undefined };
|
|
1029
1232
|
let releaseFonts;
|
|
1030
1233
|
let readyPromise;
|
|
@@ -1084,8 +1287,43 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1084
1287
|
get slideIndex() {
|
|
1085
1288
|
return slideIndex;
|
|
1086
1289
|
},
|
|
1290
|
+
/** The selection: the canvas's `{ path, value }` (or null); in the Studio, the list `[{ key, path, idPath, kind }]`. */
|
|
1087
1291
|
get selection() {
|
|
1088
|
-
return selection;
|
|
1292
|
+
return studioCtl ? studioCtl.selection : selection;
|
|
1293
|
+
},
|
|
1294
|
+
/** CE-20: the arrangement ("full", "canvas", "source", "slots", or the Studio's "studio" and "playground"). */
|
|
1295
|
+
get layout() {
|
|
1296
|
+
return studioCtl?.layout ?? layout;
|
|
1297
|
+
},
|
|
1298
|
+
/** CE-20: switch the Studio between "studio" and "playground" in place (same session, history, slide and selection). */
|
|
1299
|
+
setLayout(next) {
|
|
1300
|
+
if (studioCtl) return studioCtl.setLayout(next);
|
|
1301
|
+
// Before mount the switch is queued (true), as the Studio's own check of the name would refuse it later.
|
|
1302
|
+
if (!waiting() || !studioLayout) return false;
|
|
1303
|
+
if (next !== "studio" && next !== "playground") throw new RangeError('setLayout takes "studio" or "playground".');
|
|
1304
|
+
queue("layout", () => studioCtl?.setLayout(next));
|
|
1305
|
+
return true;
|
|
1306
|
+
},
|
|
1307
|
+
get zoom() {
|
|
1308
|
+
return studioCtl?.zoom ?? 1;
|
|
1309
|
+
},
|
|
1310
|
+
setZoom(value) {
|
|
1311
|
+
if (studioCtl) studioCtl.setZoom(value);
|
|
1312
|
+
else if (waiting() && studioLayout) queue("zoom", () => studioCtl?.setZoom(value));
|
|
1313
|
+
},
|
|
1314
|
+
fit(mode) {
|
|
1315
|
+
if (studioCtl) studioCtl.fit(mode);
|
|
1316
|
+
else if (waiting() && studioLayout) queue("zoom", () => studioCtl?.fit(mode));
|
|
1317
|
+
},
|
|
1318
|
+
openLayout(layoutId, openOptions = {}) {
|
|
1319
|
+
if (studioCtl) return studioCtl.openLayout(layoutId, openOptions);
|
|
1320
|
+
if (!waiting() || !studioLayout) return false;
|
|
1321
|
+
queue("openLayout", () => studioCtl?.openLayout(layoutId, openOptions));
|
|
1322
|
+
return true;
|
|
1323
|
+
},
|
|
1324
|
+
/** CE-20: the Studio's command registry (`list`, `run`, `register`); empty outside the Studio. Commands registered before mount are added when it mounts. */
|
|
1325
|
+
get commands() {
|
|
1326
|
+
return studioCtl?.commands ?? preMountCommands;
|
|
1089
1327
|
},
|
|
1090
1328
|
get view() {
|
|
1091
1329
|
return activeView;
|
|
@@ -1116,11 +1354,17 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1116
1354
|
return session.snapshot().presentation;
|
|
1117
1355
|
},
|
|
1118
1356
|
setDocument,
|
|
1357
|
+
setSourceText,
|
|
1119
1358
|
setView,
|
|
1120
1359
|
setSlide: go,
|
|
1121
1360
|
goTo,
|
|
1122
|
-
select(path,
|
|
1123
|
-
|
|
1361
|
+
select(path, selectOptions = {}) {
|
|
1362
|
+
if (waiting()) {
|
|
1363
|
+
queue("select", () => handle.select(path, selectOptions));
|
|
1364
|
+
return studioLayout ? true : null;
|
|
1365
|
+
}
|
|
1366
|
+
if (studioCtl) return studioCtl.selectTargets(path, selectOptions);
|
|
1367
|
+
return canvas?.reveal(path, { focus: Boolean(selectOptions.focus) }) ?? null;
|
|
1124
1368
|
},
|
|
1125
1369
|
showPanel,
|
|
1126
1370
|
present,
|
|
@@ -1136,6 +1380,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1136
1380
|
return canvas?.commit() !== false;
|
|
1137
1381
|
},
|
|
1138
1382
|
focus(target) {
|
|
1383
|
+
if (waiting()) {
|
|
1384
|
+
queue("focus", () => handle.focus(target));
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1139
1387
|
if ((target ?? (pane ? "source" : "canvas")) === "source") {
|
|
1140
1388
|
pane?.editor?.api?.focus?.();
|
|
1141
1389
|
return Boolean(pane);
|
|
@@ -1145,6 +1393,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1145
1393
|
return Boolean(node);
|
|
1146
1394
|
},
|
|
1147
1395
|
openFind(openOptions) {
|
|
1396
|
+
if (waiting() && parts.find) {
|
|
1397
|
+
queue("find", () => handle.openFind(openOptions));
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1148
1400
|
if (!find) return false;
|
|
1149
1401
|
find.openPanel(openOptions ?? {});
|
|
1150
1402
|
return true;
|
|
@@ -1157,10 +1409,12 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1157
1409
|
return { text, format: pane.getFormat(), draft: draftInvalid };
|
|
1158
1410
|
},
|
|
1159
1411
|
setName(name) {
|
|
1412
|
+
if (waiting()) requested.name = name;
|
|
1160
1413
|
pane?.setName(name);
|
|
1161
1414
|
},
|
|
1162
1415
|
getName() {
|
|
1163
|
-
return pane
|
|
1416
|
+
if (pane) return pane.getName();
|
|
1417
|
+
return waiting() ? (requested.name ?? options.source?.name) : undefined;
|
|
1164
1418
|
},
|
|
1165
1419
|
setReadOnly(value) {
|
|
1166
1420
|
readOnly = Boolean(value);
|
|
@@ -1187,6 +1441,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1187
1441
|
/* already gone */
|
|
1188
1442
|
}
|
|
1189
1443
|
}
|
|
1444
|
+
studioCtl?.destroy();
|
|
1190
1445
|
unbindKeys?.();
|
|
1191
1446
|
checking?.disconnect();
|
|
1192
1447
|
persistence?.destroy();
|
|
@@ -1214,17 +1469,23 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1214
1469
|
|
|
1215
1470
|
// ---- start ------------------------------------------------------------------------------------------------------------------
|
|
1216
1471
|
function mount(resolved) {
|
|
1472
|
+
if (studioLayout && !studioShell) buildStudio();
|
|
1217
1473
|
fonts = resolved;
|
|
1218
|
-
density
|
|
1219
|
-
|
|
1474
|
+
// A density, an export mode or a view the host chose before the parts mounted wins over the option.
|
|
1475
|
+
density = densityFor("density" in requested ? requested.density : options.density);
|
|
1476
|
+
exportMode = exportModeFor("exportMode" in requested ? requested.exportMode : options.exportMode);
|
|
1220
1477
|
root.setAttribute("data-density", density);
|
|
1478
|
+
partsReady = true;
|
|
1221
1479
|
mountParts();
|
|
1222
1480
|
mounted = true;
|
|
1223
1481
|
if (readOnly) applyReadOnly();
|
|
1224
1482
|
say(MESSAGES.ready);
|
|
1225
|
-
|
|
1483
|
+
const wantedView = "view" in requested ? requested.view : options.view;
|
|
1484
|
+
activeView = "slide";
|
|
1485
|
+
if (wantedView && wantedView !== "slide") setView(wantedView);
|
|
1226
1486
|
else markView("slide");
|
|
1227
1487
|
syncChrome();
|
|
1488
|
+
flushQueued();
|
|
1228
1489
|
}
|
|
1229
1490
|
readyPromise = new Promise((resolve, reject) => {
|
|
1230
1491
|
const start = (arrived) => {
|
|
@@ -1254,15 +1515,20 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1254
1515
|
};
|
|
1255
1516
|
try {
|
|
1256
1517
|
fontsRef = fontsSource(options.fonts);
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1518
|
+
const chunk = slideParts && !partsLoaded ? loadParts() : null;
|
|
1519
|
+
if (isThenable(fontsRef.value) || chunk) {
|
|
1520
|
+
if (isThenable(fontsRef.value)) say(MESSAGES.loadingFonts);
|
|
1521
|
+
Promise.all([fontsRef.value, chunk]).then(([resolved]) => {
|
|
1260
1522
|
try {
|
|
1261
1523
|
start(resolved);
|
|
1262
1524
|
} catch (error) {
|
|
1263
1525
|
fail(error);
|
|
1264
1526
|
}
|
|
1265
|
-
},
|
|
1527
|
+
}, (error) => {
|
|
1528
|
+
// The slide chunk did not load: fonts that did arrive are given back.
|
|
1529
|
+
Promise.resolve(fontsRef.value).then((resolved) => fontsRef.use(resolved).release?.(), () => {});
|
|
1530
|
+
fail(error);
|
|
1531
|
+
});
|
|
1266
1532
|
} else start(fontsRef.value);
|
|
1267
1533
|
} catch (error) {
|
|
1268
1534
|
fail(error);
|