@netless/app-presentation 0.1.13-beta.0 → 0.1.13-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 CHANGED
@@ -143,6 +143,40 @@ interface DisposableStore extends DisposableDisposer {
143
143
  */
144
144
  size(): number;
145
145
  }
146
+ /**
147
+ * Create a {@link DisposableStore} that manages {@link Disposer}s and {@link IDisposable}s.
148
+ *
149
+ * A {@link DisposableStore} is also a {@link Disposer}, which means it can be the `dispose` method of an {@link IDisposable}.
150
+ *
151
+ * A {@link DisposableStore} is also an {@link IDisposable}, which means it can be managed by another {@link DisposableStore}.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { type IDisposable, disposableStore } from "@wopjs/disposable";
156
+ *
157
+ * class A implements IDisposable {
158
+ * dispose = disposableStore()
159
+ * constructor() {
160
+ * this.dispose.add(...);
161
+ * }
162
+ * }
163
+ *
164
+ * class B implements IDisposable {
165
+ * dispose = disposableStore()
166
+ * a = this.dispose.add(new A())
167
+ * constructor() {
168
+ * this.dispose.add(...);
169
+ * }
170
+ * }
171
+ *
172
+ * const b = new B();
173
+ * b.dispose(); // dispose both `b` and `b.a`.
174
+ * ```
175
+ *
176
+ * @param disposables Optional array of {@link DisposableType}s added to the store.
177
+ * @returns A disposable store.
178
+ */
179
+ declare function disposableStore(disposables?: DisposableType[]): DisposableStore;
146
180
 
147
181
  interface ILazyLoadInstance {
148
182
  /**
@@ -218,7 +252,10 @@ declare class Preload implements IDisposable {
218
252
  readonly preloadMap: Map<number, IPreloadMapValue>;
219
253
  touchIndex: number;
220
254
  preloadSize: number;
255
+ paused: boolean;
221
256
  constructor(pages: PresentationPage[]);
257
+ pause(): void;
258
+ resume(index?: number): void;
222
259
  touch(index: number, force?: boolean): void;
223
260
  private createLink;
224
261
  private destroySomeLink;
@@ -299,15 +336,27 @@ declare class Presentation implements IDisposable<void> {
299
336
  setReadonly(readonly: boolean): void;
300
337
  setPageIndex(pageIndex: number): void;
301
338
  togglePreview(showPreview?: boolean): void;
339
+ /** Release preview images while this presentation stays in the runtime cache. */
340
+ suspendPreviewResources(): void;
302
341
  onNewPageIndex(index: number, _origin: "navigation" | "keydown" | "input" | "preview"): void;
303
342
  page(): PresentationPage | undefined;
304
343
  updateImage(): void;
305
344
  /** Returns a modified URL that points to the thumbnail image. */
306
345
  thumbnail(src: string): string;
346
+ private destroyPreviewLazyload;
307
347
  private c;
308
348
  private isEditable;
309
349
  }
310
350
 
351
+ type Subscriber<T> = (value: T) => void;
352
+ type Unsubscriber = () => void;
353
+ interface Readable<T> {
354
+ readonly value: T;
355
+ subscribe(this: void, run: Subscriber<T>): Unsubscriber;
356
+ reaction(this: void, run: Subscriber<T>): Unsubscriber;
357
+ dispose(value?: T): void;
358
+ }
359
+
311
360
  interface ScrollbarOption {
312
361
  appId: string;
313
362
  readonly?: boolean;
@@ -420,6 +469,17 @@ interface PresentationAppOptions {
420
469
  goToPageByClick?: boolean;
421
470
  /** useClipView is used to set the presentation use clip view, it will be used in the presentation, and the presentation will be use clip view when the app is initialized */
422
471
  useClipView?: boolean;
472
+ /**
473
+ * Max time (ms) `setup()` waits for the first visible page image to load
474
+ * before resolving anyway. Default: 5_000.
475
+ */
476
+ setupReadyTimeout?: number;
477
+ /**
478
+ * Blur thumbnail degradation keeps cached (focused-lost) presentations on
479
+ * the low-res `previewURL` thumbnail to save memory. Enabled by default in
480
+ * maximized/minimized states; set `true` to disable it.
481
+ */
482
+ disableBlurThumbnailDegradation?: boolean;
423
483
  }
424
484
  interface PresentationController {
425
485
  readonly app: Presentation;
@@ -469,6 +529,29 @@ interface PresentationController {
469
529
  /** screenshot the current page */
470
530
  screenshotCurrentPageAsync: (context: CanvasRenderingContext2D, width?: number, height?: number) => Promise<void>;
471
531
  }
532
+ /**
533
+ * Blur thumbnail degradation (see lazy-setup design doc §15): when the app
534
+ * loses focus but stays cached (multi-runtime cache mode), swap the scene
535
+ * background `<img>` src to the low-res `PptDescription.previewURL`
536
+ * (`page.thumbnail`) via a pure DOM change and pause full-image preloading;
537
+ * restore the full image when focus comes back. A page without a real
538
+ * thumbnail keeps its measurable view container, but its background layer is
539
+ * hidden and replaced with a static pixel after TeleBox commits the blur.
540
+ *
541
+ * While degraded, every load of a background `<img>` is re-degraded
542
+ * immediately via a capture-phase `load` listener on the view container
543
+ * (`load` does not bubble but capture works). This covers remote page turns,
544
+ * SDK React re-renders and the SDK's own background-image onError retries —
545
+ * any path that (re)renders the full-size image while the app stays blurred.
546
+ */
547
+ declare const setupBlurThumbnailDegradation: (config: {
548
+ context: AppContext;
549
+ view: View;
550
+ pages: PresentationPage[];
551
+ pageIndex$: Readable<number>;
552
+ app: Presentation;
553
+ dispose: ReturnType<typeof disposableStore>;
554
+ }) => void;
472
555
  declare const NetlessAppPresentation: NetlessApp<PresentationAttributes, {}, PresentationAppOptions, PresentationController> & {
473
556
  teardown(context: AppContext): void;
474
557
  };
@@ -496,5 +579,5 @@ declare const install: (register: RegisterFn, options?: InstallOptions) => Promi
496
579
 
497
580
  declare const version: string;
498
581
 
499
- export { NetlessAppPresentation, Presentation, Scrollbar, NetlessAppPresentation as default, install, version };
582
+ export { NetlessAppPresentation, Presentation, Scrollbar, NetlessAppPresentation as default, install, setupBlurThumbnailDegradation, version };
500
583
  export type { InstallOptions, Logger, PresentationAppOptions, PresentationAttributes, PresentationConfig, PresentationController, PresentationPage, RegisterFn, ScrollbarEventCallback, ScrollbarOption };
@@ -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 {
@@ -1299,10 +1316,7 @@ var NetlessAppPresentation = (function (exports) {
1299
1316
  if (this.initialized)
1300
1317
  return;
1301
1318
  this.initialized = true;
1302
- this.dispose.add(() => {
1303
- var _a2;
1304
- return (_a2 = this.previewLazyload) == null ? void 0 : _a2.destroy();
1305
- });
1319
+ this.dispose.add(() => this.destroyPreviewLazyload());
1306
1320
  this.contentDOM.classList.toggle(this.c("readonly"), this.readonly);
1307
1321
  this.dom.appendChild(this.contentDOM);
1308
1322
  const previewMask = this.contentDOM.appendChild(document.createElement("div"));
@@ -1455,6 +1469,17 @@ var NetlessAppPresentation = (function (exports) {
1455
1469
  }
1456
1470
  }
1457
1471
  }
1472
+ /** Release preview images while this presentation stays in the runtime cache. */
1473
+ suspendPreviewResources() {
1474
+ this.togglePreview(false);
1475
+ this.destroyPreviewLazyload();
1476
+ this.previewDOM.querySelectorAll("img").forEach((img) => {
1477
+ img.removeAttribute("src");
1478
+ img.removeAttribute("srcset");
1479
+ img.removeAttribute("sizes");
1480
+ LazyLoad.resetStatus(img);
1481
+ });
1482
+ }
1458
1483
  // [origin] means the cause:
1459
1484
  // - navigation: user clicked on the left / right button on footer
1460
1485
  // - input: user filled the bottom-right page number input box
@@ -1489,6 +1514,11 @@ var NetlessAppPresentation = (function (exports) {
1489
1514
  return src;
1490
1515
  }
1491
1516
  }
1517
+ destroyPreviewLazyload() {
1518
+ var _a;
1519
+ (_a = this.previewLazyload) == null ? void 0 : _a.destroy();
1520
+ this.previewLazyload = null;
1521
+ }
1492
1522
  c(className) {
1493
1523
  return `${this.namespace}-${className}`;
1494
1524
  }
@@ -1501,7 +1531,7 @@ var NetlessAppPresentation = (function (exports) {
1501
1531
  };
1502
1532
 
1503
1533
  // src/style.scss?inline
1504
- var style_default = ".netless-app-presentation{height:100%;display:flex;flex-flow:column nowrap}.netless-app-presentation-content{position:relative;height:100%;overflow:hidden}.netless-app-presentation-preview-mask{display:none;position:absolute;z-index:200;top:0;left:0;width:100%;height:100%}.netless-app-presentation-preview{display:flex;flex-direction:column;align-items:center;position:absolute;z-index:300;top:0;left:0;width:33%;max-width:200px;height:100%;padding-top:10px;transform:translate(-100%);background:#ededf0e6;box-shadow:inset -1px 0 #0000001c;transition:transform .4s;overflow:auto;overflow-y:scroll}.netless-app-presentation-preview-active .netless-app-presentation-preview-mask{display:block}.netless-app-presentation-preview-active .netless-app-presentation-preview{transform:translate(0)}.netless-app-presentation-preview-page{position:relative;display:block;width:55%;margin-bottom:10px;font-size:0;color:#0000;outline:none;border:7px solid rgba(0,0,0,0);border-radius:4px;transition:border-color .3s;user-select:none}.netless-app-presentation-preview-page:hover,.netless-app-presentation-preview-page.netless-app-presentation-preview-page-active{border-color:#444e601a}.netless-app-presentation-preview-page>img{width:100%;height:auto;box-sizing:border-box;border:1px solid rgba(0,0,0,.5);border-radius:1px;background-color:#fff;box-shadow:0 2px 8px #0000004d}.netless-app-presentation-preview-page-name{position:absolute;top:1px;left:-10px;transform:translate(-100%);text-align:right;font-size:12px;color:#5f5f5f;user-select:none}.netless-app-presentation-footer{box-sizing:border-box;height:26px;display:flex;align-items:center;padding:0 16px;border-top:1px solid #eeeef7;color:#191919}.netless-app-presentation-float-footer{width:100%;min-height:26px;position:absolute;left:0;bottom:0;z-index:2000;background:#f9f9fce6;transition:opacity .4s}.netless-app-presentation-footer-btn{box-sizing:border-box;width:26px;height:26px;font-size:0;margin:0;padding:3px;border:none;border-radius:1px;outline:none;color:currentColor;background:#0000;transition:background .4s;cursor:pointer;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-footer-btn:hover{background:#ededf0e6}@media (hover: none){.netless-app-presentation-footer-btn:hover{background:#0000!important}}.netless-app-presentation-footer-btn>svg{width:100%;height:100%}.netless-app-presentation-footer-btn>svg:nth-of-type(2){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(1){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(2){display:initial}.netless-app-presentation-footer-btn~.netless-app-presentation-footer-btn{margin-left:15px}.netless-app-presentation-page-jumps{flex:1;display:flex;justify-content:center;align-items:center}.netless-app-presentation-page-number{margin-left:auto;font-size:13px;user-select:none;white-space:nowrap;word-break:keep-all}.netless-app-presentation-page-number-input{border:none;outline:none;width:3em;margin:0;padding:0 2px;text-align:right;font-size:13px;line-height:1;font-weight:400;font-family:inherit;border-radius:2px;color:currentColor;background:#0000;transition:background .4s;user-select:text;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-page-number-input:hover,.netless-app-presentation-page-number-input:focus,.netless-app-presentation-page-number-input:active{background:#fff;box-shadow:#63636333 0 2px 8px}.netless-app-presentation-readonly.netless-app-presentation-footer{display:none}.netless-app-presentation-wb-view{position:absolute;top:0;left:0;width:100%;height:100%;z-index:100;overflow:hidden;transition:opacity .2s}.netless-app-presentation-image{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.netless-app-presentation-image>img{max-width:100%;max-height:100%;object-fit:contain}.telebox-color-scheme-dark .netless-app-presentation-page-number-input{color:#a6a6a8}.telebox-color-scheme-dark .netless-app-presentation-page-number-input:active,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:focus,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:hover{color:#222}.telebox-color-scheme-dark .netless-app-presentation-footer{color:#a6a6a8;background:#2d2d33;border-top:none}.telebox-color-scheme-dark .netless-app-presentation-footer-btn:hover{background:#212126}.telebox-color-scheme-dark .netless-app-presentation-preview{background:#323232e6}.netless-app-presentation-scrollbar-container{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:101}.netless-app-presentation-scrollbar-container-x,.netless-app-presentation-scrollbar-container-y{position:absolute;pointer-events:none;display:flex;justify-content:center;box-sizing:border-box;padding:0}.netless-app-presentation-scrollbar-container-x{bottom:0;left:0;height:6px;width:100%;padding-right:6px}.netless-app-presentation-scrollbar-container-y{top:0;right:0;width:6px;height:100%;flex-direction:column;padding-bottom:6px}.netless-app-presentation-scrollbar-x,.netless-app-presentation-scrollbar-y{border-radius:2px;background-color:#1b1f4d4d;pointer-events:auto;cursor:default}.netless-app-presentation-scrollbar-x{height:6px}.netless-app-presentation-scrollbar-y{width:6px}";
1534
+ var style_default = ".netless-app-presentation{height:100%;display:flex;flex-flow:column nowrap}.netless-app-presentation-content{position:relative;height:100%;overflow:hidden}.netless-app-presentation-background-suspended .background{display:none!important}.netless-app-presentation-preview-mask{display:none;position:absolute;z-index:200;top:0;left:0;width:100%;height:100%}.netless-app-presentation-preview{display:flex;flex-direction:column;align-items:center;position:absolute;z-index:300;top:0;left:0;width:33%;max-width:200px;height:100%;padding-top:10px;transform:translate(-100%);background:#ededf0e6;box-shadow:inset -1px 0 #0000001c;transition:transform .4s;overflow:auto;overflow-y:scroll}.netless-app-presentation-preview-active .netless-app-presentation-preview-mask{display:block}.netless-app-presentation-preview-active .netless-app-presentation-preview{transform:translate(0)}.netless-app-presentation-preview-page{position:relative;display:block;width:55%;margin-bottom:10px;font-size:0;color:#0000;outline:none;border:7px solid rgba(0,0,0,0);border-radius:4px;transition:border-color .3s;user-select:none}.netless-app-presentation-preview-page:hover,.netless-app-presentation-preview-page.netless-app-presentation-preview-page-active{border-color:#444e601a}.netless-app-presentation-preview-page>img{width:100%;height:auto;box-sizing:border-box;border:1px solid rgba(0,0,0,.5);border-radius:1px;background-color:#fff;box-shadow:0 2px 8px #0000004d}.netless-app-presentation-preview-page-name{position:absolute;top:1px;left:-10px;transform:translate(-100%);text-align:right;font-size:12px;color:#5f5f5f;user-select:none}.netless-app-presentation-footer{box-sizing:border-box;height:26px;display:flex;align-items:center;padding:0 16px;border-top:1px solid #eeeef7;color:#191919}.netless-app-presentation-float-footer{width:100%;min-height:26px;position:absolute;left:0;bottom:0;z-index:2000;background:#f9f9fce6;transition:opacity .4s}.netless-app-presentation-footer-btn{box-sizing:border-box;width:26px;height:26px;font-size:0;margin:0;padding:3px;border:none;border-radius:1px;outline:none;color:currentColor;background:#0000;transition:background .4s;cursor:pointer;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-footer-btn:hover{background:#ededf0e6}@media (hover: none){.netless-app-presentation-footer-btn:hover{background:#0000!important}}.netless-app-presentation-footer-btn>svg{width:100%;height:100%}.netless-app-presentation-footer-btn>svg:nth-of-type(2){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(1){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(2){display:initial}.netless-app-presentation-footer-btn~.netless-app-presentation-footer-btn{margin-left:15px}.netless-app-presentation-page-jumps{flex:1;display:flex;justify-content:center;align-items:center}.netless-app-presentation-page-number{margin-left:auto;font-size:13px;user-select:none;white-space:nowrap;word-break:keep-all}.netless-app-presentation-page-number-input{border:none;outline:none;width:3em;margin:0;padding:0 2px;text-align:right;font-size:13px;line-height:1;font-weight:400;font-family:inherit;border-radius:2px;color:currentColor;background:#0000;transition:background .4s;user-select:text;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-page-number-input:hover,.netless-app-presentation-page-number-input:focus,.netless-app-presentation-page-number-input:active{background:#fff;box-shadow:#63636333 0 2px 8px}.netless-app-presentation-readonly.netless-app-presentation-footer{display:none}.netless-app-presentation-wb-view{position:absolute;top:0;left:0;width:100%;height:100%;z-index:100;overflow:hidden;transition:opacity .2s}.netless-app-presentation-image{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.netless-app-presentation-image>img{max-width:100%;max-height:100%;object-fit:contain}.telebox-color-scheme-dark .netless-app-presentation-page-number-input{color:#a6a6a8}.telebox-color-scheme-dark .netless-app-presentation-page-number-input:active,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:focus,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:hover{color:#222}.telebox-color-scheme-dark .netless-app-presentation-footer{color:#a6a6a8;background:#2d2d33;border-top:none}.telebox-color-scheme-dark .netless-app-presentation-footer-btn:hover{background:#212126}.telebox-color-scheme-dark .netless-app-presentation-preview{background:#323232e6}.netless-app-presentation-scrollbar-container{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:101}.netless-app-presentation-scrollbar-container-x,.netless-app-presentation-scrollbar-container-y{position:absolute;pointer-events:none;display:flex;justify-content:center;box-sizing:border-box;padding:0}.netless-app-presentation-scrollbar-container-x{bottom:0;left:0;height:6px;width:100%;padding-right:6px}.netless-app-presentation-scrollbar-container-y{top:0;right:0;width:6px;height:100%;flex-direction:column;padding-bottom:6px}.netless-app-presentation-scrollbar-x,.netless-app-presentation-scrollbar-y{border-radius:2px;background-color:#1b1f4d4d;pointer-events:auto;cursor:default}.netless-app-presentation-scrollbar-x{height:6px}.netless-app-presentation-scrollbar-y{width:6px}";
1505
1535
 
1506
1536
  // src/store.ts
1507
1537
  function noop() {
@@ -1947,6 +1977,23 @@ var NetlessAppPresentation = (function (exports) {
1947
1977
  };
1948
1978
  }
1949
1979
 
1980
+ // src/runtime-lifecycle.ts
1981
+ var createPresentationRuntimeTeardown = (box, dispose2) => {
1982
+ let tornDown = false;
1983
+ return () => {
1984
+ if (tornDown)
1985
+ return;
1986
+ tornDown = true;
1987
+ try {
1988
+ dispose2();
1989
+ } finally {
1990
+ box.unmountContent();
1991
+ box.unmountFooter();
1992
+ box.unmountStyles();
1993
+ }
1994
+ };
1995
+ };
1996
+
1950
1997
  // src/app-presentation.ts
1951
1998
  var import_debounce2 = __toESM(require_debounce());
1952
1999
  var emptySceneName = "$$empty$$";
@@ -1956,6 +2003,211 @@ var NetlessAppPresentation = (function (exports) {
1956
2003
  const size = fitPageSizeToOrigin(ppt, originSize);
1957
2004
  return __spreadProps(__spreadValues({}, size), { src: ppt.src, thumbnail: ppt.previewURL, name });
1958
2005
  };
2006
+ var findBackgroundImage = (view) => {
2007
+ const container = view.divElement;
2008
+ if (!container || typeof container.querySelector !== "function")
2009
+ return null;
2010
+ return container.querySelector('img[alt="background"]');
2011
+ };
2012
+ var isBlurDegradationAllowed = (context) => {
2013
+ var _a;
2014
+ if (((_a = context.getWindowManager()) == null ? void 0 : _a.lazySetupInMaximizedMode) !== true) {
2015
+ return false;
2016
+ }
2017
+ const boxStatus = context.getBoxStatus();
2018
+ if (boxStatus) {
2019
+ return boxStatus !== "normal";
2020
+ }
2021
+ const boxState = context.getWindowManager().boxState;
2022
+ return boxState !== void 0 && boxState !== "normal";
2023
+ };
2024
+ var DEFAULT_SETUP_READY_TIMEOUT = 5e3;
2025
+ var waitForCurrentPageImage = (view, timeoutMs, isDisposed) => {
2026
+ return new Promise((resolve) => {
2027
+ let settled = false;
2028
+ let pollTimer;
2029
+ const settle = (loaded) => {
2030
+ if (settled)
2031
+ return;
2032
+ settled = true;
2033
+ window.clearTimeout(timeoutTimer);
2034
+ if (pollTimer !== void 0)
2035
+ window.clearInterval(pollTimer);
2036
+ resolve(loaded);
2037
+ };
2038
+ const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
2039
+ const check = () => {
2040
+ if (isDisposed()) {
2041
+ settle(false);
2042
+ return;
2043
+ }
2044
+ const img = findBackgroundImage(view);
2045
+ if (img && img.complete && img.naturalWidth > 0) {
2046
+ settle(true);
2047
+ }
2048
+ };
2049
+ check();
2050
+ if (!settled) {
2051
+ pollTimer = window.setInterval(check, 100);
2052
+ }
2053
+ });
2054
+ };
2055
+ var resolveImageUrl = (url) => {
2056
+ try {
2057
+ return new URL(url, window.location.href).href;
2058
+ } catch (e) {
2059
+ return url;
2060
+ }
2061
+ };
2062
+ var TRANSPARENT_BACKGROUND = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
2063
+ var BACKGROUND_SUSPENDED_CLASS = "netless-app-presentation-background-suspended";
2064
+ var hasUsableThumbnail = (page) => {
2065
+ const thumbnailURL = page.thumbnail;
2066
+ if (!thumbnailURL)
2067
+ return false;
2068
+ try {
2069
+ const source = new URL(page.src, window.location.href);
2070
+ const thumbnail = new URL(thumbnailURL, window.location.href);
2071
+ const sameResourcePath = source.origin === thumbnail.origin && source.pathname === thumbnail.pathname;
2072
+ if (/\.gif$/i.test(source.pathname) && sameResourcePath)
2073
+ return false;
2074
+ } catch (e) {
2075
+ if (/\.gif(?:$|[?#])/i.test(page.src))
2076
+ return false;
2077
+ }
2078
+ return resolveImageUrl(thumbnailURL) !== resolveImageUrl(page.src);
2079
+ };
2080
+ var setupBlurThumbnailDegradation = (config) => {
2081
+ const { context, view, pages, pageIndex$, app, dispose: dispose2 } = config;
2082
+ const box = context.getBox();
2083
+ const boxBlurClass = box.wrapClassName("blur");
2084
+ let blurEpisode = false;
2085
+ let reDegradeEnabled = false;
2086
+ let boxBlurred = box.$box.classList.contains(boxBlurClass);
2087
+ const setBackgroundSuspended = (suspended) => {
2088
+ app.whiteboardDOM.classList.toggle(
2089
+ BACKGROUND_SUSPENDED_CLASS,
2090
+ suspended
2091
+ );
2092
+ };
2093
+ const degrade = () => {
2094
+ const page = pages[pageIndex$.value];
2095
+ if (!page)
2096
+ return;
2097
+ const thumbnailURL = page.thumbnail;
2098
+ const img = findBackgroundImage(view);
2099
+ if (!thumbnailURL || !hasUsableThumbnail(page)) {
2100
+ if (boxBlurred) {
2101
+ setBackgroundSuspended(true);
2102
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(TRANSPARENT_BACKGROUND)) {
2103
+ img.src = TRANSPARENT_BACKGROUND;
2104
+ }
2105
+ }
2106
+ return;
2107
+ }
2108
+ setBackgroundSuspended(false);
2109
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(thumbnailURL)) {
2110
+ img.src = thumbnailURL;
2111
+ }
2112
+ };
2113
+ const restore2 = () => {
2114
+ const page = pages[pageIndex$.value];
2115
+ const img = findBackgroundImage(view);
2116
+ if (page && img && resolveImageUrl(img.src) !== resolveImageUrl(page.src)) {
2117
+ img.src = page.src;
2118
+ }
2119
+ setBackgroundSuspended(false);
2120
+ };
2121
+ const suspendCachedResources = () => {
2122
+ if (!blurEpisode || !boxBlurred)
2123
+ return;
2124
+ app.suspendPreviewResources();
2125
+ const page = pages[pageIndex$.value];
2126
+ if (!page || !hasUsableThumbnail(page) || reDegradeEnabled) {
2127
+ degrade();
2128
+ }
2129
+ };
2130
+ const restoreFocusedResources = () => {
2131
+ if (!blurEpisode)
2132
+ return;
2133
+ blurEpisode = false;
2134
+ reDegradeEnabled = false;
2135
+ restore2();
2136
+ app.preload.resume(pageIndex$.value);
2137
+ };
2138
+ const isBackgroundImg = (target) => {
2139
+ var _a;
2140
+ const el = target;
2141
+ return Boolean(
2142
+ el && el.tagName === "IMG" && ((_a = el.matches) == null ? void 0 : _a.call(el, 'img[alt="background"]'))
2143
+ );
2144
+ };
2145
+ const ownsTarget = (target) => {
2146
+ if (!isBackgroundImg(target))
2147
+ return false;
2148
+ const el = view.divElement;
2149
+ return !!(el && el.contains(target));
2150
+ };
2151
+ const onImgLoad = (ev) => {
2152
+ if (!blurEpisode || !reDegradeEnabled)
2153
+ return;
2154
+ if (!ownsTarget(ev.target))
2155
+ return;
2156
+ degrade();
2157
+ };
2158
+ const onImgError = (ev) => {
2159
+ if (!blurEpisode || !reDegradeEnabled)
2160
+ return;
2161
+ if (!ownsTarget(ev.target))
2162
+ return;
2163
+ reDegradeEnabled = false;
2164
+ };
2165
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2166
+ if (isFocused) {
2167
+ restoreFocusedResources();
2168
+ } else {
2169
+ if (!isBlurDegradationAllowed(context))
2170
+ return;
2171
+ blurEpisode = true;
2172
+ reDegradeEnabled = true;
2173
+ degrade();
2174
+ app.preload.pause();
2175
+ suspendCachedResources();
2176
+ }
2177
+ });
2178
+ dispose2.add(offFocus);
2179
+ const updateBoxBlurred = (blurred) => {
2180
+ if (boxBlurred === blurred)
2181
+ return;
2182
+ boxBlurred = blurred;
2183
+ if (blurred)
2184
+ suspendCachedResources();
2185
+ else
2186
+ restoreFocusedResources();
2187
+ };
2188
+ const onBoxBlur = () => updateBoxBlurred(true);
2189
+ const onBoxFocus = () => updateBoxBlurred(false);
2190
+ box.events.on("blur", onBoxBlur);
2191
+ box.events.on("focus", onBoxFocus);
2192
+ dispose2.add(() => {
2193
+ box.events.off("blur", onBoxBlur);
2194
+ box.events.off("focus", onBoxFocus);
2195
+ });
2196
+ const boxClassObserver = new MutationObserver(() => {
2197
+ updateBoxBlurred(box.$box.classList.contains(boxBlurClass));
2198
+ });
2199
+ boxClassObserver.observe(box.$box, {
2200
+ attributes: true,
2201
+ attributeFilter: ["class"]
2202
+ });
2203
+ dispose2.add(() => boxClassObserver.disconnect());
2204
+ document.addEventListener("load", onImgLoad, true);
2205
+ document.addEventListener("error", onImgError, true);
2206
+ dispose2.add(() => {
2207
+ document.removeEventListener("load", onImgLoad, true);
2208
+ document.removeEventListener("error", onImgError, true);
2209
+ });
2210
+ };
1959
2211
  var createLogger = (room) => {
1960
2212
  if (room && room.logger) {
1961
2213
  return (...args) => room.logger.info(...args);
@@ -2031,7 +2283,7 @@ var NetlessAppPresentation = (function (exports) {
2031
2283
  var NetlessAppPresentation = {
2032
2284
  kind: "Presentation",
2033
2285
  setup(context) {
2034
- var _a, _b, _c;
2286
+ var _a, _b, _c, _d;
2035
2287
  const diagnosticLogger = createDiagnosticLogger(context);
2036
2288
  const view = context.getView();
2037
2289
  if (!view)
@@ -2377,10 +2629,13 @@ var NetlessAppPresentation = (function (exports) {
2377
2629
  options.useClipView
2378
2630
  )
2379
2631
  );
2380
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.0";
2632
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.2";
2381
2633
  app.scaleDocsToFit = scaleDocsToFit;
2382
2634
  app.log = log;
2383
2635
  app.warn = warn;
2636
+ if (pageIndex$.value !== 0) {
2637
+ app.preload.touch(pageIndex$.value, true);
2638
+ }
2384
2639
  const getCameraDiagnosticState = (reason, requestedCamera) => {
2385
2640
  const page = app.page();
2386
2641
  const pageSize = page && isValidSize(page) ? { width: page.width, height: page.height } : void 0;
@@ -2437,8 +2692,8 @@ var NetlessAppPresentation = (function (exports) {
2437
2692
  app.setDocsViewReadonly(true);
2438
2693
  }
2439
2694
  const goToPageByClick = () => {
2440
- var _a2, _b2, _c2, _d;
2441
- 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 : "";
2695
+ var _a2, _b2, _c2, _d2;
2696
+ 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 : "";
2442
2697
  if (!app.readonly && currentApplianceName === "clicker") {
2443
2698
  nextPage();
2444
2699
  }
@@ -2485,6 +2740,9 @@ var NetlessAppPresentation = (function (exports) {
2485
2740
  app.setReadonly(!isWritable);
2486
2741
  })
2487
2742
  );
2743
+ if (!options.disableBlurThumbnailDegradation) {
2744
+ setupBlurThumbnailDegradation({ context, view, pages, pageIndex$, app, dispose: dispose2 });
2745
+ }
2488
2746
  const getOriginScale = () => {
2489
2747
  const page = app.page();
2490
2748
  if (!page || !isValidSize(page))
@@ -2613,6 +2871,10 @@ var NetlessAppPresentation = (function (exports) {
2613
2871
  };
2614
2872
  let disposed = false;
2615
2873
  let offDestroy;
2874
+ const teardownRuntime = createPresentationRuntimeTeardown(
2875
+ box,
2876
+ dispose2
2877
+ );
2616
2878
  const teardown = () => {
2617
2879
  if (disposed)
2618
2880
  return;
@@ -2620,7 +2882,7 @@ var NetlessAppPresentation = (function (exports) {
2620
2882
  const removeDestroy = offDestroy;
2621
2883
  offDestroy = void 0;
2622
2884
  removeDestroy == null ? void 0 : removeDestroy();
2623
- dispose2();
2885
+ teardownRuntime();
2624
2886
  };
2625
2887
  offDestroy = context.emitter.on("destroy", teardown);
2626
2888
  teardownByContext.set(context, teardown);
@@ -2834,9 +3096,14 @@ var NetlessAppPresentation = (function (exports) {
2834
3096
  }
2835
3097
  )
2836
3098
  );
2837
- return prepareScenesPromise.then(
2838
- () => controller
2839
- );
3099
+ const setupReadyTimeout = (_d = options.setupReadyTimeout) != null ? _d : DEFAULT_SETUP_READY_TIMEOUT;
3100
+ return prepareScenesPromise.then(async () => {
3101
+ const loaded = await waitForCurrentPageImage(view, setupReadyTimeout, () => disposed);
3102
+ if (!loaded && !disposed) {
3103
+ warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
3104
+ }
3105
+ return controller;
3106
+ });
2840
3107
  },
2841
3108
  teardown(context) {
2842
3109
  var _a;
@@ -2922,13 +3189,14 @@ var NetlessAppPresentation = (function (exports) {
2922
3189
  };
2923
3190
 
2924
3191
  // src/index.ts
2925
- var version = "0.1.13-beta.0";
3192
+ var version = "0.1.13-beta.2";
2926
3193
 
2927
3194
  exports.NetlessAppPresentation = NetlessAppPresentation;
2928
3195
  exports.Presentation = Presentation;
2929
3196
  exports.Scrollbar = Scrollbar;
2930
3197
  exports.default = NetlessAppPresentation;
2931
3198
  exports.install = install;
3199
+ exports.setupBlurThumbnailDegradation = setupBlurThumbnailDegradation;
2932
3200
  exports.version = version;
2933
3201
 
2934
3202
  Object.defineProperty(exports, '__esModule', { value: true });