@netless/app-presentation 0.1.13-beta.2 → 0.1.13-beta.4

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/README-zh.md CHANGED
@@ -93,6 +93,24 @@ scenePpt.height = ppt.height * ratio
93
93
 
94
94
  ### 应用选项
95
95
 
96
+ #### Lazy 缓存生命周期与图片就绪
97
+
98
+ Lazy setup/cache 需要带有 `teardown` 和 focus 事件生命周期的 WindowManager
99
+ `1.0.23-beta.1` 或更高版本;当前集成验证版本为 `1.0.23-beta.3`。
100
+ 未启用 lazy 的旧宿主仍可使用普通查看功能。
101
+
102
+ `setupReadyTimeout` 限制当前背景图的等待时间,默认 5000ms。图片加载成功或请求失败
103
+ 均结束等待;尚未设置图片地址的元素仍等待 SDK 赋值。失败/超时会记录警告并允许 setup
104
+ 完成,不会取消 SDK 自己的图片加载或重试。
105
+
106
+ 在 lazy 最大化/最小化模式下,失焦 App 优先使用可用的 `ppt.previewURL` 缩略图;
107
+ 没有可用缩略图时隐藏背景并替换为静态像素,重新聚焦恢复原图。`thumbnail(src)` 自定义
108
+ 侧边预览图,不参与此背景降级策略。设置 `disableBlurThumbnailDegradation: true` 可关闭降级。
109
+ 该优化减少常驻图片资源,不保证节省下载流量:宿主可能在首次 setup 等待完成后才补发焦点状态。
110
+
111
+ 预加载为尽力而为:失败时移除对应 link,不自动循环重试;显式重新访问失败页时可再次尝试
112
+ 预加载。SDK 的可见图片加载不受此策略影响。
113
+
96
114
  #### `disableDeviceCameraTransform`
97
115
 
98
116
  禁止鼠标滚轮、触摸手势等本地设备输入改变相机,但不改变程序化相机操作使用的
package/README.md CHANGED
@@ -99,6 +99,28 @@ handles app whose kind is `DocsViewer` or `Slide`.
99
99
 
100
100
  ### App Options
101
101
 
102
+ #### Lazy cache lifecycle and image readiness
103
+
104
+ Lazy setup/cache requires WindowManager `1.0.23-beta.1` or later with the
105
+ `teardown` and focus-event lifecycle; integration is currently verified with
106
+ `1.0.23-beta.3`. Older hosts without lazy mode can still use the normal viewer.
107
+
108
+ `setupReadyTimeout` bounds the current background-image wait (default 5000 ms).
109
+ A loaded image or a failed image request releases the wait; an image element
110
+ without a source still waits for the SDK to assign it. Failure/timeout logs a
111
+ warning and lets setup finish; it does not cancel SDK image loading or retrying.
112
+
113
+ In lazy maximized/minimized mode, blurred apps use `ppt.previewURL` when it is a
114
+ usable thumbnail. Otherwise their background is hidden behind a static pixel;
115
+ focus restores the original image. `thumbnail(src)` customizes sidebar previews,
116
+ not this background policy. Set `disableBlurThumbnailDegradation: true` to opt out.
117
+ This reduces retained image resources; it does not guarantee reduced downloads,
118
+ since the host can synchronize focus only after the initial setup wait.
119
+
120
+ Image preloading is best effort: failed preload links are removed without an
121
+ automatic retry loop. Explicitly revisiting the failed page can retry preloading;
122
+ the SDK's visible image loading remains independent.
123
+
102
124
  #### `disableDeviceCameraTransform`
103
125
 
104
126
  Disable camera transforms initiated by local device input, such as mouse-wheel and touch gestures,
package/dist/index.d.ts CHANGED
@@ -253,11 +253,13 @@ declare class Preload implements IDisposable {
253
253
  touchIndex: number;
254
254
  preloadSize: number;
255
255
  paused: boolean;
256
+ private disposed;
256
257
  constructor(pages: PresentationPage[]);
257
258
  pause(): void;
258
259
  resume(index?: number): void;
259
260
  touch(index: number, force?: boolean): void;
260
261
  private createLink;
262
+ private removeLink;
261
263
  private destroySomeLink;
262
264
  private requestAsyncCallBack;
263
265
  dispose(): void;
@@ -470,8 +472,8 @@ interface PresentationAppOptions {
470
472
  /** 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 */
471
473
  useClipView?: boolean;
472
474
  /**
473
- * Max time (ms) `setup()` waits for the first visible page image to load
474
- * before resolving anyway. Default: 5_000.
475
+ * First-image warning threshold in ms. Default: 5_000. New lazy hosts
476
+ * await real readiness; legacy/eager hosts resolve on timeout.
475
477
  */
476
478
  setupReadyTimeout?: number;
477
479
  /**
@@ -539,7 +541,8 @@ interface PresentationController {
539
541
  * hidden and replaced with a static pixel after TeleBox commits the blur.
540
542
  *
541
543
  * While degraded, every load of a background `<img>` is re-degraded
542
- * immediately via a capture-phase `load` listener on the view container
544
+ * immediately via a capture-phase `load` listener on the document, filtered
545
+ * to the current view container
543
546
  * (`load` does not bubble but capture works). This covers remote page turns,
544
547
  * SDK React re-renders and the SDK's own background-image onError retries —
545
548
  * any path that (re)renders the full-size image while the app stays blurred.
@@ -1153,6 +1153,7 @@ var NetlessAppPresentation = (function (exports) {
1153
1153
  // 暂停后不再创建预加载链接(app 失焦降级时调用);已插入的 link 会被移除,
1154
1154
  // 挂起的 idle 回调重新进入 touch() 时也会被跳过
1155
1155
  this.paused = false;
1156
+ this.disposed = false;
1156
1157
  this.preloadMap = new Map(pages.map((e, index) => [index, {
1157
1158
  src: e.src,
1158
1159
  state: 0 /* unloaded */
@@ -1167,13 +1168,13 @@ var NetlessAppPresentation = (function (exports) {
1167
1168
  this.destroySomeLink();
1168
1169
  }
1169
1170
  resume(index) {
1170
- if (!this.paused)
1171
+ if (!this.paused || this.disposed)
1171
1172
  return;
1172
1173
  this.paused = false;
1173
1174
  this.touch(index != null ? index : this.touchIndex, true);
1174
1175
  }
1175
1176
  touch(index, force = false) {
1176
- if (this.paused)
1177
+ if (this.paused || this.disposed)
1177
1178
  return;
1178
1179
  if (index >= this.preloadSize) {
1179
1180
  this.touchIndex = 0;
@@ -1181,11 +1182,11 @@ var NetlessAppPresentation = (function (exports) {
1181
1182
  this.touchIndex = index;
1182
1183
  }
1183
1184
  const value = this.preloadMap.get(this.touchIndex);
1184
- if (value && value.state === 0 /* unloaded */) {
1185
+ if (value && (value.state === 0 /* unloaded */ || force && value.state === 2 /* error */)) {
1185
1186
  this.createLink(this.touchIndex, force);
1186
1187
  }
1187
1188
  if (this.loadingLinks.size < _Preload.maxLinks) {
1188
- const willLoad = [...this.preloadMap.entries()].find(([i, e]) => i > this.touchIndex && e.state !== 1 /* loaded */);
1189
+ const willLoad = [...this.preloadMap.entries()].find(([i, e]) => i > this.touchIndex && e.state === 0 /* unloaded */);
1189
1190
  if (willLoad) {
1190
1191
  this.requestAsyncCallBack(() => {
1191
1192
  this.touch(willLoad[0], false);
@@ -1218,21 +1219,27 @@ var NetlessAppPresentation = (function (exports) {
1218
1219
  linkDom.as = "image";
1219
1220
  linkDom.href = value.src;
1220
1221
  linkDom.dataset.order = index + "";
1221
- document.head.appendChild(linkDom);
1222
1222
  linkDom.onload = () => {
1223
+ var _a;
1224
+ if (((_a = this.loadingLinks.get(index)) == null ? void 0 : _a.link) !== linkDom)
1225
+ return;
1223
1226
  const value2 = this.preloadMap.get(index);
1224
1227
  if (value2) {
1225
1228
  value2.state = 1 /* loaded */;
1226
1229
  this.preloadMap.set(index, value2);
1227
- document.head.contains(linkDom) && document.head.removeChild(linkDom);
1228
- this.loadingLinks.delete(index);
1230
+ this.removeLink(index, linkDom);
1229
1231
  this.touch(this.touchIndex + 1, false);
1230
1232
  }
1231
1233
  };
1232
1234
  linkDom.onerror = () => {
1235
+ var _a;
1236
+ if (((_a = this.loadingLinks.get(index)) == null ? void 0 : _a.link) !== linkDom)
1237
+ return;
1233
1238
  const value2 = this.preloadMap.get(index);
1234
- if (value2 == null ? void 0 : value2.src) {
1235
- linkDom.href = value2.src;
1239
+ if (value2) {
1240
+ value2.state = 2 /* error */;
1241
+ this.removeLink(index, linkDom);
1242
+ this.touch(this.touchIndex + 1, false);
1236
1243
  }
1237
1244
  };
1238
1245
  this.loadingLinks.set(index, {
@@ -1241,17 +1248,23 @@ var NetlessAppPresentation = (function (exports) {
1241
1248
  });
1242
1249
  value.state = 0 /* unloaded */;
1243
1250
  this.preloadMap.set(index, value);
1251
+ document.head.appendChild(linkDom);
1244
1252
  return 0 /* unloaded */;
1245
1253
  }
1246
1254
  return 2 /* error */;
1247
1255
  }
1256
+ removeLink(index, link) {
1257
+ link.onload = null;
1258
+ link.onerror = null;
1259
+ document.head.contains(link) && document.head.removeChild(link);
1260
+ this.loadingLinks.delete(index);
1261
+ }
1248
1262
  destroySomeLink(excludeIndex) {
1249
1263
  for (const [index, { link }] of this.loadingLinks) {
1250
1264
  if (excludeIndex !== void 0 && index === excludeIndex) {
1251
1265
  continue;
1252
1266
  }
1253
- document.head.contains(link) && document.head.removeChild(link);
1254
- this.loadingLinks.delete(index);
1267
+ this.removeLink(index, link);
1255
1268
  }
1256
1269
  }
1257
1270
  async requestAsyncCallBack(callBack, timeout) {
@@ -1269,6 +1282,7 @@ var NetlessAppPresentation = (function (exports) {
1269
1282
  callBack();
1270
1283
  }
1271
1284
  dispose() {
1285
+ this.disposed = true;
1272
1286
  this.destroySomeLink();
1273
1287
  this.preloadMap.clear();
1274
1288
  }
@@ -1981,19 +1995,72 @@ var NetlessAppPresentation = (function (exports) {
1981
1995
  var createPresentationRuntimeTeardown = (box, dispose2) => {
1982
1996
  let tornDown = false;
1983
1997
  return () => {
1998
+ var _a, _b, _c;
1984
1999
  if (tornDown)
1985
2000
  return;
1986
2001
  tornDown = true;
1987
2002
  try {
1988
2003
  dispose2();
1989
2004
  } finally {
1990
- box.unmountContent();
1991
- box.unmountFooter();
1992
- box.unmountStyles();
2005
+ try {
2006
+ (_a = box.unmountContent) == null ? void 0 : _a.call(box);
2007
+ } finally {
2008
+ try {
2009
+ (_b = box.unmountFooter) == null ? void 0 : _b.call(box);
2010
+ } finally {
2011
+ (_c = box.unmountStyles) == null ? void 0 : _c.call(box);
2012
+ }
2013
+ }
1993
2014
  }
1994
2015
  };
1995
2016
  };
1996
2017
 
2018
+ // src/setup-ready.ts
2019
+ var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed, options) => {
2020
+ return new Promise((resolve) => {
2021
+ let settled = false;
2022
+ let timedOut = false;
2023
+ let pollTimer;
2024
+ const settle = (result) => {
2025
+ if (settled)
2026
+ return;
2027
+ settled = true;
2028
+ window.clearTimeout(timeoutTimer);
2029
+ if (pollTimer !== void 0)
2030
+ window.clearInterval(pollTimer);
2031
+ resolve(result);
2032
+ };
2033
+ const timeoutTimer = window.setTimeout(() => {
2034
+ timedOut = true;
2035
+ options == null ? void 0 : options.onTimeout();
2036
+ if (!(options == null ? void 0 : options.waitForReady()))
2037
+ settle("timeout");
2038
+ }, timeoutMs);
2039
+ const check = () => {
2040
+ var _a, _b;
2041
+ if (isDisposed()) {
2042
+ settle("disposed");
2043
+ return;
2044
+ }
2045
+ if (timedOut && !(options == null ? void 0 : options.waitForReady())) {
2046
+ settle("timeout");
2047
+ return;
2048
+ }
2049
+ const img = getImage();
2050
+ if (img == null ? void 0 : img.complete) {
2051
+ if (img.naturalWidth > 0) {
2052
+ settle("loaded");
2053
+ } else if (img.currentSrc || ((_a = img.getAttribute("src")) == null ? void 0 : _a.trim()) || ((_b = img.getAttribute("srcset")) == null ? void 0 : _b.trim())) {
2054
+ settle("error");
2055
+ }
2056
+ }
2057
+ };
2058
+ check();
2059
+ if (!settled)
2060
+ pollTimer = window.setInterval(check, 100);
2061
+ });
2062
+ };
2063
+
1997
2064
  // src/app-presentation.ts
1998
2065
  var import_debounce2 = __toESM(require_debounce());
1999
2066
  var emptySceneName = "$$empty$$";
@@ -2022,36 +2089,6 @@ var NetlessAppPresentation = (function (exports) {
2022
2089
  return boxState !== void 0 && boxState !== "normal";
2023
2090
  };
2024
2091
  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
2092
  var resolveImageUrl = (url) => {
2056
2093
  try {
2057
2094
  return new URL(url, window.location.href).href;
@@ -2083,6 +2120,19 @@ var NetlessAppPresentation = (function (exports) {
2083
2120
  const boxBlurClass = box.wrapClassName("blur");
2084
2121
  let blurEpisode = false;
2085
2122
  let reDegradeEnabled = false;
2123
+ let previewSuspended = false;
2124
+ let appliedFocusState = true;
2125
+ let logicalFocused = true;
2126
+ const activityBlocked = () => {
2127
+ var _a, _b;
2128
+ const wm = context.getWindowManager();
2129
+ if ((wm == null ? void 0 : wm.lazySetupInMaximizedMode) !== true)
2130
+ return false;
2131
+ const host = (_a = context.getRuntimeActivity) == null ? void 0 : _a.call(context);
2132
+ if (host)
2133
+ return document.visibilityState === "hidden" || !host.active;
2134
+ return document.visibilityState === "hidden" || wm.boxState === "minimized" || ((_b = wm.attributes) == null ? void 0 : _b.minimized) === true || context.getBoxStatus() === "minimized";
2135
+ };
2086
2136
  let boxBlurred = box.$box.classList.contains(boxBlurClass);
2087
2137
  const setBackgroundSuspended = (suspended) => {
2088
2138
  app.whiteboardDOM.classList.toggle(
@@ -2097,7 +2147,7 @@ var NetlessAppPresentation = (function (exports) {
2097
2147
  const thumbnailURL = page.thumbnail;
2098
2148
  const img = findBackgroundImage(view);
2099
2149
  if (!thumbnailURL || !hasUsableThumbnail(page)) {
2100
- if (boxBlurred) {
2150
+ if (boxBlurred || activityBlocked()) {
2101
2151
  setBackgroundSuspended(true);
2102
2152
  if (img && resolveImageUrl(img.src) !== resolveImageUrl(TRANSPARENT_BACKGROUND)) {
2103
2153
  img.src = TRANSPARENT_BACKGROUND;
@@ -2119,9 +2169,12 @@ var NetlessAppPresentation = (function (exports) {
2119
2169
  setBackgroundSuspended(false);
2120
2170
  };
2121
2171
  const suspendCachedResources = () => {
2122
- if (!blurEpisode || !boxBlurred)
2172
+ if (!blurEpisode || !boxBlurred && !activityBlocked())
2123
2173
  return;
2124
- app.suspendPreviewResources();
2174
+ if (!previewSuspended) {
2175
+ previewSuspended = true;
2176
+ app.suspendPreviewResources();
2177
+ }
2125
2178
  const page = pages[pageIndex$.value];
2126
2179
  if (!page || !hasUsableThumbnail(page) || reDegradeEnabled) {
2127
2180
  degrade();
@@ -2132,6 +2185,7 @@ var NetlessAppPresentation = (function (exports) {
2132
2185
  return;
2133
2186
  blurEpisode = false;
2134
2187
  reDegradeEnabled = false;
2188
+ previewSuspended = false;
2135
2189
  restore2();
2136
2190
  app.preload.resume(pageIndex$.value);
2137
2191
  };
@@ -2162,27 +2216,40 @@ var NetlessAppPresentation = (function (exports) {
2162
2216
  return;
2163
2217
  reDegradeEnabled = false;
2164
2218
  };
2165
- const offFocus = context.emitter.on("focus", (isFocused) => {
2219
+ const applyActivity = () => {
2220
+ var _a;
2221
+ const lazy = ((_a = context.getWindowManager()) == null ? void 0 : _a.lazySetupInMaximizedMode) === true;
2222
+ const isFocused = (!lazy || logicalFocused) && !activityBlocked();
2223
+ if (appliedFocusState === isFocused)
2224
+ return;
2225
+ if (!isFocused && !isBlurDegradationAllowed(context) && !activityBlocked())
2226
+ return;
2166
2227
  if (isFocused) {
2167
2228
  restoreFocusedResources();
2168
2229
  } else {
2169
- if (!isBlurDegradationAllowed(context))
2170
- return;
2171
2230
  blurEpisode = true;
2172
2231
  reDegradeEnabled = true;
2173
2232
  degrade();
2174
2233
  app.preload.pause();
2175
2234
  suspendCachedResources();
2176
2235
  }
2236
+ appliedFocusState = isFocused;
2237
+ };
2238
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2239
+ logicalFocused = isFocused;
2240
+ applyActivity();
2177
2241
  });
2178
2242
  dispose2.add(offFocus);
2243
+ dispose2.add(context.emitter.on("runtimeActivity", applyActivity));
2244
+ document.addEventListener("visibilitychange", applyActivity);
2245
+ dispose2.add(() => document.removeEventListener("visibilitychange", applyActivity));
2179
2246
  const updateBoxBlurred = (blurred) => {
2180
2247
  if (boxBlurred === blurred)
2181
2248
  return;
2182
2249
  boxBlurred = blurred;
2183
2250
  if (blurred)
2184
2251
  suspendCachedResources();
2185
- else
2252
+ else if (logicalFocused && !activityBlocked())
2186
2253
  restoreFocusedResources();
2187
2254
  };
2188
2255
  const onBoxBlur = () => updateBoxBlurred(true);
@@ -2629,7 +2696,7 @@ var NetlessAppPresentation = (function (exports) {
2629
2696
  options.useClipView
2630
2697
  )
2631
2698
  );
2632
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.2";
2699
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.4";
2633
2700
  app.scaleDocsToFit = scaleDocsToFit;
2634
2701
  app.log = log;
2635
2702
  app.warn = warn;
@@ -3098,9 +3165,22 @@ var NetlessAppPresentation = (function (exports) {
3098
3165
  );
3099
3166
  const setupReadyTimeout = (_d = options.setupReadyTimeout) != null ? _d : DEFAULT_SETUP_READY_TIMEOUT;
3100
3167
  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`);
3168
+ const result = await waitForCurrentPageImage(
3169
+ () => findBackgroundImage(view),
3170
+ setupReadyTimeout,
3171
+ () => disposed,
3172
+ {
3173
+ waitForReady: () => Boolean(context.waitForActualSetupReady),
3174
+ onTimeout: () => warn(`[Presentation] setup page image still pending after ${setupReadyTimeout}ms`)
3175
+ }
3176
+ );
3177
+ if (disposed)
3178
+ throw new Error("[Presentation] disposed before setup ready");
3179
+ if (result === "error" && context.waitForActualSetupReady) {
3180
+ throw new Error("[Presentation] setup page image failed to load");
3181
+ }
3182
+ if (result === "error" && !disposed) {
3183
+ warn("[Presentation] setup page image failed to load; continuing setup");
3104
3184
  }
3105
3185
  return controller;
3106
3186
  });
@@ -3189,7 +3269,7 @@ var NetlessAppPresentation = (function (exports) {
3189
3269
  };
3190
3270
 
3191
3271
  // src/index.ts
3192
- var version = "0.1.13-beta.2";
3272
+ var version = "0.1.13-beta.4";
3193
3273
 
3194
3274
  exports.NetlessAppPresentation = NetlessAppPresentation;
3195
3275
  exports.Presentation = Presentation;