@netless/app-presentation 0.1.13-beta.0 → 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.
package/dist/index.d.ts CHANGED
@@ -218,7 +218,10 @@ declare class Preload implements IDisposable {
218
218
  readonly preloadMap: Map<number, IPreloadMapValue>;
219
219
  touchIndex: number;
220
220
  preloadSize: number;
221
+ paused: boolean;
221
222
  constructor(pages: PresentationPage[]);
223
+ pause(): void;
224
+ resume(index?: number): void;
222
225
  touch(index: number, force?: boolean): void;
223
226
  private createLink;
224
227
  private destroySomeLink;
@@ -420,6 +423,17 @@ interface PresentationAppOptions {
420
423
  goToPageByClick?: boolean;
421
424
  /** 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
425
  useClipView?: boolean;
426
+ /**
427
+ * Max time (ms) `setup()` waits for the first visible page image to load
428
+ * before resolving anyway. Default: 5_000.
429
+ */
430
+ setupReadyTimeout?: number;
431
+ /**
432
+ * Blur thumbnail degradation keeps cached (focused-lost) presentations on
433
+ * the low-res `previewURL` thumbnail to save memory. Enabled by default in
434
+ * maximized/minimized states; set `true` to disable it.
435
+ */
436
+ disableBlurThumbnailDegradation?: boolean;
423
437
  }
424
438
  interface PresentationController {
425
439
  readonly app: Presentation;
@@ -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);
@@ -2031,7 +2180,7 @@ var NetlessAppPresentation = (function (exports) {
2031
2180
  var NetlessAppPresentation = {
2032
2181
  kind: "Presentation",
2033
2182
  setup(context) {
2034
- var _a, _b, _c;
2183
+ var _a, _b, _c, _d;
2035
2184
  const diagnosticLogger = createDiagnosticLogger(context);
2036
2185
  const view = context.getView();
2037
2186
  if (!view)
@@ -2377,10 +2526,13 @@ var NetlessAppPresentation = (function (exports) {
2377
2526
  options.useClipView
2378
2527
  )
2379
2528
  );
2380
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.0";
2529
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.1";
2381
2530
  app.scaleDocsToFit = scaleDocsToFit;
2382
2531
  app.log = log;
2383
2532
  app.warn = warn;
2533
+ if (pageIndex$.value !== 0) {
2534
+ app.preload.touch(pageIndex$.value, true);
2535
+ }
2384
2536
  const getCameraDiagnosticState = (reason, requestedCamera) => {
2385
2537
  const page = app.page();
2386
2538
  const pageSize = page && isValidSize(page) ? { width: page.width, height: page.height } : void 0;
@@ -2437,8 +2589,8 @@ var NetlessAppPresentation = (function (exports) {
2437
2589
  app.setDocsViewReadonly(true);
2438
2590
  }
2439
2591
  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 : "";
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 : "";
2442
2594
  if (!app.readonly && currentApplianceName === "clicker") {
2443
2595
  nextPage();
2444
2596
  }
@@ -2485,6 +2637,9 @@ var NetlessAppPresentation = (function (exports) {
2485
2637
  app.setReadonly(!isWritable);
2486
2638
  })
2487
2639
  );
2640
+ if (!options.disableBlurThumbnailDegradation) {
2641
+ setupBlurThumbnailDegradation({ context, view, pages, pageIndex$, app, dispose: dispose2 });
2642
+ }
2488
2643
  const getOriginScale = () => {
2489
2644
  const page = app.page();
2490
2645
  if (!page || !isValidSize(page))
@@ -2834,9 +2989,14 @@ var NetlessAppPresentation = (function (exports) {
2834
2989
  }
2835
2990
  )
2836
2991
  );
2837
- return prepareScenesPromise.then(
2838
- () => controller
2839
- );
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
+ });
2840
3000
  },
2841
3001
  teardown(context) {
2842
3002
  var _a;
@@ -2922,7 +3082,7 @@ var NetlessAppPresentation = (function (exports) {
2922
3082
  };
2923
3083
 
2924
3084
  // src/index.ts
2925
- var version = "0.1.13-beta.0";
3085
+ var version = "0.1.13-beta.1";
2926
3086
 
2927
3087
  exports.NetlessAppPresentation = NetlessAppPresentation;
2928
3088
  exports.Presentation = Presentation;
package/dist/index.js CHANGED
@@ -1151,6 +1151,9 @@ var _Preload = class _Preload {
1151
1151
  // 当前触摸的索引
1152
1152
  this.touchIndex = 0;
1153
1153
  this.preloadSize = 0;
1154
+ // 暂停后不再创建预加载链接(app 失焦降级时调用);已插入的 link 会被移除,
1155
+ // 挂起的 idle 回调重新进入 touch() 时也会被跳过
1156
+ this.paused = false;
1154
1157
  this.preloadMap = new Map(pages.map((e, index) => [index, {
1155
1158
  src: e.src,
1156
1159
  state: 0 /* unloaded */
@@ -1158,7 +1161,21 @@ var _Preload = class _Preload {
1158
1161
  this.preloadSize = this.preloadMap.size;
1159
1162
  this.touch(0, true);
1160
1163
  }
1164
+ pause() {
1165
+ if (this.paused)
1166
+ return;
1167
+ this.paused = true;
1168
+ this.destroySomeLink();
1169
+ }
1170
+ resume(index) {
1171
+ if (!this.paused)
1172
+ return;
1173
+ this.paused = false;
1174
+ this.touch(index != null ? index : this.touchIndex, true);
1175
+ }
1161
1176
  touch(index, force = false) {
1177
+ if (this.paused)
1178
+ return;
1162
1179
  if (index >= this.preloadSize) {
1163
1180
  this.touchIndex = 0;
1164
1181
  } else {
@@ -1957,6 +1974,138 @@ var ppt2page = (ppt, name, originSize) => {
1957
1974
  const size = fitPageSizeToOrigin(ppt, originSize);
1958
1975
  return __spreadProps(__spreadValues({}, size), { src: ppt.src, thumbnail: ppt.previewURL, name });
1959
1976
  };
1977
+ var findBackgroundImage = (view) => {
1978
+ const container = view.divElement;
1979
+ if (!container || typeof container.querySelector !== "function")
1980
+ return null;
1981
+ return container.querySelector('img[alt="background"]');
1982
+ };
1983
+ var isBlurDegradationAllowed = (context) => {
1984
+ const boxStatus = context.getBoxStatus();
1985
+ if (boxStatus) {
1986
+ return boxStatus !== "normal";
1987
+ }
1988
+ const boxState = context.getWindowManager().boxState;
1989
+ return boxState !== void 0 && boxState !== "normal";
1990
+ };
1991
+ var DEFAULT_SETUP_READY_TIMEOUT = 5e3;
1992
+ var waitForCurrentPageImage = (view, timeoutMs, isDisposed) => {
1993
+ return new Promise((resolve) => {
1994
+ let settled = false;
1995
+ let pollTimer;
1996
+ const settle = (loaded) => {
1997
+ if (settled)
1998
+ return;
1999
+ settled = true;
2000
+ window.clearTimeout(timeoutTimer);
2001
+ if (pollTimer !== void 0)
2002
+ window.clearInterval(pollTimer);
2003
+ resolve(loaded);
2004
+ };
2005
+ const timeoutTimer = window.setTimeout(() => settle(false), timeoutMs);
2006
+ const check = () => {
2007
+ if (isDisposed()) {
2008
+ settle(false);
2009
+ return;
2010
+ }
2011
+ const img = findBackgroundImage(view);
2012
+ if (img && img.complete && img.naturalWidth > 0) {
2013
+ settle(true);
2014
+ }
2015
+ };
2016
+ check();
2017
+ if (!settled) {
2018
+ pollTimer = window.setInterval(check, 100);
2019
+ }
2020
+ });
2021
+ };
2022
+ var resolveImageUrl = (url) => {
2023
+ try {
2024
+ return new URL(url, window.location.href).href;
2025
+ } catch (e) {
2026
+ return url;
2027
+ }
2028
+ };
2029
+ var setupBlurThumbnailDegradation = (config) => {
2030
+ const { context, view, pages, pageIndex$, app, dispose: dispose2 } = config;
2031
+ let blurEpisode = false;
2032
+ let reDegradeEnabled = false;
2033
+ const degrade = () => {
2034
+ const page = pages[pageIndex$.value];
2035
+ if (!(page == null ? void 0 : page.thumbnail))
2036
+ return;
2037
+ const img = findBackgroundImage(view);
2038
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(page.thumbnail)) {
2039
+ img.src = page.thumbnail;
2040
+ }
2041
+ };
2042
+ const restore2 = () => {
2043
+ const page = pages[pageIndex$.value];
2044
+ const img = findBackgroundImage(view);
2045
+ if (page && img && resolveImageUrl(img.src) !== resolveImageUrl(page.src)) {
2046
+ img.src = page.src;
2047
+ }
2048
+ };
2049
+ const isBackgroundImg = (target) => {
2050
+ var _a;
2051
+ const el = target;
2052
+ return Boolean(
2053
+ el && el.tagName === "IMG" && ((_a = el.matches) == null ? void 0 : _a.call(el, 'img[alt="background"]'))
2054
+ );
2055
+ };
2056
+ const onImgLoad = (ev) => {
2057
+ if (!blurEpisode || !reDegradeEnabled)
2058
+ return;
2059
+ if (!isBackgroundImg(ev.target))
2060
+ return;
2061
+ degrade();
2062
+ };
2063
+ const onImgError = (ev) => {
2064
+ if (!blurEpisode || !reDegradeEnabled)
2065
+ return;
2066
+ if (!isBackgroundImg(ev.target))
2067
+ return;
2068
+ reDegradeEnabled = false;
2069
+ };
2070
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2071
+ if (isFocused) {
2072
+ if (blurEpisode) {
2073
+ blurEpisode = false;
2074
+ reDegradeEnabled = false;
2075
+ restore2();
2076
+ app.preload.resume(pageIndex$.value);
2077
+ }
2078
+ } else {
2079
+ if (!isBlurDegradationAllowed(context))
2080
+ return;
2081
+ blurEpisode = true;
2082
+ reDegradeEnabled = true;
2083
+ degrade();
2084
+ app.preload.pause();
2085
+ }
2086
+ });
2087
+ dispose2.add(offFocus);
2088
+ const viewEl = view.divElement;
2089
+ if (viewEl && typeof viewEl.addEventListener === "function") {
2090
+ viewEl.addEventListener("load", onImgLoad, true);
2091
+ viewEl.addEventListener("error", onImgError, true);
2092
+ dispose2.add(() => {
2093
+ viewEl.removeEventListener("load", onImgLoad, true);
2094
+ viewEl.removeEventListener("error", onImgError, true);
2095
+ });
2096
+ } else {
2097
+ warnOnceMissingViewContainer(view);
2098
+ }
2099
+ };
2100
+ var degradedWarnedViews = /* @__PURE__ */ new WeakSet();
2101
+ var warnOnceMissingViewContainer = (view) => {
2102
+ if (degradedWarnedViews.has(view))
2103
+ return;
2104
+ degradedWarnedViews.add(view);
2105
+ console.warn(
2106
+ "[Presentation]: blur thumbnail degradation runs without re-degrade coverage (view container unavailable)"
2107
+ );
2108
+ };
1960
2109
  var createLogger = (room) => {
1961
2110
  if (room && room.logger) {
1962
2111
  return (...args) => room.logger.info(...args);
@@ -2032,7 +2181,7 @@ var teardownByContext = /* @__PURE__ */ new WeakMap();
2032
2181
  var NetlessAppPresentation = {
2033
2182
  kind: "Presentation",
2034
2183
  setup(context) {
2035
- var _a, _b, _c;
2184
+ var _a, _b, _c, _d;
2036
2185
  const diagnosticLogger = createDiagnosticLogger(context);
2037
2186
  const view = context.getView();
2038
2187
  if (!view)
@@ -2378,10 +2527,13 @@ var NetlessAppPresentation = {
2378
2527
  options.useClipView
2379
2528
  )
2380
2529
  );
2381
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.0";
2530
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.1";
2382
2531
  app.scaleDocsToFit = scaleDocsToFit;
2383
2532
  app.log = log;
2384
2533
  app.warn = warn;
2534
+ if (pageIndex$.value !== 0) {
2535
+ app.preload.touch(pageIndex$.value, true);
2536
+ }
2385
2537
  const getCameraDiagnosticState = (reason, requestedCamera) => {
2386
2538
  const page = app.page();
2387
2539
  const pageSize = page && isValidSize(page) ? { width: page.width, height: page.height } : void 0;
@@ -2438,8 +2590,8 @@ var NetlessAppPresentation = {
2438
2590
  app.setDocsViewReadonly(true);
2439
2591
  }
2440
2592
  const goToPageByClick = () => {
2441
- var _a2, _b2, _c2, _d;
2442
- 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 : "";
2593
+ var _a2, _b2, _c2, _d2;
2594
+ 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 : "";
2443
2595
  if (!app.readonly && currentApplianceName === "clicker") {
2444
2596
  nextPage();
2445
2597
  }
@@ -2486,6 +2638,9 @@ var NetlessAppPresentation = {
2486
2638
  app.setReadonly(!isWritable);
2487
2639
  })
2488
2640
  );
2641
+ if (!options.disableBlurThumbnailDegradation) {
2642
+ setupBlurThumbnailDegradation({ context, view, pages, pageIndex$, app, dispose: dispose2 });
2643
+ }
2489
2644
  const getOriginScale = () => {
2490
2645
  const page = app.page();
2491
2646
  if (!page || !isValidSize(page))
@@ -2835,9 +2990,14 @@ var NetlessAppPresentation = {
2835
2990
  }
2836
2991
  )
2837
2992
  );
2838
- return prepareScenesPromise.then(
2839
- () => controller
2840
- );
2993
+ const setupReadyTimeout = (_d = options.setupReadyTimeout) != null ? _d : DEFAULT_SETUP_READY_TIMEOUT;
2994
+ return prepareScenesPromise.then(async () => {
2995
+ const loaded = await waitForCurrentPageImage(view, setupReadyTimeout, () => disposed);
2996
+ if (!loaded) {
2997
+ warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
2998
+ }
2999
+ return controller;
3000
+ });
2841
3001
  },
2842
3002
  teardown(context) {
2843
3003
  var _a;
@@ -2923,7 +3083,7 @@ var install = (register, options = {}) => {
2923
3083
  };
2924
3084
 
2925
3085
  // src/index.ts
2926
- var version = "0.1.13-beta.0";
3086
+ var version = "0.1.13-beta.1";
2927
3087
 
2928
3088
  exports.NetlessAppPresentation = NetlessAppPresentation;
2929
3089
  exports.Presentation = Presentation;