@netless/app-slide 0.2.105 → 0.2.106-beta.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/dist/index.d.ts +21 -9
- package/dist/main.cjs.js +272 -272
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +195 -48
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +283 -283
- package/dist/main.iife.js.map +1 -1
- package/package.json +3 -3
- package/src/DocsViewer/ResizableContainer.ts +7 -5
- package/src/SlideController/index.ts +10 -6
- package/src/SlideDocsViewer/index.ts +21 -9
- package/src/SlidePreviewer/index.ts +4 -2
- package/src/index.ts +125 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.106-beta.1",
|
|
4
4
|
"main": "dist/main.cjs.js",
|
|
5
5
|
"module": "dist/main.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"README-zh.md"
|
|
18
18
|
],
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@
|
|
20
|
+
"@juggle/resize-observer": "^3.3.1",
|
|
21
|
+
"@netless/slide": "1.4.61-beta.0",
|
|
21
22
|
"@types/color-string": "^1.5.2",
|
|
22
23
|
"color-string": "^1.9.1",
|
|
23
24
|
"jspdf": "2.5.1",
|
|
24
|
-
"@juggle/resize-observer": "^3.3.1",
|
|
25
25
|
"side-effect-manager": "^1.2.1",
|
|
26
26
|
"vanilla-lazyload": "^17.8.3"
|
|
27
27
|
},
|
|
@@ -35,6 +35,7 @@ export class ResizableContainer {
|
|
|
35
35
|
enableResize: boolean,
|
|
36
36
|
_box?: ReadonlyTeleBox
|
|
37
37
|
) {
|
|
38
|
+
void _box;
|
|
38
39
|
this.enableResize = enableResize;
|
|
39
40
|
this.parent = parent;
|
|
40
41
|
this.context = context;
|
|
@@ -130,9 +131,6 @@ export class ResizableContainer {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
this.translateX = 0.5;
|
|
134
|
-
this.translateY = 0.5;
|
|
135
|
-
|
|
136
134
|
this.renderScrollBar(
|
|
137
135
|
parentBounds.width,
|
|
138
136
|
parentBounds.width * this.scale,
|
|
@@ -141,7 +139,8 @@ export class ResizableContainer {
|
|
|
141
139
|
);
|
|
142
140
|
this.handleNormalizeTranslate(this.translateX, this.translateY, {
|
|
143
141
|
triggerScrollBar: true,
|
|
144
|
-
triggerSync:
|
|
142
|
+
triggerSync: false,
|
|
143
|
+
force: true,
|
|
145
144
|
});
|
|
146
145
|
|
|
147
146
|
// enableScale / scaleView 只改变内部 container 的像素尺寸,box 本身不变。
|
|
@@ -158,12 +157,14 @@ export class ResizableContainer {
|
|
|
158
157
|
options: {
|
|
159
158
|
triggerScrollBar: boolean;
|
|
160
159
|
triggerSync: boolean;
|
|
160
|
+
force?: boolean;
|
|
161
161
|
}
|
|
162
162
|
) {
|
|
163
163
|
if (
|
|
164
164
|
Math.abs(x - this.translateX) < 0.001 &&
|
|
165
165
|
Math.abs(y - this.translateY) < 0.001 &&
|
|
166
|
-
!options.triggerSync
|
|
166
|
+
!options.triggerSync &&
|
|
167
|
+
!options.force
|
|
167
168
|
) {
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
@@ -230,6 +231,7 @@ export class ResizableContainer {
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
public destroy(_box?: ReadonlyTeleBox): void {
|
|
234
|
+
void _box;
|
|
233
235
|
this.cancelScheduledLayoutUpdate();
|
|
234
236
|
this.onLayoutUpdated = null;
|
|
235
237
|
this.scaleChangedListeners.clear();
|
|
@@ -355,14 +355,16 @@ export class SlideControllerBase {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
protected destroyed = false;
|
|
358
|
+
protected destroyPromise?: Promise<void>;
|
|
358
359
|
|
|
359
|
-
public destroy() {
|
|
360
|
+
public destroy(): Promise<void> {
|
|
360
361
|
this.sideEffect.flushAll();
|
|
361
362
|
if (!this.destroyed) {
|
|
362
363
|
log("[Slide] destroy slide (once)");
|
|
363
|
-
this.slide.destroy();
|
|
364
364
|
this.destroyed = true;
|
|
365
|
+
this.destroyPromise = Promise.resolve(this.slide.destroy());
|
|
365
366
|
}
|
|
367
|
+
return this.destroyPromise ?? Promise.resolve();
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
public timestamp = () => {
|
|
@@ -378,28 +380,30 @@ export class SlideControllerBase {
|
|
|
378
380
|
public isFrozen = false;
|
|
379
381
|
protected _toFreeze: -1 | 0 | 1 = 0; // -1: unfreeze, 0: no change, 1: freeze
|
|
380
382
|
|
|
381
|
-
public freeze = () => {
|
|
383
|
+
public freeze = (): Promise<void> => {
|
|
382
384
|
this.isFrozen = true;
|
|
383
385
|
if (this.ready) {
|
|
384
386
|
log("[Slide] freeze", this.context.appId);
|
|
385
387
|
if (this.invisibleBehavior === "frozen") {
|
|
386
|
-
this.slide.frozen();
|
|
388
|
+
return Promise.resolve(this.slide.frozen());
|
|
387
389
|
} else {
|
|
388
390
|
this.slide.pause();
|
|
389
391
|
}
|
|
390
392
|
} else {
|
|
391
393
|
this._toFreeze = 1;
|
|
392
394
|
}
|
|
395
|
+
return Promise.resolve();
|
|
393
396
|
};
|
|
394
397
|
|
|
395
|
-
public unfreeze = async () => {
|
|
398
|
+
public unfreeze = async (): Promise<void> => {
|
|
396
399
|
if (!this.visible) return;
|
|
400
|
+
if (!this.isFrozen && this.ready) return;
|
|
397
401
|
this.isFrozen = false;
|
|
398
402
|
if (this.ready) {
|
|
399
403
|
let isNeedSyncState = false;
|
|
400
404
|
log("[Slide] unfreeze", this.context.appId);
|
|
401
405
|
if (this.invisibleBehavior === "frozen") {
|
|
402
|
-
this.slide.release(() => {
|
|
406
|
+
await this.slide.release(() => {
|
|
403
407
|
// release 会重建 player,内部 observer 已关闭,需要按当前 frame 尺寸重绘
|
|
404
408
|
this.slide.notifyFrameResize();
|
|
405
409
|
});
|
|
@@ -163,6 +163,13 @@ export class SlideDocsViewer {
|
|
|
163
163
|
console.log("[SlideDocsViewer] No initial slideScale found");
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
const initialTranslateX = this.context.storage.state.translateX ?? 0.5;
|
|
167
|
+
const initialTranslateY = this.context.storage.state.translateY ?? 0.5;
|
|
168
|
+
this.resizableContainer.handleNormalizeTranslate(initialTranslateX, initialTranslateY, {
|
|
169
|
+
triggerScrollBar: true,
|
|
170
|
+
triggerSync: false,
|
|
171
|
+
});
|
|
172
|
+
|
|
166
173
|
const handler: StorageStateChangedListener<Attributes> = diff => {
|
|
167
174
|
if (diff.slideScale !== undefined) {
|
|
168
175
|
// slideScale 是一个包含 newValue 和 oldValue 的对象
|
|
@@ -221,7 +228,7 @@ export class SlideDocsViewer {
|
|
|
221
228
|
this.viewer.$content,
|
|
222
229
|
this.context,
|
|
223
230
|
this.enableScale,
|
|
224
|
-
this.box
|
|
231
|
+
this.box
|
|
225
232
|
);
|
|
226
233
|
this.resizableContainer.onLayoutUpdated = () => {
|
|
227
234
|
this.scaleDocsToFit();
|
|
@@ -384,7 +391,10 @@ export class SlideDocsViewer {
|
|
|
384
391
|
this.latestBoxSize = { width: payload.width, height: payload.height };
|
|
385
392
|
|
|
386
393
|
// observer 已经先处理过同一尺寸时,忽略晚到的 event
|
|
387
|
-
if (
|
|
394
|
+
if (
|
|
395
|
+
sizesEqual(this.lastObserverSize, this.latestBoxSize) ||
|
|
396
|
+
sizesEqual(this.lastAppliedSize, this.latestBoxSize)
|
|
397
|
+
) {
|
|
388
398
|
console.log("[app-slide] boxSizeChange skipped, observer already applied", {
|
|
389
399
|
appId: this.appId,
|
|
390
400
|
eventCount: this.boxSizeEventCount,
|
|
@@ -455,14 +465,15 @@ export class SlideDocsViewer {
|
|
|
455
465
|
return (page > 0 ? page : 1) - 1;
|
|
456
466
|
}
|
|
457
467
|
|
|
458
|
-
public unmount() {
|
|
468
|
+
public async unmount(): Promise<this> {
|
|
459
469
|
this.offBoxSizeChange?.();
|
|
460
470
|
this.offBoxSizeChange = undefined;
|
|
461
471
|
this.contentResizeObserver?.disconnect();
|
|
462
472
|
this.contentResizeObserver = undefined;
|
|
463
473
|
if (this.slideController) {
|
|
464
|
-
this.slideController
|
|
474
|
+
const controller = this.slideController;
|
|
465
475
|
this.slideController = null;
|
|
476
|
+
await controller.destroy();
|
|
466
477
|
}
|
|
467
478
|
this.viewer.unmount();
|
|
468
479
|
this.resizableContainer.destroy(this.box);
|
|
@@ -473,9 +484,9 @@ export class SlideDocsViewer {
|
|
|
473
484
|
this.viewer.setReadonly(readonly);
|
|
474
485
|
}
|
|
475
486
|
|
|
476
|
-
public destroy() {
|
|
487
|
+
public async destroy(): Promise<void> {
|
|
477
488
|
this.sideEffect.flushAll();
|
|
478
|
-
this.unmount();
|
|
489
|
+
await this.unmount();
|
|
479
490
|
this.viewer.destroy();
|
|
480
491
|
}
|
|
481
492
|
|
|
@@ -489,9 +500,10 @@ export class SlideDocsViewer {
|
|
|
489
500
|
const { width: slideWidth, height: slideHeight } = this.slideController.slide;
|
|
490
501
|
if (slideWidth && slideHeight) {
|
|
491
502
|
const originSize = this.context.storage.state.originSize;
|
|
492
|
-
const { width, height } =
|
|
493
|
-
|
|
494
|
-
|
|
503
|
+
const { width, height } =
|
|
504
|
+
originSize && originSize.width > 0 && originSize.height > 0
|
|
505
|
+
? originSize
|
|
506
|
+
: { width: slideWidth, height: slideHeight };
|
|
495
507
|
if (width > 0 && height > 0) {
|
|
496
508
|
this.whiteboardView.moveCameraToContain({
|
|
497
509
|
originX: -width / 2,
|
|
@@ -184,12 +184,14 @@ export class SlidePreviewer {
|
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
protected destroyed = false;
|
|
187
|
-
|
|
187
|
+
protected destroyPromise?: Promise<void>;
|
|
188
|
+
public async destroy(): Promise<void> {
|
|
188
189
|
this.sideEffect.flushAll();
|
|
189
190
|
if (this.slide && !this.destroyed) {
|
|
190
|
-
this.slide.destroy();
|
|
191
191
|
this.destroyed = true;
|
|
192
|
+
this.destroyPromise = Promise.resolve(this.slide.destroy());
|
|
192
193
|
}
|
|
194
|
+
await this.destroyPromise;
|
|
193
195
|
this.viewer.destroy();
|
|
194
196
|
}
|
|
195
197
|
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,8 @@ export const usePlugin: (plugin: any) => any = /* @__PURE__ */ Slide.usePlugin.b
|
|
|
37
37
|
|
|
38
38
|
export const version = __APP_VERSION__;
|
|
39
39
|
|
|
40
|
+
const DEFAULT_SLIDE_SETUP_READY_TIMEOUT = 5_000;
|
|
41
|
+
|
|
40
42
|
export { DefaultUrl, apps, FreezerLength, addHooks, useFreezer, log, logger };
|
|
41
43
|
|
|
42
44
|
export { setFreezerLength, getFreezerLength, onCreated, onDestroyed } from "./utils/freezer";
|
|
@@ -87,6 +89,11 @@ export interface AppOptions
|
|
|
87
89
|
enableScale?: boolean;
|
|
88
90
|
resourceMaxRetries?: number;
|
|
89
91
|
onResourceMaxRetries: (url: string, error: Error) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Max time (ms) `setup()` waits for the first `renderEnd` before resolving
|
|
94
|
+
* anyway (the slide keeps loading in background). Default: 5_000.
|
|
95
|
+
*/
|
|
96
|
+
setupReadyTimeout?: number;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
export interface ILogger {
|
|
@@ -115,7 +122,9 @@ export interface AppResult {
|
|
|
115
122
|
playPptMedia: () => void;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult>
|
|
125
|
+
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
126
|
+
teardown(context: import("@netless/window-manager").AppContext): Promise<void>;
|
|
127
|
+
} = {
|
|
119
128
|
kind: "Slide",
|
|
120
129
|
setup(context) {
|
|
121
130
|
console.log("[Slide] setup @ " + version);
|
|
@@ -133,6 +142,8 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
133
142
|
throw new Error("[Slide] no view, please set scenePath on addApp()");
|
|
134
143
|
}
|
|
135
144
|
view.disableCameraTransform = true;
|
|
145
|
+
const isLazySetupMode = (): boolean =>
|
|
146
|
+
(context.getWindowManager() as any)?.lazySetupInMaximizedMode === true;
|
|
136
147
|
|
|
137
148
|
const box = context.getBox();
|
|
138
149
|
box.mountStyles(styles);
|
|
@@ -146,6 +157,10 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
146
157
|
const baseScenePath = context.getInitScenePath() as string;
|
|
147
158
|
|
|
148
159
|
let docsViewer: SlideDocsViewer | null = null;
|
|
160
|
+
// Serial setup queue support: capture the controller so setup() can await
|
|
161
|
+
// its first render before WindowManager starts the next app's setup.
|
|
162
|
+
let slideControllerRef: SlideController | null | undefined;
|
|
163
|
+
let firstRenderFailed = false;
|
|
149
164
|
|
|
150
165
|
const onPageChanged = (page: number) => {
|
|
151
166
|
const room = context.getRoom();
|
|
@@ -174,17 +189,29 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
174
189
|
...options,
|
|
175
190
|
onPageChanged,
|
|
176
191
|
onNavigate: options.onNavigate,
|
|
177
|
-
|
|
192
|
+
// Wrap the user callback: a render failure before the first
|
|
193
|
+
// renderEnd also fails the setup wait (serial setup queue).
|
|
194
|
+
onRenderError: (error, pageIndex) => {
|
|
195
|
+
firstRenderFailed = true;
|
|
196
|
+
appOptions.onRenderError?.(error, pageIndex);
|
|
197
|
+
},
|
|
178
198
|
showRenderError: appOptions.showRenderError,
|
|
179
199
|
invisibleBehavior: appOptions.invisibleBehavior,
|
|
180
200
|
});
|
|
181
|
-
|
|
201
|
+
slideControllerRef = slideController;
|
|
202
|
+
// The legacy addHooks freezer is not awaitable across App instances.
|
|
203
|
+
// Lazy WindowManager mode owns freeze/release ordering through the
|
|
204
|
+
// per-App focus lifecycle instead.
|
|
205
|
+
if (useFreezer && !isLazySetupMode()) apps.set(context.appId, slideController, box);
|
|
182
206
|
logger.setAppController(context.appId, slideController);
|
|
183
207
|
if (import.meta.env.DEV) {
|
|
184
208
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
209
|
(window as any).slideController = slideController;
|
|
186
210
|
}
|
|
187
211
|
slideController.readyPromise.then(options.onReady).then(() => {
|
|
212
|
+
// Teardown may have destroyed the box while this deferred callback
|
|
213
|
+
// was pending; syncing scenes against a destroyed app throws.
|
|
214
|
+
if (disposed) return;
|
|
188
215
|
const room = context.getRoom();
|
|
189
216
|
let synced = false;
|
|
190
217
|
if (room && context.getIsWritable()) {
|
|
@@ -249,19 +276,91 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
249
276
|
});
|
|
250
277
|
}
|
|
251
278
|
|
|
252
|
-
|
|
279
|
+
// Blur freeze (same semantics as the Presentation blur thumbnail
|
|
280
|
+
// degradation): while the app is cached and loses focus in a
|
|
281
|
+
// maximized/minimized workspace, freeze the slide (destroy the player,
|
|
282
|
+
// keep a snapshot); on focus, unfreeze. Freezing is skipped in normal
|
|
283
|
+
// mode where several windows are visible side by side. The whole flow is
|
|
284
|
+
// a lazy-mode-only cache optimization: inactive unless the WindowManager
|
|
285
|
+
// runs with lazySetupInMaximizedMode enabled (read dynamically — lazy can
|
|
286
|
+
// be disabled at runtime, e.g. when forceMaximized is cleared).
|
|
287
|
+
const isBlurFreezeAllowed = (): boolean => {
|
|
288
|
+
const boxStatus = context.getBoxStatus();
|
|
289
|
+
if (boxStatus) return boxStatus !== "normal";
|
|
290
|
+
const boxState = context.getWindowManager()?.boxState;
|
|
291
|
+
return boxState != null && boxState !== "normal";
|
|
292
|
+
};
|
|
293
|
+
sideEffect.add(() =>
|
|
294
|
+
context.emitter.on("focus", async (isFocused: boolean) => {
|
|
295
|
+
if (disposed) return;
|
|
296
|
+
if (!isLazySetupMode()) return;
|
|
297
|
+
const controller = docsViewer?.slideController;
|
|
298
|
+
if (!controller) return;
|
|
299
|
+
if (!isFocused) {
|
|
300
|
+
if (!isBlurFreezeAllowed()) return;
|
|
301
|
+
if (!controller.isFrozen) {
|
|
302
|
+
log("[Slide] blur freeze", context.appId);
|
|
303
|
+
await controller.freeze();
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (controller.isFrozen) {
|
|
308
|
+
log("[Slide] focus unfreeze", context.appId);
|
|
309
|
+
await controller.unfreeze();
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
let disposed = false;
|
|
315
|
+
let offDestroy: (() => void) | undefined;
|
|
316
|
+
const teardown = async (): Promise<void> => {
|
|
317
|
+
if (disposed) return;
|
|
318
|
+
disposed = true;
|
|
319
|
+
const removeDestroy = offDestroy;
|
|
320
|
+
offDestroy = undefined;
|
|
321
|
+
removeDestroy?.();
|
|
253
322
|
log("[Slide] destroy", context.appId);
|
|
254
323
|
if (useFreezer) apps.delete(context.appId);
|
|
255
324
|
sideEffect.flushAll();
|
|
256
325
|
if (docsViewer) {
|
|
257
|
-
docsViewer
|
|
326
|
+
const viewer = docsViewer;
|
|
258
327
|
docsViewer = null;
|
|
328
|
+
await viewer.destroy();
|
|
259
329
|
}
|
|
260
|
-
}
|
|
330
|
+
};
|
|
331
|
+
offDestroy = context.emitter.on("destroy", teardown);
|
|
261
332
|
|
|
262
333
|
docsViewer.mount();
|
|
263
334
|
|
|
264
|
-
|
|
335
|
+
(SlideApp as any).__teardownByContext ||= new WeakMap<object, () => Promise<void>>();
|
|
336
|
+
(SlideApp as any).__teardownByContext.set(context, teardown);
|
|
337
|
+
|
|
338
|
+
// Resolve once the SlideController finished its first render (renderEnd).
|
|
339
|
+
// `false` means timeout or teardown; the slide then keeps loading in the
|
|
340
|
+
// background without blocking WindowManager's serial setup queue.
|
|
341
|
+
const waitForFirstRender = (timeoutMs: number): Promise<boolean> =>
|
|
342
|
+
new Promise<boolean>(resolve => {
|
|
343
|
+
let settled = false;
|
|
344
|
+
const settle = (loaded: boolean) => {
|
|
345
|
+
if (settled) return;
|
|
346
|
+
settled = true;
|
|
347
|
+
window.clearTimeout(timeoutTimer);
|
|
348
|
+
window.clearInterval(pollTimer);
|
|
349
|
+
resolve(loaded);
|
|
350
|
+
};
|
|
351
|
+
const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
|
|
352
|
+
// Success path resolves straight from readyPromise (no polling lag);
|
|
353
|
+
// the poll only catches teardown and pre-ready render failures.
|
|
354
|
+
slideControllerRef?.readyPromise.then(
|
|
355
|
+
() => settle(!firstRenderFailed),
|
|
356
|
+
() => settle(false)
|
|
357
|
+
);
|
|
358
|
+
const pollTimer = window.setInterval(() => {
|
|
359
|
+
if (disposed || firstRenderFailed) settle(false);
|
|
360
|
+
}, 100);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const appResult: AppResult = {
|
|
265
364
|
onPptMediaPermissionRequest: callback => {
|
|
266
365
|
const slide = docsViewer?.slideController?.slide;
|
|
267
366
|
return slide?.onPptMediaPermissionRequest(callback) ?? (() => undefined);
|
|
@@ -286,6 +385,10 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
286
385
|
translateX: 0.5,
|
|
287
386
|
translateY: 0.5,
|
|
288
387
|
});
|
|
388
|
+
container.handleNormalizeTranslate(0.5, 0.5, {
|
|
389
|
+
triggerScrollBar: true,
|
|
390
|
+
triggerSync: false,
|
|
391
|
+
});
|
|
289
392
|
container.scaleContainer(applyScale);
|
|
290
393
|
},
|
|
291
394
|
getViewScale: () => {
|
|
@@ -392,6 +495,21 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
392
495
|
return false;
|
|
393
496
|
},
|
|
394
497
|
};
|
|
498
|
+
|
|
499
|
+
const setupReadyTimeout = appOptions?.setupReadyTimeout ?? DEFAULT_SLIDE_SETUP_READY_TIMEOUT;
|
|
500
|
+
return waitForFirstRender(setupReadyTimeout).then(() => {
|
|
501
|
+
if (firstRenderFailed) {
|
|
502
|
+
throw new Error("[Slide] first render failed before ready");
|
|
503
|
+
}
|
|
504
|
+
if (!disposed && !slideControllerRef?.ready) {
|
|
505
|
+
log("[Slide] setup ready wait timed out, slide keeps loading in background");
|
|
506
|
+
}
|
|
507
|
+
return appResult;
|
|
508
|
+
}) as unknown as AppResult;
|
|
509
|
+
},
|
|
510
|
+
async teardown(context) {
|
|
511
|
+
await (SlideApp as any).__teardownByContext?.get(context)?.();
|
|
512
|
+
(SlideApp as any).__teardownByContext?.delete(context);
|
|
395
513
|
},
|
|
396
514
|
};
|
|
397
515
|
|