@netless/app-slide 0.2.106-beta.5 → 0.2.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +18 -7
- package/dist/main.cjs.js +138 -138
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +407 -230
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +135 -135
- package/dist/main.iife.js.map +1 -1
- package/package.json +2 -2
- package/src/SlideController/index.ts +177 -97
- package/src/SlideDocsViewer/index.ts +20 -12
- package/src/index.ts +48 -37
- package/src/utils/freezer.ts +81 -13
- package/src/utils/logger.ts +35 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.2.106
|
|
3
|
+
"version": "0.2.106",
|
|
4
4
|
"main": "dist/main.cjs.js",
|
|
5
5
|
"module": "dist/main.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@juggle/resize-observer": "^3.3.1",
|
|
21
|
-
"@netless/slide": "1.4.62
|
|
21
|
+
"@netless/slide": "1.4.62",
|
|
22
22
|
"@types/color-string": "^1.5.2",
|
|
23
23
|
"color-string": "^1.9.1",
|
|
24
24
|
"jspdf": "2.5.1",
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
// 1. map slide events to ui
|
|
3
3
|
// 2. make sure to init correctly
|
|
4
4
|
// - the one with (context.isAddApp === true) should call renderSlide(1)
|
|
5
|
-
// - others
|
|
6
|
-
// - if none of above happen, force doing renderSlide(1) after timeout
|
|
5
|
+
// - others restore from sync state, or render page 1 locally if no state exists
|
|
7
6
|
// 3. send/receive slide sync events
|
|
8
7
|
// 4. automatically re-create scenes to sync strokes, a view must be existing
|
|
9
8
|
// 5. pages information are loaded dynamically by the slide package
|
|
10
9
|
|
|
11
|
-
import type { AppContext, Player, Room
|
|
10
|
+
import type { AppContext, Player, Room } from "@netless/window-manager";
|
|
12
11
|
import type { ISlideConfig, SyncEvent } from "@netless/slide";
|
|
13
12
|
import type { Attributes, MagixEvents, MagixPayload, SlideState } from "../typings";
|
|
14
13
|
import type { AppOptions } from "..";
|
|
@@ -73,16 +72,27 @@ function runNextSlideWebGLTask(): void {
|
|
|
73
72
|
slideWebGLRunning = true;
|
|
74
73
|
const startedAt = performance.now();
|
|
75
74
|
log("[Slide][player-queue] start", task.label, "pending", slideWebGLTasks.length);
|
|
76
|
-
void Promise.resolve()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
void Promise.resolve()
|
|
76
|
+
.then(task.run)
|
|
77
|
+
.then(
|
|
78
|
+
() => {
|
|
79
|
+
log(
|
|
80
|
+
"[Slide][player-queue] done",
|
|
81
|
+
task.label,
|
|
82
|
+
"ms",
|
|
83
|
+
Math.round(performance.now() - startedAt)
|
|
84
|
+
);
|
|
85
|
+
task.resolve();
|
|
86
|
+
},
|
|
87
|
+
error => {
|
|
88
|
+
log("[Slide][player-queue] failed", task.label, error);
|
|
89
|
+
task.reject(error);
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
.finally(() => {
|
|
93
|
+
slideWebGLRunning = false;
|
|
94
|
+
runNextSlideWebGLTask();
|
|
95
|
+
});
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
export function enqueueSlideWebGLTransition(
|
|
@@ -156,7 +166,7 @@ export class SlideControllerBase {
|
|
|
156
166
|
this.context = context;
|
|
157
167
|
this.room = context.getRoom();
|
|
158
168
|
this.player = this.room ? undefined : (context.getDisplayer() as Player);
|
|
159
|
-
setRoomLogger(
|
|
169
|
+
setRoomLogger(context);
|
|
160
170
|
// this.slide = this.createSlide(anchor, {
|
|
161
171
|
// whiteTracker: getRoomTracker(context.getDisplayer()),
|
|
162
172
|
// });
|
|
@@ -225,10 +235,14 @@ export class SlideControllerBase {
|
|
|
225
235
|
// otherwise, maybe this slide is just added, let the adder kick start first render
|
|
226
236
|
log("[Slide] init by renderSlide", 1);
|
|
227
237
|
slide.renderSlide(1);
|
|
238
|
+
} else if (taskId) {
|
|
239
|
+
// A previous add may have persisted the App before its first render.
|
|
240
|
+
// Render locally so a restored or read-only client can recover it.
|
|
241
|
+
void slide.doRenderSlide(1).catch(error => {
|
|
242
|
+
logger.error("[Slide] initial local render failed", context.appId, error);
|
|
243
|
+
});
|
|
228
244
|
}
|
|
229
|
-
//
|
|
230
|
-
// so anyway, we start polling the slide's "ready state"
|
|
231
|
-
// if in the next 20 seconds the slide is not ready, start render first page
|
|
245
|
+
// Keep tracking the first render for setup diagnostics.
|
|
232
246
|
this.pollReadyState();
|
|
233
247
|
}
|
|
234
248
|
|
|
@@ -333,9 +347,13 @@ export class SlideControllerBase {
|
|
|
333
347
|
return;
|
|
334
348
|
}
|
|
335
349
|
if (this._toFreeze === 1) {
|
|
336
|
-
this.freeze()
|
|
350
|
+
void this.freeze().catch(error =>
|
|
351
|
+
logger.error("[Slide] deferred freeze failed", this.context.appId, error)
|
|
352
|
+
);
|
|
337
353
|
} else if (this._toFreeze === -1) {
|
|
338
|
-
this.unfreeze()
|
|
354
|
+
void this.unfreeze().catch(error =>
|
|
355
|
+
logger.error("[Slide] deferred unfreeze failed", this.context.appId, error)
|
|
356
|
+
);
|
|
339
357
|
}
|
|
340
358
|
} else if (this.pollCount < MaxPollCount) {
|
|
341
359
|
this.pollCount++;
|
|
@@ -360,7 +378,7 @@ export class SlideControllerBase {
|
|
|
360
378
|
}
|
|
361
379
|
|
|
362
380
|
protected createSlide(anchor: HTMLDivElement, defaults: Partial<ISlideConfig> = {}) {
|
|
363
|
-
const options = this.context.getAppOptions() || {};
|
|
381
|
+
const options = (this.context.getAppOptions() || {}) as AppOptions;
|
|
364
382
|
const attribute = this.context.storage.state;
|
|
365
383
|
const slide = new Slide({
|
|
366
384
|
anchor,
|
|
@@ -387,7 +405,29 @@ export class SlideControllerBase {
|
|
|
387
405
|
resourceTimeout: options.resourceTimeout,
|
|
388
406
|
rtcAudio: options.rtcAudio,
|
|
389
407
|
useLocalCache: options.useLocalCache,
|
|
390
|
-
logger:
|
|
408
|
+
logger: {
|
|
409
|
+
...options.logger,
|
|
410
|
+
error: (message, ...details) => {
|
|
411
|
+
logger.error(message, ...details);
|
|
412
|
+
if (options.logger !== logger.roomLogger) {
|
|
413
|
+
try {
|
|
414
|
+
options.logger?.error?.(message, ...details);
|
|
415
|
+
} catch {
|
|
416
|
+
// Keep the App error even if a custom logger fails.
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
warn: (message, ...details) => {
|
|
421
|
+
logger.warn(message, ...details);
|
|
422
|
+
if (options.logger !== logger.roomLogger) {
|
|
423
|
+
try {
|
|
424
|
+
options.logger?.warn?.(message, ...details);
|
|
425
|
+
} catch {
|
|
426
|
+
// Keep the App warning even if a custom logger fails.
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
},
|
|
391
431
|
whiteTracker: defaults.whiteTracker,
|
|
392
432
|
timestamp: this.timestamp,
|
|
393
433
|
customLinks: attribute.customLinks,
|
|
@@ -416,19 +456,29 @@ export class SlideControllerBase {
|
|
|
416
456
|
if (!this.destroyed) {
|
|
417
457
|
log("[Slide] destroy slide (once)");
|
|
418
458
|
this.destroyed = true;
|
|
419
|
-
this.destroyPromise = enqueueSlideWebGLTransition(
|
|
420
|
-
|
|
421
|
-
(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
459
|
+
this.destroyPromise = enqueueSlideWebGLTransition(
|
|
460
|
+
() =>
|
|
461
|
+
new Promise<void>((resolve, reject) => {
|
|
462
|
+
try {
|
|
463
|
+
(
|
|
464
|
+
this.slide.destroy as (
|
|
465
|
+
onPlayerDestroyed?: () => void,
|
|
466
|
+
onError?: (error: unknown) => void
|
|
467
|
+
) => void
|
|
468
|
+
).call(
|
|
469
|
+
this.slide,
|
|
470
|
+
() => {
|
|
471
|
+
resolve();
|
|
472
|
+
},
|
|
473
|
+
reject
|
|
474
|
+
);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
reject(error);
|
|
477
|
+
}
|
|
478
|
+
}),
|
|
479
|
+
true,
|
|
480
|
+
`destroy:${this.context.appId}`
|
|
481
|
+
);
|
|
432
482
|
this.resolveDestroyedSignal();
|
|
433
483
|
}
|
|
434
484
|
return this.destroyPromise ?? Promise.resolve();
|
|
@@ -462,7 +512,7 @@ export class SlideControllerBase {
|
|
|
462
512
|
if (active && this.shouldBeActive()) await this.unfreeze();
|
|
463
513
|
else await this.freeze();
|
|
464
514
|
},
|
|
465
|
-
error => logger.
|
|
515
|
+
error => logger.error("[Slide] resource transition failed", this.context.appId, error)
|
|
466
516
|
);
|
|
467
517
|
|
|
468
518
|
private shouldBeActive = (): boolean => {
|
|
@@ -504,20 +554,29 @@ export class SlideControllerBase {
|
|
|
504
554
|
if (this.ready) {
|
|
505
555
|
log("[Slide] freeze", this.context.appId);
|
|
506
556
|
if (this.invisibleBehavior === "frozen") {
|
|
507
|
-
await enqueueSlideWebGLTransition(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
557
|
+
await enqueueSlideWebGLTransition(
|
|
558
|
+
() => {
|
|
559
|
+
if (this.destroyed) return Promise.resolve();
|
|
560
|
+
const destroyed = new Promise<void>((resolve, reject) => {
|
|
561
|
+
const result = (
|
|
562
|
+
this.slide.frozen as (
|
|
563
|
+
onPlayerDestroyed?: () => void,
|
|
564
|
+
onError?: (error: unknown) => void
|
|
565
|
+
) => void | Promise<void>
|
|
566
|
+
).call(
|
|
567
|
+
this.slide,
|
|
568
|
+
() => {
|
|
569
|
+
resolve();
|
|
570
|
+
},
|
|
571
|
+
reject
|
|
572
|
+
);
|
|
573
|
+
if (result) void result.then(resolve, reject);
|
|
574
|
+
});
|
|
575
|
+
return Promise.race([destroyed, this.destroyedSignal]);
|
|
576
|
+
},
|
|
577
|
+
false,
|
|
578
|
+
`freeze:${this.context.appId}`
|
|
579
|
+
);
|
|
521
580
|
} else {
|
|
522
581
|
this.slide.pause();
|
|
523
582
|
}
|
|
@@ -543,33 +602,46 @@ export class SlideControllerBase {
|
|
|
543
602
|
if (this.ready) {
|
|
544
603
|
log("[Slide] unfreeze", this.context.appId);
|
|
545
604
|
if (this.invisibleBehavior === "frozen") {
|
|
546
|
-
await enqueueSlideWebGLTransition(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
605
|
+
await enqueueSlideWebGLTransition(
|
|
606
|
+
() => {
|
|
607
|
+
if (this.destroyed) return Promise.resolve();
|
|
608
|
+
const created = new Promise<void>((resolve, reject) => {
|
|
609
|
+
const result = (
|
|
610
|
+
this.slide.release as (
|
|
611
|
+
onRestored?: () => void,
|
|
612
|
+
onPlayerCreated?: () => void,
|
|
613
|
+
onError?: (error: unknown) => void
|
|
614
|
+
) => void | Promise<void>
|
|
615
|
+
).call(
|
|
616
|
+
this.slide,
|
|
617
|
+
() => {
|
|
618
|
+
if (this.isLazySetupMode() && !this.shouldBeActive()) return;
|
|
619
|
+
this.slide.notifyFrameResize();
|
|
620
|
+
const state = this.context.storage.state.state;
|
|
621
|
+
if (state) {
|
|
622
|
+
log("[Slide] sync storage", JSON.stringify(state));
|
|
623
|
+
void this.slide.setSlideState(state).catch(error => {
|
|
624
|
+
this.resourceStateUnknown = true;
|
|
625
|
+
logger.error(
|
|
626
|
+
"[Slide] storage sync after release failed",
|
|
627
|
+
this.context.appId,
|
|
628
|
+
error
|
|
629
|
+
);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
() => {
|
|
634
|
+
resolve();
|
|
635
|
+
},
|
|
636
|
+
reject
|
|
637
|
+
);
|
|
638
|
+
if (result) void result.then(resolve, reject);
|
|
639
|
+
});
|
|
640
|
+
return Promise.race([created, this.destroyedSignal]);
|
|
641
|
+
},
|
|
642
|
+
false,
|
|
643
|
+
`release:${this.context.appId}`
|
|
644
|
+
);
|
|
573
645
|
} else {
|
|
574
646
|
this.slide.resume();
|
|
575
647
|
this.slide.notifyFrameResize();
|
|
@@ -614,7 +686,9 @@ export class SlideControllerBase {
|
|
|
614
686
|
}
|
|
615
687
|
if (state === "minimized") {
|
|
616
688
|
log("[Slide] freeze because app state is minimized");
|
|
617
|
-
this.freeze()
|
|
689
|
+
void this.freeze().catch(error =>
|
|
690
|
+
logger.error("[Slide] minimized freeze failed", this.context.appId, error)
|
|
691
|
+
);
|
|
618
692
|
}
|
|
619
693
|
};
|
|
620
694
|
|
|
@@ -629,7 +703,9 @@ export class SlideControllerBase {
|
|
|
629
703
|
}
|
|
630
704
|
if (appId === this.context.appId && status === "minimized") {
|
|
631
705
|
log("[Slide] freeze because app status is minimized");
|
|
632
|
-
this.freeze()
|
|
706
|
+
void this.freeze().catch(error =>
|
|
707
|
+
logger.error("[Slide] minimized freeze failed", this.context.appId, error)
|
|
708
|
+
);
|
|
633
709
|
}
|
|
634
710
|
};
|
|
635
711
|
|
|
@@ -658,26 +734,30 @@ export class SlideControllerBase {
|
|
|
658
734
|
};
|
|
659
735
|
|
|
660
736
|
protected onVisibilityChange = async () => {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
this.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
737
|
+
try {
|
|
738
|
+
this.visible = document.visibilityState === "visible";
|
|
739
|
+
if (this.isLazySetupMode() || this.managedActivity) {
|
|
740
|
+
await this.reconcileActivity().catch(() => undefined);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const appStatus = this.getAppStatus();
|
|
744
|
+
if (!this.visible) {
|
|
745
|
+
this.savedIsFrozen = this.isFrozen;
|
|
746
|
+
log("[Slide] freeze because tab becomes invisible");
|
|
747
|
+
if (!this.isFrozen) await this.freeze();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
673
750
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
751
|
+
if (appStatus === "minimized") {
|
|
752
|
+
log("[Slide] do nothing because app state is minimized");
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (!this.savedIsFrozen) {
|
|
756
|
+
log("[Slide] unfreeze because tab becomes visible", { savedIsFrozen: this.savedIsFrozen });
|
|
757
|
+
await this.unfreeze();
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
logger.error("[Slide] visibility transition failed", this.context.appId, error);
|
|
681
761
|
}
|
|
682
762
|
};
|
|
683
763
|
}
|
|
@@ -431,12 +431,12 @@ export class SlideDocsViewer {
|
|
|
431
431
|
if (this.slideController?.onRenderError) {
|
|
432
432
|
this.slideController.onRenderError(error, index);
|
|
433
433
|
}
|
|
434
|
-
logger.
|
|
434
|
+
logger.error("[Slide] render error", this.appId, error);
|
|
435
435
|
};
|
|
436
436
|
|
|
437
437
|
protected onRenderStart = () => {
|
|
438
|
-
logger.log("[Slide][notes] hide", this.appId, "scene", this.whiteboardView.focusScenePath);
|
|
439
438
|
this.$whiteboardView.classList.add(this.wrapClassName("wb-view-hidden"));
|
|
439
|
+
logger.log("[Slide][notes] hide", this.appId, "scene", this.whiteboardView.focusScenePath);
|
|
440
440
|
this.viewer.setPlaying();
|
|
441
441
|
};
|
|
442
442
|
|
|
@@ -448,15 +448,14 @@ export class SlideDocsViewer {
|
|
|
448
448
|
|
|
449
449
|
public onPageChanged = () => {
|
|
450
450
|
clearTimeout(this._onPageChangedTimer);
|
|
451
|
-
logger.log("[Slide][notes] show scheduled", this.appId, "scene", this.whiteboardView.focusScenePath);
|
|
452
451
|
this._onPageChangedTimer = setTimeout(this._onPageChanged, 200) as unknown as number;
|
|
453
452
|
};
|
|
454
453
|
|
|
455
454
|
protected _onPageChangedTimer = 0;
|
|
456
455
|
protected _onPageChanged = () => {
|
|
457
|
-
logger.log("[Slide][notes] show", this.appId, "scene", this.whiteboardView.focusScenePath);
|
|
458
456
|
this.$overlay.style.opacity = "";
|
|
459
457
|
this.$whiteboardView.classList.remove(this.wrapClassName("wb-view-hidden"));
|
|
458
|
+
logger.log("[Slide][notes] show", this.appId, "scene", this.whiteboardView.focusScenePath);
|
|
460
459
|
};
|
|
461
460
|
|
|
462
461
|
protected refreshPages = () => {
|
|
@@ -484,13 +483,19 @@ export class SlideDocsViewer {
|
|
|
484
483
|
this.offBoxSizeChange = undefined;
|
|
485
484
|
this.contentResizeObserver?.disconnect();
|
|
486
485
|
this.contentResizeObserver = undefined;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
486
|
+
try {
|
|
487
|
+
if (this.slideController) {
|
|
488
|
+
const controller = this.slideController;
|
|
489
|
+
this.slideController = null;
|
|
490
|
+
await controller.destroy();
|
|
491
|
+
}
|
|
492
|
+
} finally {
|
|
493
|
+
try {
|
|
494
|
+
this.viewer.unmount();
|
|
495
|
+
} finally {
|
|
496
|
+
this.resizableContainer.destroy(this.box);
|
|
497
|
+
}
|
|
491
498
|
}
|
|
492
|
-
this.viewer.unmount();
|
|
493
|
-
this.resizableContainer.destroy(this.box);
|
|
494
499
|
return this;
|
|
495
500
|
});
|
|
496
501
|
}
|
|
@@ -505,8 +510,11 @@ export class SlideDocsViewer {
|
|
|
505
510
|
if (!this.destroyPromise) {
|
|
506
511
|
this.destroyPromise = Promise.resolve().then(async () => {
|
|
507
512
|
this.sideEffect.flushAll();
|
|
508
|
-
|
|
509
|
-
|
|
513
|
+
try {
|
|
514
|
+
await this.unmount();
|
|
515
|
+
} finally {
|
|
516
|
+
this.viewer.destroy();
|
|
517
|
+
}
|
|
510
518
|
});
|
|
511
519
|
}
|
|
512
520
|
return this.destroyPromise;
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./SlideController";
|
|
18
18
|
import { SlideDocsViewer } from "./SlideDocsViewer";
|
|
19
19
|
import { apps, FreezerLength, addHooks, useFreezer } from "./utils/freezer";
|
|
20
|
-
import { log, logger } from "./utils/logger";
|
|
20
|
+
import { log, logger, setRoomLogger } from "./utils/logger";
|
|
21
21
|
import styles from "./style.scss?inline";
|
|
22
22
|
|
|
23
23
|
export type { PreviewParams } from "./SlidePreviewer";
|
|
@@ -130,6 +130,7 @@ export interface AppResult {
|
|
|
130
130
|
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
131
131
|
kind: "Slide",
|
|
132
132
|
setup(context) {
|
|
133
|
+
setRoomLogger(context);
|
|
133
134
|
console.log("[Slide] setup @ " + version);
|
|
134
135
|
|
|
135
136
|
if (context.getIsWritable()) {
|
|
@@ -185,7 +186,7 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
185
186
|
};
|
|
186
187
|
|
|
187
188
|
const mountSlideController = (options: MountSlideOptions): SlideController => {
|
|
188
|
-
const appOptions = context.getAppOptions() || {};
|
|
189
|
+
const appOptions = (context.getAppOptions() || {}) as AppOptions;
|
|
189
190
|
|
|
190
191
|
const slideController = new SlideController({
|
|
191
192
|
context,
|
|
@@ -211,22 +212,25 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
211
212
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
213
|
(window as any).slideController = slideController;
|
|
213
214
|
}
|
|
214
|
-
slideController.readyPromise
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
215
|
+
slideController.readyPromise
|
|
216
|
+
.then(options.onReady)
|
|
217
|
+
.then(() => {
|
|
218
|
+
// Teardown may have destroyed the box while this deferred callback
|
|
219
|
+
// was pending; syncing scenes against a destroyed app throws.
|
|
220
|
+
if (disposed) return;
|
|
221
|
+
const room = context.getRoom();
|
|
222
|
+
let synced = false;
|
|
223
|
+
if (room && context.getIsWritable()) {
|
|
224
|
+
syncSceneWithSlide(room, context, slideController.slide, baseScenePath);
|
|
225
|
+
synced = true;
|
|
226
|
+
}
|
|
227
|
+
const page = slideController.slide.slideState.currentSlideIndex;
|
|
228
|
+
log("[Slide] page to", page, synced ? "(synced)" : "", "(on ready)");
|
|
229
|
+
slideController.slide.on("renderEnd", options.onRenderEnd);
|
|
230
|
+
})
|
|
231
|
+
.catch(error => {
|
|
232
|
+
if (!disposed) logger.warn("[Slide] ready callback failed", context.appId, error);
|
|
233
|
+
});
|
|
230
234
|
return slideController;
|
|
231
235
|
};
|
|
232
236
|
|
|
@@ -330,9 +334,13 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
330
334
|
};
|
|
331
335
|
offDestroy = context.emitter.on("destroy", teardown);
|
|
332
336
|
|
|
333
|
-
const mounting = enqueueSlideWebGLTransition(
|
|
334
|
-
|
|
335
|
-
|
|
337
|
+
const mounting = enqueueSlideWebGLTransition(
|
|
338
|
+
() => {
|
|
339
|
+
if (!disposed) docsViewer?.mount();
|
|
340
|
+
},
|
|
341
|
+
false,
|
|
342
|
+
`setup:${context.appId}`
|
|
343
|
+
);
|
|
336
344
|
|
|
337
345
|
// Resolve once the SlideController finished its first render (renderEnd).
|
|
338
346
|
// New lazy hosts retain the real completion after the warning threshold.
|
|
@@ -505,22 +513,25 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
505
513
|
};
|
|
506
514
|
|
|
507
515
|
const setupReadyTimeout = appOptions?.setupReadyTimeout ?? DEFAULT_SLIDE_SETUP_READY_TIMEOUT;
|
|
508
|
-
return mounting
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
throw new Error("[Slide] first render
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
516
|
+
return mounting
|
|
517
|
+
.then(() => {
|
|
518
|
+
if (disposed) throw new Error("[Slide] disposed before first render");
|
|
519
|
+
return waitForFirstRender(setupReadyTimeout);
|
|
520
|
+
})
|
|
521
|
+
.then(async () => {
|
|
522
|
+
if (disposed) throw new Error("[Slide] disposed before first render");
|
|
523
|
+
if (firstRenderFailed) {
|
|
524
|
+
throw new Error("[Slide] first render failed before ready");
|
|
525
|
+
}
|
|
526
|
+
if (!disposed && !slideControllerRef?.ready) {
|
|
527
|
+
log("[Slide] setup ready wait timed out, slide keeps loading in background");
|
|
528
|
+
}
|
|
529
|
+
return appResult;
|
|
530
|
+
})
|
|
531
|
+
.catch(async error => {
|
|
532
|
+
if (!disposed) await teardown();
|
|
533
|
+
throw error;
|
|
534
|
+
}) as unknown as AppResult;
|
|
524
535
|
},
|
|
525
536
|
};
|
|
526
537
|
|