@netless/app-slide 0.2.105-beta.0 → 0.2.105-beta.2
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 +5 -0
- package/dist/main.cjs.js +117 -117
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +84 -4
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +139 -139
- package/dist/main.iife.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +100 -2
package/package.json
CHANGED
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 {
|
|
@@ -146,6 +153,10 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
146
153
|
const baseScenePath = context.getInitScenePath() as string;
|
|
147
154
|
|
|
148
155
|
let docsViewer: SlideDocsViewer | null = null;
|
|
156
|
+
// Serial setup queue support: capture the controller so setup() can await
|
|
157
|
+
// its first render before WindowManager starts the next app's setup.
|
|
158
|
+
let slideControllerRef: SlideController | null | undefined;
|
|
159
|
+
let firstRenderFailed = false;
|
|
149
160
|
|
|
150
161
|
const onPageChanged = (page: number) => {
|
|
151
162
|
const room = context.getRoom();
|
|
@@ -174,10 +185,16 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
174
185
|
...options,
|
|
175
186
|
onPageChanged,
|
|
176
187
|
onNavigate: options.onNavigate,
|
|
177
|
-
|
|
188
|
+
// Wrap the user callback: a render failure before the first
|
|
189
|
+
// renderEnd also fails the setup wait (serial setup queue).
|
|
190
|
+
onRenderError: (error, pageIndex) => {
|
|
191
|
+
firstRenderFailed = true;
|
|
192
|
+
appOptions.onRenderError?.(error, pageIndex);
|
|
193
|
+
},
|
|
178
194
|
showRenderError: appOptions.showRenderError,
|
|
179
195
|
invisibleBehavior: appOptions.invisibleBehavior,
|
|
180
196
|
});
|
|
197
|
+
slideControllerRef = slideController;
|
|
181
198
|
if (useFreezer) apps.set(context.appId, slideController, box);
|
|
182
199
|
logger.setAppController(context.appId, slideController);
|
|
183
200
|
if (import.meta.env.DEV) {
|
|
@@ -185,6 +202,9 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
185
202
|
(window as any).slideController = slideController;
|
|
186
203
|
}
|
|
187
204
|
slideController.readyPromise.then(options.onReady).then(() => {
|
|
205
|
+
// Teardown may have destroyed the box while this deferred callback
|
|
206
|
+
// was pending; syncing scenes against a destroyed app throws.
|
|
207
|
+
if (disposed) return;
|
|
188
208
|
const room = context.getRoom();
|
|
189
209
|
let synced = false;
|
|
190
210
|
if (room && context.getIsWritable()) {
|
|
@@ -249,6 +269,46 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
249
269
|
});
|
|
250
270
|
}
|
|
251
271
|
|
|
272
|
+
// Blur freeze (same semantics as the Presentation blur thumbnail
|
|
273
|
+
// degradation): while the app is cached and loses focus in a
|
|
274
|
+
// maximized/minimized workspace, freeze the slide (destroy the player,
|
|
275
|
+
// keep a snapshot); on focus, unfreeze. Freezing is skipped in normal
|
|
276
|
+
// mode where several windows are visible side by side. The whole flow is
|
|
277
|
+
// a lazy-mode-only cache optimization: inactive unless the WindowManager
|
|
278
|
+
// runs with lazySetupInMaximizedMode enabled (read dynamically — lazy can
|
|
279
|
+
// be disabled at runtime, e.g. when forceMaximized is cleared).
|
|
280
|
+
const isLazySetupMode = (): boolean =>
|
|
281
|
+
(context.getWindowManager() as any)?.lazySetupInMaximizedMode === true;
|
|
282
|
+
const isBlurFreezeAllowed = (): boolean => {
|
|
283
|
+
const boxStatus = context.getBoxStatus();
|
|
284
|
+
if (boxStatus) return boxStatus !== "normal";
|
|
285
|
+
const boxState = context.getWindowManager()?.boxState;
|
|
286
|
+
return boxState != null && boxState !== "normal";
|
|
287
|
+
};
|
|
288
|
+
sideEffect.add(
|
|
289
|
+
() =>
|
|
290
|
+
context.emitter.on("focus", (isFocused: boolean) => {
|
|
291
|
+
if (disposed) return;
|
|
292
|
+
if (!isLazySetupMode()) return;
|
|
293
|
+
const controller = docsViewer?.slideController;
|
|
294
|
+
if (!controller) return;
|
|
295
|
+
if (!isFocused) {
|
|
296
|
+
if (!isBlurFreezeAllowed()) return;
|
|
297
|
+
if (!controller.isFrozen) {
|
|
298
|
+
log("[Slide] blur freeze", context.appId);
|
|
299
|
+
controller.freeze();
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (useFreezer) {
|
|
304
|
+
apps.focus(context.appId);
|
|
305
|
+
} else if (controller.isFrozen) {
|
|
306
|
+
log("[Slide] focus unfreeze", context.appId);
|
|
307
|
+
controller.unfreeze();
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
|
|
252
312
|
let disposed = false;
|
|
253
313
|
let offDestroy: (() => void) | undefined;
|
|
254
314
|
const teardown = () => {
|
|
@@ -272,7 +332,33 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
272
332
|
(SlideApp as any).__teardownByContext ||= new WeakMap<object, () => void>();
|
|
273
333
|
(SlideApp as any).__teardownByContext.set(context, teardown);
|
|
274
334
|
|
|
275
|
-
|
|
335
|
+
// Resolve once the SlideController finished its first render (renderEnd).
|
|
336
|
+
// `false` means timeout or teardown; the slide then keeps loading in the
|
|
337
|
+
// background without blocking WindowManager's serial setup queue.
|
|
338
|
+
const waitForFirstRender = (timeoutMs: number): Promise<boolean> =>
|
|
339
|
+
new Promise<boolean>(resolve => {
|
|
340
|
+
let settled = false;
|
|
341
|
+
let pollTimer: number | undefined;
|
|
342
|
+
const settle = (loaded: boolean) => {
|
|
343
|
+
if (settled) return;
|
|
344
|
+
settled = true;
|
|
345
|
+
window.clearTimeout(timeoutTimer);
|
|
346
|
+
if (pollTimer !== undefined) window.clearInterval(pollTimer);
|
|
347
|
+
resolve(loaded);
|
|
348
|
+
};
|
|
349
|
+
const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
|
|
350
|
+
// Success path resolves straight from readyPromise (no polling lag);
|
|
351
|
+
// the poll only catches teardown and pre-ready render failures.
|
|
352
|
+
slideControllerRef?.readyPromise.then(
|
|
353
|
+
() => settle(!firstRenderFailed),
|
|
354
|
+
() => settle(false),
|
|
355
|
+
);
|
|
356
|
+
pollTimer = window.setInterval(() => {
|
|
357
|
+
if (disposed || firstRenderFailed) settle(false);
|
|
358
|
+
}, 100);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const appResult: AppResult = {
|
|
276
362
|
onScaleChanged: (cb: (scale: number) => void) => {
|
|
277
363
|
if (!docsViewer) {
|
|
278
364
|
return () => void 0;
|
|
@@ -396,6 +482,18 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
|
396
482
|
return false;
|
|
397
483
|
},
|
|
398
484
|
};
|
|
485
|
+
|
|
486
|
+
const setupReadyTimeout =
|
|
487
|
+
appOptions?.setupReadyTimeout ?? DEFAULT_SLIDE_SETUP_READY_TIMEOUT;
|
|
488
|
+
return waitForFirstRender(setupReadyTimeout).then(() => {
|
|
489
|
+
if (firstRenderFailed) {
|
|
490
|
+
throw new Error("[Slide] first render failed before ready");
|
|
491
|
+
}
|
|
492
|
+
if (!disposed && !slideControllerRef?.ready) {
|
|
493
|
+
log("[Slide] setup ready wait timed out, slide keeps loading in background");
|
|
494
|
+
}
|
|
495
|
+
return appResult;
|
|
496
|
+
}) as unknown as AppResult;
|
|
399
497
|
},
|
|
400
498
|
teardown(context) {
|
|
401
499
|
(SlideApp as any).__teardownByContext?.get(context)?.();
|