@netless/app-presentation 0.1.12 → 0.1.13-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.
@@ -1150,6 +1150,9 @@ var NetlessAppPresentation = (function (exports) {
1150
1150
  // 当前触摸的索引
1151
1151
  this.touchIndex = 0;
1152
1152
  this.preloadSize = 0;
1153
+ // 暂停后不再创建预加载链接(app 失焦降级时调用);已插入的 link 会被移除,
1154
+ // 挂起的 idle 回调重新进入 touch() 时也会被跳过
1155
+ this.paused = false;
1153
1156
  this.preloadMap = new Map(pages.map((e, index) => [index, {
1154
1157
  src: e.src,
1155
1158
  state: 0 /* unloaded */
@@ -1157,7 +1160,21 @@ var NetlessAppPresentation = (function (exports) {
1157
1160
  this.preloadSize = this.preloadMap.size;
1158
1161
  this.touch(0, true);
1159
1162
  }
1163
+ pause() {
1164
+ if (this.paused)
1165
+ return;
1166
+ this.paused = true;
1167
+ this.destroySomeLink();
1168
+ }
1169
+ resume(index) {
1170
+ if (!this.paused)
1171
+ return;
1172
+ this.paused = false;
1173
+ this.touch(index != null ? index : this.touchIndex, true);
1174
+ }
1160
1175
  touch(index, force = false) {
1176
+ if (this.paused)
1177
+ return;
1161
1178
  if (index >= this.preloadSize) {
1162
1179
  this.touchIndex = 0;
1163
1180
  } else {
@@ -1956,6 +1973,138 @@ var NetlessAppPresentation = (function (exports) {
1956
1973
  const size = fitPageSizeToOrigin(ppt, originSize);
1957
1974
  return __spreadProps(__spreadValues({}, size), { src: ppt.src, thumbnail: ppt.previewURL, name });
1958
1975
  };
1976
+ var findBackgroundImage = (view) => {
1977
+ const container = view.divElement;
1978
+ if (!container || typeof container.querySelector !== "function")
1979
+ return null;
1980
+ return container.querySelector('img[alt="background"]');
1981
+ };
1982
+ var isBlurDegradationAllowed = (context) => {
1983
+ const boxStatus = context.getBoxStatus();
1984
+ if (boxStatus) {
1985
+ return boxStatus !== "normal";
1986
+ }
1987
+ const boxState = context.getWindowManager().boxState;
1988
+ return boxState !== void 0 && boxState !== "normal";
1989
+ };
1990
+ var DEFAULT_SETUP_READY_TIMEOUT = 5e3;
1991
+ var waitForCurrentPageImage = (view, timeoutMs, isDisposed) => {
1992
+ return new Promise((resolve) => {
1993
+ let settled = false;
1994
+ let pollTimer;
1995
+ const settle = (loaded) => {
1996
+ if (settled)
1997
+ return;
1998
+ settled = true;
1999
+ window.clearTimeout(timeoutTimer);
2000
+ if (pollTimer !== void 0)
2001
+ window.clearInterval(pollTimer);
2002
+ resolve(loaded);
2003
+ };
2004
+ const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
2005
+ const check = () => {
2006
+ if (isDisposed()) {
2007
+ settle(false);
2008
+ return;
2009
+ }
2010
+ const img = findBackgroundImage(view);
2011
+ if (img && img.complete && img.naturalWidth > 0) {
2012
+ settle(true);
2013
+ }
2014
+ };
2015
+ check();
2016
+ if (!settled) {
2017
+ pollTimer = window.setInterval(check, 100);
2018
+ }
2019
+ });
2020
+ };
2021
+ var resolveImageUrl = (url) => {
2022
+ try {
2023
+ return new URL(url, window.location.href).href;
2024
+ } catch (e) {
2025
+ return url;
2026
+ }
2027
+ };
2028
+ var setupBlurThumbnailDegradation = (config) => {
2029
+ const { context, view, pages, pageIndex$, app, dispose: dispose2 } = config;
2030
+ let blurEpisode = false;
2031
+ let reDegradeEnabled = false;
2032
+ const degrade = () => {
2033
+ const page = pages[pageIndex$.value];
2034
+ if (!(page == null ? void 0 : page.thumbnail))
2035
+ return;
2036
+ const img = findBackgroundImage(view);
2037
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(page.thumbnail)) {
2038
+ img.src = page.thumbnail;
2039
+ }
2040
+ };
2041
+ const restore2 = () => {
2042
+ const page = pages[pageIndex$.value];
2043
+ const img = findBackgroundImage(view);
2044
+ if (page && img && resolveImageUrl(img.src) !== resolveImageUrl(page.src)) {
2045
+ img.src = page.src;
2046
+ }
2047
+ };
2048
+ const isBackgroundImg = (target) => {
2049
+ var _a;
2050
+ const el = target;
2051
+ return Boolean(
2052
+ el && el.tagName === "IMG" && ((_a = el.matches) == null ? void 0 : _a.call(el, 'img[alt="background"]'))
2053
+ );
2054
+ };
2055
+ const onImgLoad = (ev) => {
2056
+ if (!blurEpisode || !reDegradeEnabled)
2057
+ return;
2058
+ if (!isBackgroundImg(ev.target))
2059
+ return;
2060
+ degrade();
2061
+ };
2062
+ const onImgError = (ev) => {
2063
+ if (!blurEpisode || !reDegradeEnabled)
2064
+ return;
2065
+ if (!isBackgroundImg(ev.target))
2066
+ return;
2067
+ reDegradeEnabled = false;
2068
+ };
2069
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2070
+ if (isFocused) {
2071
+ if (blurEpisode) {
2072
+ blurEpisode = false;
2073
+ reDegradeEnabled = false;
2074
+ restore2();
2075
+ app.preload.resume(pageIndex$.value);
2076
+ }
2077
+ } else {
2078
+ if (!isBlurDegradationAllowed(context))
2079
+ return;
2080
+ blurEpisode = true;
2081
+ reDegradeEnabled = true;
2082
+ degrade();
2083
+ app.preload.pause();
2084
+ }
2085
+ });
2086
+ dispose2.add(offFocus);
2087
+ const viewEl = view.divElement;
2088
+ if (viewEl && typeof viewEl.addEventListener === "function") {
2089
+ viewEl.addEventListener("load", onImgLoad, true);
2090
+ viewEl.addEventListener("error", onImgError, true);
2091
+ dispose2.add(() => {
2092
+ viewEl.removeEventListener("load", onImgLoad, true);
2093
+ viewEl.removeEventListener("error", onImgError, true);
2094
+ });
2095
+ } else {
2096
+ warnOnceMissingViewContainer(view);
2097
+ }
2098
+ };
2099
+ var degradedWarnedViews = /* @__PURE__ */ new WeakSet();
2100
+ var warnOnceMissingViewContainer = (view) => {
2101
+ if (degradedWarnedViews.has(view))
2102
+ return;
2103
+ degradedWarnedViews.add(view);
2104
+ console.warn(
2105
+ "[Presentation]: blur thumbnail degradation runs without re-degrade coverage (view container unavailable)"
2106
+ );
2107
+ };
1959
2108
  var createLogger = (room) => {
1960
2109
  if (room && room.logger) {
1961
2110
  return (...args) => room.logger.info(...args);
@@ -1967,7 +2116,10 @@ var NetlessAppPresentation = (function (exports) {
1967
2116
  var _a;
1968
2117
  const createAppLogger = context.createLogger;
1969
2118
  if (typeof createAppLogger === "function") {
1970
- return createAppLogger.call(context, "camera", { debounceTime: 300, maxWaitTime: 2e3 });
2119
+ return createAppLogger.call(context, "camera", {
2120
+ debounceTime: 300,
2121
+ maxWaitTime: 2e3
2122
+ });
1971
2123
  }
1972
2124
  const roomLogger = (_a = context.getRoom()) == null ? void 0 : _a.logger;
1973
2125
  const prefix = `[Presentation][${context.appId}][camera]`;
@@ -2024,40 +2176,60 @@ var NetlessAppPresentation = (function (exports) {
2024
2176
  return scene.name === scene2.name && ((_a = scene.ppt) == null ? void 0 : _a.width) === ((_b = scene2.ppt) == null ? void 0 : _b.width) && ((_c = scene.ppt) == null ? void 0 : _c.height) === ((_d = scene2.ppt) == null ? void 0 : _d.height) && ((_e = scene.ppt) == null ? void 0 : _e.src) === ((_f = scene2.ppt) == null ? void 0 : _f.src);
2025
2177
  });
2026
2178
  };
2179
+ var teardownByContext = /* @__PURE__ */ new WeakMap();
2027
2180
  var NetlessAppPresentation = {
2028
2181
  kind: "Presentation",
2029
2182
  setup(context) {
2030
- var _a, _b, _c;
2183
+ var _a, _b, _c, _d;
2031
2184
  const diagnosticLogger = createDiagnosticLogger(context);
2032
2185
  const view = context.getView();
2033
2186
  if (!view)
2034
- throw new Error("[Presentation]: no whiteboard view, make sure you have added options.scenePath in addApp()");
2187
+ throw new Error(
2188
+ "[Presentation]: no whiteboard view, make sure you have added options.scenePath in addApp()"
2189
+ );
2035
2190
  const options = context.getAppOptions() || {};
2036
2191
  const room = context.getRoom();
2037
2192
  const log = options.log || createLogger(room);
2038
2193
  const roomLogger = room == null ? void 0 : room.logger;
2039
2194
  const warn = (...data) => (roomLogger == null ? void 0 : roomLogger.warn) ? roomLogger.warn(...data) : log(...data);
2040
2195
  const configuredOriginSize = context.storage.state.originSize;
2041
- const originSize = isValidSize(configuredOriginSize) ? { width: configuredOriginSize.width, height: configuredOriginSize.height } : void 0;
2196
+ const originSize = isValidSize(configuredOriginSize) ? {
2197
+ width: configuredOriginSize.width,
2198
+ height: configuredOriginSize.height
2199
+ } : void 0;
2042
2200
  if (configuredOriginSize != null && !originSize) {
2043
- warn(`[Presentation] originSize should contain finite positive width and height, got ${JSON.stringify(configuredOriginSize)}`);
2201
+ warn(
2202
+ `[Presentation] originSize should contain finite positive width and height, got ${JSON.stringify(configuredOriginSize)}`
2203
+ );
2044
2204
  }
2045
2205
  const pages = (_a = context.getScenes()) == null ? void 0 : _a.map(({ ppt, name }) => ppt2page(ppt, name, originSize)).filter(Boolean);
2046
2206
  if (!pages || pages.length === 0)
2047
- throw new Error("[Presentation]: empty scenes, make sure you have added options.scenes in addApp()");
2207
+ throw new Error(
2208
+ "[Presentation]: empty scenes, make sure you have added options.scenes in addApp()"
2209
+ );
2048
2210
  if (pages[0].src.startsWith("ppt"))
2049
- throw new Error("[Presentation]: legacy dynamic PPT is unsupported, please use the projector converter and @netless/slide to render it");
2211
+ throw new Error(
2212
+ "[Presentation]: legacy dynamic PPT is unsupported, please use the projector converter and @netless/slide to render it"
2213
+ );
2050
2214
  const scenePath = context.getInitScenePath();
2051
2215
  let maxCameraScale = (_b = options.maxCameraScale) != null ? _b : 3;
2052
2216
  if (!(Number.isFinite(maxCameraScale) && maxCameraScale > 0)) {
2053
- warn(`[Presentation] maxCameraScale should be a positive number, got ${options.maxCameraScale}`);
2217
+ warn(
2218
+ `[Presentation] maxCameraScale should be a positive number, got ${options.maxCameraScale}`
2219
+ );
2054
2220
  maxCameraScale = 3;
2055
2221
  }
2056
2222
  log(`[Presentation] new ${context.appId}`);
2057
2223
  const dispose2 = disposableStore();
2058
2224
  dispose2.add(() => log(`[Presentation] dispose ${context.appId}`));
2059
2225
  dispose2.add(() => diagnosticLogger.flush());
2060
- const view$$ = context.createStorage("view", { uid: "", originX: 0, originY: 0, width: 0, height: 0 });
2226
+ const view$$ = context.createStorage("view", {
2227
+ uid: "",
2228
+ originX: 0,
2229
+ originY: 0,
2230
+ width: 0,
2231
+ height: 0
2232
+ });
2061
2233
  const _addScenePathListener = (name, listener) => {
2062
2234
  const windowManger = context.manager.windowManger;
2063
2235
  windowManger.emitter.on(name, listener);
@@ -2079,13 +2251,16 @@ var NetlessAppPresentation = (function (exports) {
2079
2251
  let pageIndex = getPageIndex(view);
2080
2252
  const pageIndex$ = readable(pageIndex, (set) => {
2081
2253
  set(pageIndex);
2082
- return _addScenePathListener("onAppScenePathChange", (payload) => {
2083
- const { appId } = payload;
2084
- if (appId === context.appId) {
2085
- const _pageIndex = getPageIndex(payload.view);
2086
- set(_pageIndex);
2254
+ return _addScenePathListener(
2255
+ "onAppScenePathChange",
2256
+ (payload) => {
2257
+ const { appId } = payload;
2258
+ if (appId === context.appId) {
2259
+ const _pageIndex = getPageIndex(payload.view);
2260
+ set(_pageIndex);
2261
+ }
2087
2262
  }
2088
- });
2263
+ );
2089
2264
  });
2090
2265
  dispose2.add(() => {
2091
2266
  pageIndex$.dispose();
@@ -2102,7 +2277,12 @@ var NetlessAppPresentation = (function (exports) {
2102
2277
  const p = pages[index];
2103
2278
  const name = (_a2 = p.name) != null ? _a2 : String(index + 1);
2104
2279
  if (!scenes.some((scene) => scene.name === name)) {
2105
- await context.addPage({ scene: { name, ppt: { width: p.width, height: p.height, src: p.src } } });
2280
+ await context.addPage({
2281
+ scene: {
2282
+ name,
2283
+ ppt: { width: p.width, height: p.height, src: p.src }
2284
+ }
2285
+ });
2106
2286
  }
2107
2287
  if (logger) {
2108
2288
  logger.info(`[Presentation] syncPage ${scenePath}/${name}`);
@@ -2114,15 +2294,21 @@ var NetlessAppPresentation = (function (exports) {
2114
2294
  if (!context.isAddApp)
2115
2295
  return;
2116
2296
  if (pages.length > 100)
2117
- warn(`[Presentation]: too many pages (${pages.length}), may cause performance issues`);
2297
+ warn(
2298
+ `[Presentation]: too many pages (${pages.length}), may cause performance issues`
2299
+ );
2118
2300
  if (!room || !room.isWritable)
2119
2301
  return;
2120
2302
  if (pageIndex$.value < 0 || pageIndex$.value >= pages.length) {
2121
- throw new Error(`[Presentation] Invalid page index: ${pageIndex$.value}, scenes length: ${pages.length}`);
2303
+ throw new Error(
2304
+ `[Presentation] Invalid page index: ${pageIndex$.value}, scenes length: ${pages.length}`
2305
+ );
2122
2306
  }
2123
2307
  const scenes = room.entireScenes()[scenePath];
2124
2308
  if (!scenes || !scenes[pageIndex$.value]) {
2125
- throw new Error(`[Presentation]: no initial scene found at ${scenePath}, page index: ${pageIndex$.value}`);
2309
+ throw new Error(
2310
+ `[Presentation]: no initial scene found at ${scenePath}, page index: ${pageIndex$.value}`
2311
+ );
2126
2312
  }
2127
2313
  const { name, ppt } = scenes[pageIndex$.value];
2128
2314
  const nextScenes = pages.map((page, index) => {
@@ -2151,16 +2337,22 @@ var NetlessAppPresentation = (function (exports) {
2151
2337
  const prepareScenesPromise = prepareScenes();
2152
2338
  const canJumpPage = (index) => {
2153
2339
  if (!context.getIsWritable()) {
2154
- warn("[Presentation]: no permission, make sure you have test room.isWritable");
2340
+ warn(
2341
+ "[Presentation]: no permission, make sure you have test room.isWritable"
2342
+ );
2155
2343
  return false;
2156
2344
  }
2157
2345
  if (!(0 <= index && index < pages.length)) {
2158
- warn(`[Presentation]: page ${index + 1} out of bounds [1, ${pages.length}]`);
2346
+ warn(
2347
+ `[Presentation]: page ${index + 1} out of bounds [1, ${pages.length}]`
2348
+ );
2159
2349
  return false;
2160
2350
  }
2161
2351
  const scenes = context.getDisplayer().entireScenes()[scenePath];
2162
2352
  if (!scenes) {
2163
- warn(`[Presentation]: no scenes found at ${scenePath}, make sure you have added options.scenePath in addApp()`);
2353
+ warn(
2354
+ `[Presentation]: no scenes found at ${scenePath}, make sure you have added options.scenePath in addApp()`
2355
+ );
2164
2356
  return false;
2165
2357
  }
2166
2358
  return true;
@@ -2266,13 +2458,20 @@ var NetlessAppPresentation = (function (exports) {
2266
2458
  try {
2267
2459
  const { camera, size } = view;
2268
2460
  const referenceSize = getCameraReferenceSize(originSize, page);
2269
- const viewport = cameraToSharedViewport(camera, size, referenceSize);
2461
+ const viewport = cameraToSharedViewport(
2462
+ camera,
2463
+ size,
2464
+ referenceSize
2465
+ );
2270
2466
  if (viewport)
2271
2467
  view$$.setState(__spreadValues({ uid: me }, viewport));
2272
2468
  if (pendingMoveCameraRequest) {
2273
2469
  diagnosticLogger.debouncedInfo(
2274
2470
  "moveCamera",
2275
- getCameraDiagnosticState("moveCamera", pendingMoveCameraRequest)
2471
+ getCameraDiagnosticState(
2472
+ "moveCamera",
2473
+ pendingMoveCameraRequest
2474
+ )
2276
2475
  );
2277
2476
  pendingMoveCameraRequest = void 0;
2278
2477
  }
@@ -2280,7 +2479,10 @@ var NetlessAppPresentation = (function (exports) {
2280
2479
  diagnosticLogger.error(
2281
2480
  "syncView.failed",
2282
2481
  error,
2283
- getCameraDiagnosticState("moveCamera", pendingMoveCameraRequest)
2482
+ getCameraDiagnosticState(
2483
+ "moveCamera",
2484
+ pendingMoveCameraRequest
2485
+ )
2284
2486
  );
2285
2487
  pendingMoveCameraRequest = void 0;
2286
2488
  }
@@ -2305,17 +2507,32 @@ var NetlessAppPresentation = (function (exports) {
2305
2507
  });
2306
2508
  }
2307
2509
  };
2308
- dispose2.add(view$$.addStateChangedListener(() => syncViewFromRemote(false, true)));
2510
+ dispose2.add(
2511
+ view$$.addStateChangedListener(() => syncViewFromRemote(false, true))
2512
+ );
2309
2513
  const getPageSize = () => {
2310
2514
  const { width, height } = app.page() || {};
2311
2515
  return { width: width || 0, height: height || 0 };
2312
2516
  };
2313
2517
  const box = context.getBox();
2314
- const app = dispose2.add(createPresentation(box, pages, jumpPage, pageIndex$, view, options.thumbnail, options.useClipView));
2315
- app.contentDOM.dataset.appPresentationVersion = "0.1.12";
2518
+ const app = dispose2.add(
2519
+ createPresentation(
2520
+ box,
2521
+ pages,
2522
+ jumpPage,
2523
+ pageIndex$,
2524
+ view,
2525
+ options.thumbnail,
2526
+ options.useClipView
2527
+ )
2528
+ );
2529
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.1";
2316
2530
  app.scaleDocsToFit = scaleDocsToFit;
2317
2531
  app.log = log;
2318
2532
  app.warn = warn;
2533
+ if (pageIndex$.value !== 0) {
2534
+ app.preload.touch(pageIndex$.value, true);
2535
+ }
2319
2536
  const getCameraDiagnosticState = (reason, requestedCamera) => {
2320
2537
  const page = app.page();
2321
2538
  const pageSize = page && isValidSize(page) ? { width: page.width, height: page.height } : void 0;
@@ -2359,19 +2576,21 @@ var NetlessAppPresentation = (function (exports) {
2359
2576
  };
2360
2577
  if (originSize) {
2361
2578
  let previousPageIndex = pageIndex$.value;
2362
- dispose2.add(pageIndex$.subscribe((nextPageIndex) => {
2363
- if (nextPageIndex === previousPageIndex)
2364
- return;
2365
- previousPageIndex = nextPageIndex;
2366
- scaleDocsToFit();
2367
- }));
2579
+ dispose2.add(
2580
+ pageIndex$.subscribe((nextPageIndex) => {
2581
+ if (nextPageIndex === previousPageIndex)
2582
+ return;
2583
+ previousPageIndex = nextPageIndex;
2584
+ scaleDocsToFit();
2585
+ })
2586
+ );
2368
2587
  }
2369
2588
  if (options.justDocsViewReadonly) {
2370
2589
  app.setDocsViewReadonly(true);
2371
2590
  }
2372
2591
  const goToPageByClick = () => {
2373
- var _a2, _b2, _c2, _d;
2374
- const currentApplianceName = (_d = (_c2 = (_b2 = (_a2 = context.getRoom()) == null ? void 0 : _a2.state) == null ? void 0 : _b2.memberState) == null ? void 0 : _c2.currentApplianceName) != null ? _d : "";
2592
+ var _a2, _b2, _c2, _d2;
2593
+ const currentApplianceName = (_d2 = (_c2 = (_b2 = (_a2 = context.getRoom()) == null ? void 0 : _a2.state) == null ? void 0 : _b2.memberState) == null ? void 0 : _c2.currentApplianceName) != null ? _d2 : "";
2375
2594
  if (!app.readonly && currentApplianceName === "clicker") {
2376
2595
  nextPage();
2377
2596
  }
@@ -2413,9 +2632,14 @@ var NetlessAppPresentation = (function (exports) {
2413
2632
  });
2414
2633
  syncViewFromRemote(true);
2415
2634
  reportInitializedCamera("setup");
2416
- dispose2.add(context.emitter.on("writableChange", (isWritable) => {
2417
- app.setReadonly(!isWritable);
2418
- }));
2635
+ dispose2.add(
2636
+ context.emitter.on("writableChange", (isWritable) => {
2637
+ app.setReadonly(!isWritable);
2638
+ })
2639
+ );
2640
+ if (!options.disableBlurThumbnailDegradation) {
2641
+ setupBlurThumbnailDegradation({ context, view, pages, pageIndex$, app, dispose: dispose2 });
2642
+ }
2419
2643
  const getOriginScale = () => {
2420
2644
  const page = app.page();
2421
2645
  if (!page || !isValidSize(page))
@@ -2437,29 +2661,53 @@ var NetlessAppPresentation = (function (exports) {
2437
2661
  img.crossOrigin = "Anonymous";
2438
2662
  await new Promise((resolve, reject) => {
2439
2663
  img.onload = () => resolve();
2440
- img.onerror = () => reject(new Error(
2441
- `[Presentation]: failed to load screenshot page image: ${safeResourceLocation(src)}`
2442
- ));
2664
+ img.onerror = () => reject(
2665
+ new Error(
2666
+ `[Presentation]: failed to load screenshot page image: ${safeResourceLocation(src)}`
2667
+ )
2668
+ );
2443
2669
  img.src = src;
2444
2670
  });
2445
- _context.drawImage(img, 0, 0, width, height, 0, 0, _width || width, _height || height);
2671
+ _context.drawImage(
2672
+ img,
2673
+ 0,
2674
+ 0,
2675
+ width,
2676
+ height,
2677
+ 0,
2678
+ 0,
2679
+ _width || width,
2680
+ _height || height
2681
+ );
2446
2682
  const currentScenePath = view.focusScenePath;
2447
2683
  if (!currentScenePath) {
2448
2684
  throw new Error("[Presentation]: current scene path not found");
2449
2685
  }
2450
2686
  const windowManger = context.getWindowManager();
2451
2687
  if (windowManger._appliancePlugin) {
2452
- await windowManger._appliancePlugin.screenshotToCanvasAsync(_context, currentScenePath, _width || width, _height || height, {
2453
- centerX: 0,
2454
- centerY: 0,
2455
- scale: _width && _height ? Math.min(_width / width, _height / height) : 1
2456
- });
2688
+ await windowManger._appliancePlugin.screenshotToCanvasAsync(
2689
+ _context,
2690
+ currentScenePath,
2691
+ _width || width,
2692
+ _height || height,
2693
+ {
2694
+ centerX: 0,
2695
+ centerY: 0,
2696
+ scale: _width && _height ? Math.min(_width / width, _height / height) : 1
2697
+ }
2698
+ );
2457
2699
  } else {
2458
- await view.screenshotToCanvasAsync(_context, currentScenePath, _width || width, _height || height, {
2459
- centerX: 0,
2460
- centerY: 0,
2461
- scale: _width && _height ? Math.min(_width / width, _height / height) : 1
2462
- });
2700
+ await view.screenshotToCanvasAsync(
2701
+ _context,
2702
+ currentScenePath,
2703
+ _width || width,
2704
+ _height || height,
2705
+ {
2706
+ centerX: 0,
2707
+ centerY: 0,
2708
+ scale: _width && _height ? Math.min(_width / width, _height / height) : 1
2709
+ }
2710
+ );
2463
2711
  }
2464
2712
  };
2465
2713
  const screenshotCurrentPageAsync = async (_context, _width, _height) => {
@@ -2477,21 +2725,27 @@ var NetlessAppPresentation = (function (exports) {
2477
2725
  let scrollbar;
2478
2726
  if (options.useScrollbar) {
2479
2727
  dispose2.make(() => {
2480
- scrollbar = new Scrollbar(app.contentDOM, {
2481
- appId: context.appId,
2482
- getPageSize,
2483
- getOriginScale,
2484
- syncView,
2485
- getWritable: () => context.getIsWritable(),
2486
- scrollbarEventCallback: options.scrollbarEventCallback
2487
- }, view);
2728
+ scrollbar = new Scrollbar(
2729
+ app.contentDOM,
2730
+ {
2731
+ appId: context.appId,
2732
+ getPageSize,
2733
+ getOriginScale,
2734
+ syncView,
2735
+ getWritable: () => context.getIsWritable(),
2736
+ scrollbarEventCallback: options.scrollbarEventCallback
2737
+ },
2738
+ view
2739
+ );
2488
2740
  return () => scrollbar == null ? void 0 : scrollbar.destroy();
2489
2741
  });
2490
2742
  }
2491
2743
  const moveCamera = (camera) => {
2492
2744
  try {
2493
2745
  if (!context.getIsWritable()) {
2494
- throw new Error("[Presentation]: moveCamera must be called in writable room");
2746
+ throw new Error(
2747
+ "[Presentation]: moveCamera must be called in writable room"
2748
+ );
2495
2749
  }
2496
2750
  pendingMoveCameraRequest = __spreadValues({}, camera);
2497
2751
  if (scrollbar) {
@@ -2512,9 +2766,29 @@ var NetlessAppPresentation = (function (exports) {
2512
2766
  throw error;
2513
2767
  }
2514
2768
  };
2515
- context.emitter.on("destroy", () => dispose2());
2769
+ let disposed = false;
2770
+ let offDestroy;
2771
+ const teardown = () => {
2772
+ if (disposed)
2773
+ return;
2774
+ disposed = true;
2775
+ const removeDestroy = offDestroy;
2776
+ offDestroy = void 0;
2777
+ removeDestroy == null ? void 0 : removeDestroy();
2778
+ dispose2();
2779
+ };
2780
+ offDestroy = context.emitter.on("destroy", teardown);
2781
+ teardownByContext.set(context, teardown);
2516
2782
  const reportProgress = (progress, result) => {
2517
- window.postMessage({ type: "@netless/_result_save_pdf_", appId: context.appId, progress, result }, "*");
2783
+ window.postMessage(
2784
+ {
2785
+ type: "@netless/_result_save_pdf_",
2786
+ appId: context.appId,
2787
+ progress,
2788
+ result
2789
+ },
2790
+ "*"
2791
+ );
2518
2792
  return result;
2519
2793
  };
2520
2794
  const base64url = async (url) => {
@@ -2554,7 +2828,9 @@ var NetlessAppPresentation = (function (exports) {
2554
2828
  }
2555
2829
  const scenes = context.getDisplayer().entireScenes()[scenePath];
2556
2830
  if (!scenes) {
2557
- throw new Error(`[Presentation]: no scenes found while exporting PDF: ${scenePath}`);
2831
+ throw new Error(
2832
+ `[Presentation]: no scenes found while exporting PDF: ${scenePath}`
2833
+ );
2558
2834
  }
2559
2835
  const stage_canvas = document.createElement("canvas");
2560
2836
  stage_canvas.width = pdfWidth;
@@ -2579,20 +2855,32 @@ var NetlessAppPresentation = (function (exports) {
2579
2855
  const img = document.createElement("img");
2580
2856
  await new Promise((resolve, reject) => {
2581
2857
  img.onload = () => resolve();
2582
- img.onerror = () => reject(new Error(
2583
- `[Presentation]: failed to load PDF page image, page index: ${index}`
2584
- ));
2858
+ img.onerror = () => reject(
2859
+ new Error(
2860
+ `[Presentation]: failed to load PDF page image, page index: ${index}`
2861
+ )
2862
+ );
2585
2863
  img.src = url;
2586
2864
  });
2587
2865
  stage.drawImage(img, 0, 0, width2, height2);
2588
2866
  wb.clearRect(0, 0, pdfWidth, pdfHeight);
2589
2867
  const name = (_a2 = p.name) != null ? _a2 : String(index + 1);
2590
2868
  if (scenes.some((scene) => scene.name == name)) {
2591
- const camera = { centerX: 0, centerY: 0, scale: Math.min(wb_canvas.width / width2, wb_canvas.height / height2) };
2869
+ const camera = {
2870
+ centerX: 0,
2871
+ centerY: 0,
2872
+ scale: Math.min(wb_canvas.width / width2, wb_canvas.height / height2)
2873
+ };
2592
2874
  const sPath = `${scenePath}/${name}`;
2593
2875
  const windowManger = context.getWindowManager();
2594
2876
  if (windowManger._appliancePlugin) {
2595
- await windowManger._appliancePlugin.screenshotToCanvasAsync(wb, sPath, wb_canvas.width, wb_canvas.height, camera);
2877
+ await windowManger._appliancePlugin.screenshotToCanvasAsync(
2878
+ wb,
2879
+ sPath,
2880
+ wb_canvas.width,
2881
+ wb_canvas.height,
2882
+ camera
2883
+ );
2596
2884
  } else {
2597
2885
  await view.screenshotToCanvasAsync(
2598
2886
  wb,
@@ -2639,14 +2927,20 @@ var NetlessAppPresentation = (function (exports) {
2639
2927
  throw error;
2640
2928
  }
2641
2929
  };
2642
- dispose2.add(listen(window, "message", (ev) => {
2643
- if (ev.data && ev.data.type == "@netless/_request_save_pdf_" && ev.data.appId == context.appId) {
2644
- toPdf().catch((err) => {
2645
- warn(err);
2646
- reportProgress(100, null);
2647
- });
2648
- }
2649
- }));
2930
+ dispose2.add(
2931
+ listen(
2932
+ window,
2933
+ "message",
2934
+ (ev) => {
2935
+ if (ev.data && ev.data.type == "@netless/_request_save_pdf_" && ev.data.appId == context.appId) {
2936
+ toPdf().catch((err) => {
2937
+ warn(err);
2938
+ reportProgress(100, null);
2939
+ });
2940
+ }
2941
+ }
2942
+ )
2943
+ );
2650
2944
  const setDocsViewReadonly = (bol) => {
2651
2945
  app.setDocsViewReadonly(bol);
2652
2946
  };
@@ -2657,19 +2951,57 @@ var NetlessAppPresentation = (function (exports) {
2657
2951
  scrollbar.setReadonly(bol);
2658
2952
  }
2659
2953
  };
2660
- const controller = { app, view, context, jumpPage, prevPage, nextPage, jumpPageAsync, prevPageAsync, nextPageAsync, pageState, toPdf, log, setDocsViewReadonly, setReadonly, moveCamera, getOriginScale, getScale, getPageSize, screenshotCurrentPageAsync };
2661
- dispose2.add(listen(window, "message", (ev) => {
2662
- if (ev.data === "@netless/_presentation_") {
2663
- if (typeof window !== "undefined")
2664
- dispose2.make(() => {
2665
- const debug = window._presentation_ || (window._presentation_ = /* @__PURE__ */ new Set());
2666
- debug.add(controller);
2667
- return () => debug.delete(controller);
2668
- });
2669
- console.log(controller);
2670
- }
2671
- }));
2672
- return prepareScenesPromise.then(() => controller);
2954
+ const controller = {
2955
+ app,
2956
+ view,
2957
+ context,
2958
+ jumpPage,
2959
+ prevPage,
2960
+ nextPage,
2961
+ jumpPageAsync,
2962
+ prevPageAsync,
2963
+ nextPageAsync,
2964
+ pageState,
2965
+ toPdf,
2966
+ log,
2967
+ setDocsViewReadonly,
2968
+ setReadonly,
2969
+ moveCamera,
2970
+ getOriginScale,
2971
+ getScale,
2972
+ getPageSize,
2973
+ screenshotCurrentPageAsync
2974
+ };
2975
+ dispose2.add(
2976
+ listen(
2977
+ window,
2978
+ "message",
2979
+ (ev) => {
2980
+ if (ev.data === "@netless/_presentation_") {
2981
+ if (typeof window !== "undefined")
2982
+ dispose2.make(() => {
2983
+ const debug = window._presentation_ || (window._presentation_ = /* @__PURE__ */ new Set());
2984
+ debug.add(controller);
2985
+ return () => debug.delete(controller);
2986
+ });
2987
+ console.log(controller);
2988
+ }
2989
+ }
2990
+ )
2991
+ );
2992
+ const setupReadyTimeout = (_d = options.setupReadyTimeout) != null ? _d : DEFAULT_SETUP_READY_TIMEOUT;
2993
+ return prepareScenesPromise.then(async () => {
2994
+ const loaded = await waitForCurrentPageImage(view, setupReadyTimeout, () => disposed);
2995
+ if (!loaded) {
2996
+ warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
2997
+ }
2998
+ return controller;
2999
+ });
3000
+ },
3001
+ teardown(context) {
3002
+ var _a;
3003
+ (_a = teardownByContext.get(context)) == null ? void 0 : _a();
3004
+ teardownByContext.delete(context);
2673
3005
  }
2674
3006
  };
2675
3007
  var AppPresentation = class extends Presentation {
@@ -2689,19 +3021,29 @@ var NetlessAppPresentation = (function (exports) {
2689
3021
  if (0 <= index && index < this.pages.length) {
2690
3022
  this.jumpPage(index);
2691
3023
  } else {
2692
- (_a = this.warn) == null ? void 0 : _a.call(this, `[Presentation]: page index ${index} out of bounds [0, ${this.pages.length - 1}]`);
3024
+ (_a = this.warn) == null ? void 0 : _a.call(
3025
+ this,
3026
+ `[Presentation]: page index ${index} out of bounds [0, ${this.pages.length - 1}]`
3027
+ );
2693
3028
  }
2694
3029
  }
2695
3030
  };
2696
3031
  function createPresentation(box, pages, jumpPage, pageIndex$, view, thumbnail, useClipView) {
2697
3032
  box.mountStyles(style_default);
2698
- const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail });
3033
+ const app = new AppPresentation({
3034
+ pages,
3035
+ readonly: box.readonly,
3036
+ jumpPage,
3037
+ thumbnail
3038
+ });
2699
3039
  app.box = box;
2700
3040
  box.mountContent(app.contentDOM);
2701
3041
  box.mountFooter(app.footerDOM);
2702
- app.dispose.add(pageIndex$.subscribe((pageIndex) => {
2703
- app.setPageIndex(pageIndex);
2704
- }));
3042
+ app.dispose.add(
3043
+ pageIndex$.subscribe((pageIndex) => {
3044
+ app.setPageIndex(pageIndex);
3045
+ })
3046
+ );
2705
3047
  if (useClipView && view) {
2706
3048
  const onCameraUpdatedEffectForMaskView = (0, import_debounce2.default)(() => {
2707
3049
  const { width: pageWidth, height: pageHeight } = app.page() || {};
@@ -2710,8 +3052,12 @@ var NetlessAppPresentation = (function (exports) {
2710
3052
  }
2711
3053
  const { scale } = view.camera;
2712
3054
  const { width, height } = view.size;
2713
- const pageRatioX = Math.round((1 - pageWidth * scale / width) / 2 * 100);
2714
- const pageRatioY = Math.round((1 - pageHeight * scale / height) / 2 * 100);
3055
+ const pageRatioX = Math.round(
3056
+ (1 - pageWidth * scale / width) / 2 * 100
3057
+ );
3058
+ const pageRatioY = Math.round(
3059
+ (1 - pageHeight * scale / height) / 2 * 100
3060
+ );
2715
3061
  app.whiteboardDOM.style.clipPath = `inset(${pageRatioY}% ${pageRatioX}%)`;
2716
3062
  }, 50);
2717
3063
  onCameraUpdatedEffectForMaskView();
@@ -2736,7 +3082,7 @@ var NetlessAppPresentation = (function (exports) {
2736
3082
  };
2737
3083
 
2738
3084
  // src/index.ts
2739
- var version = "0.1.12";
3085
+ var version = "0.1.13-beta.1";
2740
3086
 
2741
3087
  exports.NetlessAppPresentation = NetlessAppPresentation;
2742
3088
  exports.Presentation = Presentation;