@netless/app-slide 0.2.104 → 0.2.105-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-slide",
3
- "version": "0.2.104",
3
+ "version": "0.2.105-beta.1",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
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 {
@@ -113,7 +120,9 @@ export interface AppResult {
113
120
  translateView: (offsetX: number, offsetY: number) => void;
114
121
  }
115
122
 
116
- const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
123
+ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
124
+ teardown(context: import("@netless/window-manager").AppContext): void;
125
+ } = {
117
126
  kind: "Slide",
118
127
  setup(context) {
119
128
  console.log("[Slide] setup @ " + version);
@@ -144,6 +153,10 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
144
153
  const baseScenePath = context.getInitScenePath() as string;
145
154
 
146
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;
147
160
 
148
161
  const onPageChanged = (page: number) => {
149
162
  const room = context.getRoom();
@@ -172,10 +185,16 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
172
185
  ...options,
173
186
  onPageChanged,
174
187
  onNavigate: options.onNavigate,
175
- onRenderError: appOptions.onRenderError,
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
+ },
176
194
  showRenderError: appOptions.showRenderError,
177
195
  invisibleBehavior: appOptions.invisibleBehavior,
178
196
  });
197
+ slideControllerRef = slideController;
179
198
  if (useFreezer) apps.set(context.appId, slideController, box);
180
199
  logger.setAppController(context.appId, slideController);
181
200
  if (import.meta.env.DEV) {
@@ -247,7 +266,14 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
247
266
  });
248
267
  }
249
268
 
250
- context.emitter.on("destroy", () => {
269
+ let disposed = false;
270
+ let offDestroy: (() => void) | undefined;
271
+ const teardown = () => {
272
+ if (disposed) return;
273
+ disposed = true;
274
+ const removeDestroy = offDestroy;
275
+ offDestroy = undefined;
276
+ removeDestroy?.();
251
277
  log("[Slide] destroy", context.appId);
252
278
  if (useFreezer) apps.delete(context.appId);
253
279
  sideEffect.flushAll();
@@ -255,11 +281,41 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
255
281
  docsViewer.destroy();
256
282
  docsViewer = null;
257
283
  }
258
- });
284
+ };
285
+ offDestroy = context.emitter.on("destroy", teardown);
259
286
 
260
287
  docsViewer.mount();
261
288
 
262
- return {
289
+ (SlideApp as any).__teardownByContext ||= new WeakMap<object, () => void>();
290
+ (SlideApp as any).__teardownByContext.set(context, teardown);
291
+
292
+ // Resolve once the SlideController finished its first render (renderEnd).
293
+ // `false` means timeout or teardown; the slide then keeps loading in the
294
+ // background without blocking WindowManager's serial setup queue.
295
+ const waitForFirstRender = (timeoutMs: number): Promise<boolean> =>
296
+ new Promise<boolean>(resolve => {
297
+ let settled = false;
298
+ let pollTimer: number | undefined;
299
+ const settle = (loaded: boolean) => {
300
+ if (settled) return;
301
+ settled = true;
302
+ window.clearTimeout(timeoutTimer);
303
+ if (pollTimer !== undefined) window.clearInterval(pollTimer);
304
+ resolve(loaded);
305
+ };
306
+ const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
307
+ // Success path resolves straight from readyPromise (no polling lag);
308
+ // the poll only catches teardown and pre-ready render failures.
309
+ slideControllerRef?.readyPromise.then(
310
+ () => settle(!firstRenderFailed),
311
+ () => settle(false),
312
+ );
313
+ pollTimer = window.setInterval(() => {
314
+ if (disposed || firstRenderFailed) settle(false);
315
+ }, 100);
316
+ });
317
+
318
+ const appResult: AppResult = {
263
319
  onScaleChanged: (cb: (scale: number) => void) => {
264
320
  if (!docsViewer) {
265
321
  return () => void 0;
@@ -383,6 +439,22 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
383
439
  return false;
384
440
  },
385
441
  };
442
+
443
+ const setupReadyTimeout =
444
+ appOptions?.setupReadyTimeout ?? DEFAULT_SLIDE_SETUP_READY_TIMEOUT;
445
+ return waitForFirstRender(setupReadyTimeout).then(() => {
446
+ if (firstRenderFailed) {
447
+ throw new Error("[Slide] first render failed before ready");
448
+ }
449
+ if (!disposed && !slideControllerRef?.ready) {
450
+ log("[Slide] setup ready wait timed out, slide keeps loading in background");
451
+ }
452
+ return appResult;
453
+ }) as unknown as AppResult;
454
+ },
455
+ teardown(context) {
456
+ (SlideApp as any).__teardownByContext?.get(context)?.();
457
+ (SlideApp as any).__teardownByContext?.delete(context);
386
458
  },
387
459
  };
388
460