@netless/app-presentation 0.1.13-beta.3 → 0.1.13

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
@@ -276,6 +276,7 @@ interface PresentationConfig {
276
276
  readonly pages: PresentationPage[];
277
277
  readonly readonly?: boolean;
278
278
  readonly thumbnail?: (src: string) => string;
279
+ readonly onThumbnailError?: (error: unknown) => void;
279
280
  }
280
281
  /**
281
282
  * Standalone presentation slide viewer.
@@ -316,6 +317,7 @@ declare class Presentation implements IDisposable<void> {
316
317
  readonly dispose: DisposableStore;
317
318
  readonly pages: PresentationPage[];
318
319
  readonly preload: Preload;
320
+ readonly onThumbnailError?: (error: unknown) => void;
319
321
  dom: Element | DocumentFragment;
320
322
  contentDOM: HTMLDivElement;
321
323
  previewDOM: HTMLDivElement;
@@ -429,6 +431,13 @@ declare class Scrollbar {
429
431
  }
430
432
 
431
433
  type Logger = (...data: any[]) => void;
434
+ interface PresentationDiagnosticLogger {
435
+ info(event: string, payload?: unknown): void;
436
+ warn(event: string, payload?: unknown): void;
437
+ error(event: string, error: unknown, payload?: unknown): void;
438
+ debouncedInfo(event: string, payload?: unknown): void;
439
+ flush(): void;
440
+ }
432
441
  interface PresentationAttributes {
433
442
  /** Shared logical camera reference size. New pages are proportionally contained within it. */
434
443
  originSize?: Size | null;
@@ -472,8 +481,8 @@ interface PresentationAppOptions {
472
481
  /** 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 */
473
482
  useClipView?: boolean;
474
483
  /**
475
- * Max time (ms) `setup()` waits for the first visible page image to load
476
- * before resolving anyway. Default: 5_000.
484
+ * First-image warning threshold in ms. Default: 5_000. New lazy hosts
485
+ * await real readiness; legacy/eager hosts resolve on timeout.
477
486
  */
478
487
  setupReadyTimeout?: number;
479
488
  /**
@@ -555,6 +564,7 @@ declare const setupBlurThumbnailDegradation: (config: {
555
564
  app: Presentation;
556
565
  dispose: ReturnType<typeof disposableStore>;
557
566
  }) => void;
567
+ declare const createDiagnosticLogger: (context: AppContext) => PresentationDiagnosticLogger;
558
568
  declare const NetlessAppPresentation: NetlessApp<PresentationAttributes, {}, PresentationAppOptions, PresentationController> & {
559
569
  teardown(context: AppContext): void;
560
570
  };
@@ -582,5 +592,5 @@ declare const install: (register: RegisterFn, options?: InstallOptions) => Promi
582
592
 
583
593
  declare const version: string;
584
594
 
585
- export { NetlessAppPresentation, Presentation, Scrollbar, NetlessAppPresentation as default, install, setupBlurThumbnailDegradation, version };
595
+ export { NetlessAppPresentation, Presentation, Scrollbar, createDiagnosticLogger, NetlessAppPresentation as default, install, setupBlurThumbnailDegradation, version };
586
596
  export type { InstallOptions, Logger, PresentationAppOptions, PresentationAttributes, PresentationConfig, PresentationController, PresentationPage, RegisterFn, ScrollbarEventCallback, ScrollbarOption };
@@ -1304,6 +1304,7 @@ var NetlessAppPresentation = (function (exports) {
1304
1304
  var _a;
1305
1305
  this.pages = config.pages;
1306
1306
  this.preload = new Preload(this.pages);
1307
+ this.onThumbnailError = config.onThumbnailError;
1307
1308
  if (config.thumbnail)
1308
1309
  this.thumbnail = config.thumbnail;
1309
1310
  this.dispose.add(this.preload);
@@ -1519,12 +1520,16 @@ var NetlessAppPresentation = (function (exports) {
1519
1520
  }
1520
1521
  /** Returns a modified URL that points to the thumbnail image. */
1521
1522
  thumbnail(src) {
1523
+ var _a;
1522
1524
  try {
1523
1525
  const url = new URL(src);
1524
1526
  url.searchParams.set("x-oss-process", "image/resize,l_50");
1525
1527
  return url.toString();
1526
1528
  } catch (err) {
1527
- console.error(err);
1529
+ try {
1530
+ (_a = this.onThumbnailError) == null ? void 0 : _a.call(this, err);
1531
+ } catch (e) {
1532
+ }
1528
1533
  return src;
1529
1534
  }
1530
1535
  }
@@ -2016,9 +2021,10 @@ var NetlessAppPresentation = (function (exports) {
2016
2021
  };
2017
2022
 
2018
2023
  // src/setup-ready.ts
2019
- var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed) => {
2024
+ var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed, options) => {
2020
2025
  return new Promise((resolve) => {
2021
2026
  let settled = false;
2027
+ let timedOut = false;
2022
2028
  let pollTimer;
2023
2029
  const settle = (result) => {
2024
2030
  if (settled)
@@ -2029,13 +2035,22 @@ var NetlessAppPresentation = (function (exports) {
2029
2035
  window.clearInterval(pollTimer);
2030
2036
  resolve(result);
2031
2037
  };
2032
- const timeoutTimer = window.setTimeout(() => settle("timeout"), timeoutMs);
2038
+ const timeoutTimer = window.setTimeout(() => {
2039
+ timedOut = true;
2040
+ options == null ? void 0 : options.onTimeout();
2041
+ if (!(options == null ? void 0 : options.waitForReady()))
2042
+ settle("timeout");
2043
+ }, timeoutMs);
2033
2044
  const check = () => {
2034
2045
  var _a, _b;
2035
2046
  if (isDisposed()) {
2036
2047
  settle("disposed");
2037
2048
  return;
2038
2049
  }
2050
+ if (timedOut && !(options == null ? void 0 : options.waitForReady())) {
2051
+ settle("timeout");
2052
+ return;
2053
+ }
2039
2054
  const img = getImage();
2040
2055
  if (img == null ? void 0 : img.complete) {
2041
2056
  if (img.naturalWidth > 0) {
@@ -2109,7 +2124,21 @@ var NetlessAppPresentation = (function (exports) {
2109
2124
  const box = context.getBox();
2110
2125
  const boxBlurClass = box.wrapClassName("blur");
2111
2126
  let blurEpisode = false;
2112
- let reDegradeEnabled = false;
2127
+ let failedThumbnailPageIndex = null;
2128
+ const canReDegrade = () => blurEpisode && failedThumbnailPageIndex !== pageIndex$.value;
2129
+ let previewSuspended = false;
2130
+ let appliedFocusState = true;
2131
+ let logicalFocused = true;
2132
+ const activityBlocked = () => {
2133
+ var _a, _b;
2134
+ const wm = context.getWindowManager();
2135
+ if ((wm == null ? void 0 : wm.lazySetupInMaximizedMode) !== true)
2136
+ return false;
2137
+ const host = (_a = context.getRuntimeActivity) == null ? void 0 : _a.call(context);
2138
+ if (host)
2139
+ return document.visibilityState === "hidden" || !host.active;
2140
+ return document.visibilityState === "hidden" || wm.boxState === "minimized" || ((_b = wm.attributes) == null ? void 0 : _b.minimized) === true || context.getBoxStatus() === "minimized";
2141
+ };
2113
2142
  let boxBlurred = box.$box.classList.contains(boxBlurClass);
2114
2143
  const setBackgroundSuspended = (suspended) => {
2115
2144
  app.whiteboardDOM.classList.toggle(
@@ -2124,7 +2153,7 @@ var NetlessAppPresentation = (function (exports) {
2124
2153
  const thumbnailURL = page.thumbnail;
2125
2154
  const img = findBackgroundImage(view);
2126
2155
  if (!thumbnailURL || !hasUsableThumbnail(page)) {
2127
- if (boxBlurred) {
2156
+ if (boxBlurred || activityBlocked()) {
2128
2157
  setBackgroundSuspended(true);
2129
2158
  if (img && resolveImageUrl(img.src) !== resolveImageUrl(TRANSPARENT_BACKGROUND)) {
2130
2159
  img.src = TRANSPARENT_BACKGROUND;
@@ -2146,11 +2175,14 @@ var NetlessAppPresentation = (function (exports) {
2146
2175
  setBackgroundSuspended(false);
2147
2176
  };
2148
2177
  const suspendCachedResources = () => {
2149
- if (!blurEpisode || !boxBlurred)
2178
+ if (!blurEpisode || !boxBlurred && !activityBlocked())
2150
2179
  return;
2151
- app.suspendPreviewResources();
2180
+ if (!previewSuspended) {
2181
+ previewSuspended = true;
2182
+ app.suspendPreviewResources();
2183
+ }
2152
2184
  const page = pages[pageIndex$.value];
2153
- if (!page || !hasUsableThumbnail(page) || reDegradeEnabled) {
2185
+ if (!page || !hasUsableThumbnail(page) || canReDegrade()) {
2154
2186
  degrade();
2155
2187
  }
2156
2188
  };
@@ -2158,7 +2190,8 @@ var NetlessAppPresentation = (function (exports) {
2158
2190
  if (!blurEpisode)
2159
2191
  return;
2160
2192
  blurEpisode = false;
2161
- reDegradeEnabled = false;
2193
+ failedThumbnailPageIndex = null;
2194
+ previewSuspended = false;
2162
2195
  restore2();
2163
2196
  app.preload.resume(pageIndex$.value);
2164
2197
  };
@@ -2176,40 +2209,57 @@ var NetlessAppPresentation = (function (exports) {
2176
2209
  return !!(el && el.contains(target));
2177
2210
  };
2178
2211
  const onImgLoad = (ev) => {
2179
- if (!blurEpisode || !reDegradeEnabled)
2212
+ if (!canReDegrade())
2180
2213
  return;
2181
2214
  if (!ownsTarget(ev.target))
2182
2215
  return;
2183
2216
  degrade();
2184
2217
  };
2185
2218
  const onImgError = (ev) => {
2186
- if (!blurEpisode || !reDegradeEnabled)
2219
+ var _a;
2220
+ if (!canReDegrade())
2187
2221
  return;
2188
2222
  if (!ownsTarget(ev.target))
2189
2223
  return;
2190
- reDegradeEnabled = false;
2224
+ const thumbnailURL = (_a = pages[pageIndex$.value]) == null ? void 0 : _a.thumbnail;
2225
+ if (thumbnailURL && resolveImageUrl(ev.target.src) === resolveImageUrl(thumbnailURL)) {
2226
+ failedThumbnailPageIndex = pageIndex$.value;
2227
+ }
2191
2228
  };
2192
- const offFocus = context.emitter.on("focus", (isFocused) => {
2229
+ const applyActivity = () => {
2230
+ var _a;
2231
+ const lazy = ((_a = context.getWindowManager()) == null ? void 0 : _a.lazySetupInMaximizedMode) === true;
2232
+ const isFocused = (!lazy || logicalFocused) && !activityBlocked();
2233
+ if (appliedFocusState === isFocused)
2234
+ return;
2235
+ if (!isFocused && !isBlurDegradationAllowed(context) && !activityBlocked())
2236
+ return;
2193
2237
  if (isFocused) {
2194
2238
  restoreFocusedResources();
2195
2239
  } else {
2196
- if (!isBlurDegradationAllowed(context))
2197
- return;
2198
2240
  blurEpisode = true;
2199
- reDegradeEnabled = true;
2241
+ failedThumbnailPageIndex = null;
2200
2242
  degrade();
2201
2243
  app.preload.pause();
2202
2244
  suspendCachedResources();
2203
2245
  }
2246
+ appliedFocusState = isFocused;
2247
+ };
2248
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2249
+ logicalFocused = isFocused;
2250
+ applyActivity();
2204
2251
  });
2205
2252
  dispose2.add(offFocus);
2253
+ dispose2.add(context.emitter.on("runtimeActivity", applyActivity));
2254
+ document.addEventListener("visibilitychange", applyActivity);
2255
+ dispose2.add(() => document.removeEventListener("visibilitychange", applyActivity));
2206
2256
  const updateBoxBlurred = (blurred) => {
2207
2257
  if (boxBlurred === blurred)
2208
2258
  return;
2209
2259
  boxBlurred = blurred;
2210
2260
  if (blurred)
2211
2261
  suspendCachedResources();
2212
- else
2262
+ else if (logicalFocused && !activityBlocked())
2213
2263
  restoreFocusedResources();
2214
2264
  };
2215
2265
  const onBoxBlur = () => updateBoxBlurred(true);
@@ -2235,26 +2285,21 @@ var NetlessAppPresentation = (function (exports) {
2235
2285
  document.removeEventListener("error", onImgError, true);
2236
2286
  });
2237
2287
  };
2238
- var createLogger = (room) => {
2239
- if (room && room.logger) {
2240
- return (...args) => room.logger.info(...args);
2241
- } else {
2242
- return (...args) => console.log(...args);
2243
- }
2288
+ var getLogger = (context) => {
2289
+ var _a, _b, _c;
2290
+ return (_c = (_a = context.getWindowManager()) == null ? void 0 : _a.Logger) != null ? _c : (_b = context.getRoom()) == null ? void 0 : _b.logger;
2291
+ };
2292
+ var createLogger = (context) => {
2293
+ return (...args) => {
2294
+ var _a, _b;
2295
+ return (_b = (_a = getLogger(context)) == null ? void 0 : _a.info) == null ? void 0 : _b.call(_a, ...args);
2296
+ };
2244
2297
  };
2245
2298
  var createDiagnosticLogger = (context) => {
2246
- var _a;
2247
- const createAppLogger = context.createLogger;
2248
- if (typeof createAppLogger === "function") {
2249
- return createAppLogger.call(context, "camera", {
2250
- debounceTime: 300,
2251
- maxWaitTime: 2e3
2252
- });
2253
- }
2254
- const roomLogger = (_a = context.getRoom()) == null ? void 0 : _a.logger;
2255
2299
  const prefix = `[Presentation][${context.appId}][camera]`;
2256
2300
  const emit = (level, event, ...data) => {
2257
2301
  try {
2302
+ const roomLogger = getLogger(context);
2258
2303
  const printer = roomLogger == null ? void 0 : roomLogger[level];
2259
2304
  if (typeof printer === "function")
2260
2305
  printer.call(roomLogger, `${prefix}[${event}]`, ...data);
@@ -2319,9 +2364,11 @@ var NetlessAppPresentation = (function (exports) {
2319
2364
  );
2320
2365
  const options = context.getAppOptions() || {};
2321
2366
  const room = context.getRoom();
2322
- const log = options.log || createLogger(room);
2323
- const roomLogger = room == null ? void 0 : room.logger;
2324
- const warn = (...data) => (roomLogger == null ? void 0 : roomLogger.warn) ? roomLogger.warn(...data) : log(...data);
2367
+ const log = options.log || createLogger(context);
2368
+ const warn = (...data) => {
2369
+ var _a2, _b2;
2370
+ return (_b2 = (_a2 = getLogger(context)) == null ? void 0 : _a2.warn) == null ? void 0 : _b2.call(_a2, ...data);
2371
+ };
2325
2372
  const configuredOriginSize = context.storage.state.originSize;
2326
2373
  const originSize = isValidSize(configuredOriginSize) ? {
2327
2374
  width: configuredOriginSize.width,
@@ -2653,10 +2700,14 @@ var NetlessAppPresentation = (function (exports) {
2653
2700
  pageIndex$,
2654
2701
  view,
2655
2702
  options.thumbnail,
2656
- options.useClipView
2703
+ options.useClipView,
2704
+ (error) => {
2705
+ var _a2, _b2;
2706
+ return (_b2 = (_a2 = getLogger(context)) == null ? void 0 : _a2.error) == null ? void 0 : _b2.call(_a2, "[Presentation] thumbnail URL parsing failed", context.appId, error);
2707
+ }
2657
2708
  )
2658
2709
  );
2659
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.3";
2710
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13";
2660
2711
  app.scaleDocsToFit = scaleDocsToFit;
2661
2712
  app.log = log;
2662
2713
  app.warn = warn;
@@ -3128,11 +3179,15 @@ var NetlessAppPresentation = (function (exports) {
3128
3179
  const result = await waitForCurrentPageImage(
3129
3180
  () => findBackgroundImage(view),
3130
3181
  setupReadyTimeout,
3131
- () => disposed
3182
+ () => disposed,
3183
+ {
3184
+ waitForReady: () => Boolean(context.waitForActualSetupReady),
3185
+ onTimeout: () => warn(`[Presentation] setup page image still pending after ${setupReadyTimeout}ms`)
3186
+ }
3132
3187
  );
3133
- if (result === "timeout" && !disposed) {
3134
- warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
3135
- } else if (result === "error" && !disposed) {
3188
+ if (disposed)
3189
+ throw new Error("[Presentation] disposed before setup ready");
3190
+ if (result === "error") {
3136
3191
  warn("[Presentation] setup page image failed to load; continuing setup");
3137
3192
  }
3138
3193
  return controller;
@@ -3168,13 +3223,14 @@ var NetlessAppPresentation = (function (exports) {
3168
3223
  }
3169
3224
  }
3170
3225
  };
3171
- function createPresentation(box, pages, jumpPage, pageIndex$, view, thumbnail, useClipView) {
3226
+ function createPresentation(box, pages, jumpPage, pageIndex$, view, thumbnail, useClipView, onThumbnailError) {
3172
3227
  box.mountStyles(style_default);
3173
3228
  const app = new AppPresentation({
3174
3229
  pages,
3175
3230
  readonly: box.readonly,
3176
3231
  jumpPage,
3177
- thumbnail
3232
+ thumbnail,
3233
+ onThumbnailError
3178
3234
  });
3179
3235
  app.box = box;
3180
3236
  box.mountContent(app.contentDOM);
@@ -3222,11 +3278,12 @@ var NetlessAppPresentation = (function (exports) {
3222
3278
  };
3223
3279
 
3224
3280
  // src/index.ts
3225
- var version = "0.1.13-beta.3";
3281
+ var version = "0.1.13";
3226
3282
 
3227
3283
  exports.NetlessAppPresentation = NetlessAppPresentation;
3228
3284
  exports.Presentation = Presentation;
3229
3285
  exports.Scrollbar = Scrollbar;
3286
+ exports.createDiagnosticLogger = createDiagnosticLogger;
3230
3287
  exports.default = NetlessAppPresentation;
3231
3288
  exports.install = install;
3232
3289
  exports.setupBlurThumbnailDegradation = setupBlurThumbnailDegradation;
package/dist/index.js CHANGED
@@ -1305,6 +1305,7 @@ var Presentation = class {
1305
1305
  var _a;
1306
1306
  this.pages = config.pages;
1307
1307
  this.preload = new Preload(this.pages);
1308
+ this.onThumbnailError = config.onThumbnailError;
1308
1309
  if (config.thumbnail)
1309
1310
  this.thumbnail = config.thumbnail;
1310
1311
  this.dispose.add(this.preload);
@@ -1520,12 +1521,16 @@ var Presentation = class {
1520
1521
  }
1521
1522
  /** Returns a modified URL that points to the thumbnail image. */
1522
1523
  thumbnail(src) {
1524
+ var _a;
1523
1525
  try {
1524
1526
  const url = new URL(src);
1525
1527
  url.searchParams.set("x-oss-process", "image/resize,l_50");
1526
1528
  return url.toString();
1527
1529
  } catch (err) {
1528
- console.error(err);
1530
+ try {
1531
+ (_a = this.onThumbnailError) == null ? void 0 : _a.call(this, err);
1532
+ } catch (e) {
1533
+ }
1529
1534
  return src;
1530
1535
  }
1531
1536
  }
@@ -2017,9 +2022,10 @@ var createPresentationRuntimeTeardown = (box, dispose2) => {
2017
2022
  };
2018
2023
 
2019
2024
  // src/setup-ready.ts
2020
- var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed) => {
2025
+ var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed, options) => {
2021
2026
  return new Promise((resolve) => {
2022
2027
  let settled = false;
2028
+ let timedOut = false;
2023
2029
  let pollTimer;
2024
2030
  const settle = (result) => {
2025
2031
  if (settled)
@@ -2030,13 +2036,22 @@ var waitForCurrentPageImage = (getImage, timeoutMs, isDisposed) => {
2030
2036
  window.clearInterval(pollTimer);
2031
2037
  resolve(result);
2032
2038
  };
2033
- const timeoutTimer = window.setTimeout(() => settle("timeout"), timeoutMs);
2039
+ const timeoutTimer = window.setTimeout(() => {
2040
+ timedOut = true;
2041
+ options == null ? void 0 : options.onTimeout();
2042
+ if (!(options == null ? void 0 : options.waitForReady()))
2043
+ settle("timeout");
2044
+ }, timeoutMs);
2034
2045
  const check = () => {
2035
2046
  var _a, _b;
2036
2047
  if (isDisposed()) {
2037
2048
  settle("disposed");
2038
2049
  return;
2039
2050
  }
2051
+ if (timedOut && !(options == null ? void 0 : options.waitForReady())) {
2052
+ settle("timeout");
2053
+ return;
2054
+ }
2040
2055
  const img = getImage();
2041
2056
  if (img == null ? void 0 : img.complete) {
2042
2057
  if (img.naturalWidth > 0) {
@@ -2110,7 +2125,21 @@ var setupBlurThumbnailDegradation = (config) => {
2110
2125
  const box = context.getBox();
2111
2126
  const boxBlurClass = box.wrapClassName("blur");
2112
2127
  let blurEpisode = false;
2113
- let reDegradeEnabled = false;
2128
+ let failedThumbnailPageIndex = null;
2129
+ const canReDegrade = () => blurEpisode && failedThumbnailPageIndex !== pageIndex$.value;
2130
+ let previewSuspended = false;
2131
+ let appliedFocusState = true;
2132
+ let logicalFocused = true;
2133
+ const activityBlocked = () => {
2134
+ var _a, _b;
2135
+ const wm = context.getWindowManager();
2136
+ if ((wm == null ? void 0 : wm.lazySetupInMaximizedMode) !== true)
2137
+ return false;
2138
+ const host = (_a = context.getRuntimeActivity) == null ? void 0 : _a.call(context);
2139
+ if (host)
2140
+ return document.visibilityState === "hidden" || !host.active;
2141
+ return document.visibilityState === "hidden" || wm.boxState === "minimized" || ((_b = wm.attributes) == null ? void 0 : _b.minimized) === true || context.getBoxStatus() === "minimized";
2142
+ };
2114
2143
  let boxBlurred = box.$box.classList.contains(boxBlurClass);
2115
2144
  const setBackgroundSuspended = (suspended) => {
2116
2145
  app.whiteboardDOM.classList.toggle(
@@ -2125,7 +2154,7 @@ var setupBlurThumbnailDegradation = (config) => {
2125
2154
  const thumbnailURL = page.thumbnail;
2126
2155
  const img = findBackgroundImage(view);
2127
2156
  if (!thumbnailURL || !hasUsableThumbnail(page)) {
2128
- if (boxBlurred) {
2157
+ if (boxBlurred || activityBlocked()) {
2129
2158
  setBackgroundSuspended(true);
2130
2159
  if (img && resolveImageUrl(img.src) !== resolveImageUrl(TRANSPARENT_BACKGROUND)) {
2131
2160
  img.src = TRANSPARENT_BACKGROUND;
@@ -2147,11 +2176,14 @@ var setupBlurThumbnailDegradation = (config) => {
2147
2176
  setBackgroundSuspended(false);
2148
2177
  };
2149
2178
  const suspendCachedResources = () => {
2150
- if (!blurEpisode || !boxBlurred)
2179
+ if (!blurEpisode || !boxBlurred && !activityBlocked())
2151
2180
  return;
2152
- app.suspendPreviewResources();
2181
+ if (!previewSuspended) {
2182
+ previewSuspended = true;
2183
+ app.suspendPreviewResources();
2184
+ }
2153
2185
  const page = pages[pageIndex$.value];
2154
- if (!page || !hasUsableThumbnail(page) || reDegradeEnabled) {
2186
+ if (!page || !hasUsableThumbnail(page) || canReDegrade()) {
2155
2187
  degrade();
2156
2188
  }
2157
2189
  };
@@ -2159,7 +2191,8 @@ var setupBlurThumbnailDegradation = (config) => {
2159
2191
  if (!blurEpisode)
2160
2192
  return;
2161
2193
  blurEpisode = false;
2162
- reDegradeEnabled = false;
2194
+ failedThumbnailPageIndex = null;
2195
+ previewSuspended = false;
2163
2196
  restore2();
2164
2197
  app.preload.resume(pageIndex$.value);
2165
2198
  };
@@ -2177,40 +2210,57 @@ var setupBlurThumbnailDegradation = (config) => {
2177
2210
  return !!(el && el.contains(target));
2178
2211
  };
2179
2212
  const onImgLoad = (ev) => {
2180
- if (!blurEpisode || !reDegradeEnabled)
2213
+ if (!canReDegrade())
2181
2214
  return;
2182
2215
  if (!ownsTarget(ev.target))
2183
2216
  return;
2184
2217
  degrade();
2185
2218
  };
2186
2219
  const onImgError = (ev) => {
2187
- if (!blurEpisode || !reDegradeEnabled)
2220
+ var _a;
2221
+ if (!canReDegrade())
2188
2222
  return;
2189
2223
  if (!ownsTarget(ev.target))
2190
2224
  return;
2191
- reDegradeEnabled = false;
2225
+ const thumbnailURL = (_a = pages[pageIndex$.value]) == null ? void 0 : _a.thumbnail;
2226
+ if (thumbnailURL && resolveImageUrl(ev.target.src) === resolveImageUrl(thumbnailURL)) {
2227
+ failedThumbnailPageIndex = pageIndex$.value;
2228
+ }
2192
2229
  };
2193
- const offFocus = context.emitter.on("focus", (isFocused) => {
2230
+ const applyActivity = () => {
2231
+ var _a;
2232
+ const lazy = ((_a = context.getWindowManager()) == null ? void 0 : _a.lazySetupInMaximizedMode) === true;
2233
+ const isFocused = (!lazy || logicalFocused) && !activityBlocked();
2234
+ if (appliedFocusState === isFocused)
2235
+ return;
2236
+ if (!isFocused && !isBlurDegradationAllowed(context) && !activityBlocked())
2237
+ return;
2194
2238
  if (isFocused) {
2195
2239
  restoreFocusedResources();
2196
2240
  } else {
2197
- if (!isBlurDegradationAllowed(context))
2198
- return;
2199
2241
  blurEpisode = true;
2200
- reDegradeEnabled = true;
2242
+ failedThumbnailPageIndex = null;
2201
2243
  degrade();
2202
2244
  app.preload.pause();
2203
2245
  suspendCachedResources();
2204
2246
  }
2247
+ appliedFocusState = isFocused;
2248
+ };
2249
+ const offFocus = context.emitter.on("focus", (isFocused) => {
2250
+ logicalFocused = isFocused;
2251
+ applyActivity();
2205
2252
  });
2206
2253
  dispose2.add(offFocus);
2254
+ dispose2.add(context.emitter.on("runtimeActivity", applyActivity));
2255
+ document.addEventListener("visibilitychange", applyActivity);
2256
+ dispose2.add(() => document.removeEventListener("visibilitychange", applyActivity));
2207
2257
  const updateBoxBlurred = (blurred) => {
2208
2258
  if (boxBlurred === blurred)
2209
2259
  return;
2210
2260
  boxBlurred = blurred;
2211
2261
  if (blurred)
2212
2262
  suspendCachedResources();
2213
- else
2263
+ else if (logicalFocused && !activityBlocked())
2214
2264
  restoreFocusedResources();
2215
2265
  };
2216
2266
  const onBoxBlur = () => updateBoxBlurred(true);
@@ -2236,26 +2286,21 @@ var setupBlurThumbnailDegradation = (config) => {
2236
2286
  document.removeEventListener("error", onImgError, true);
2237
2287
  });
2238
2288
  };
2239
- var createLogger = (room) => {
2240
- if (room && room.logger) {
2241
- return (...args) => room.logger.info(...args);
2242
- } else {
2243
- return (...args) => console.log(...args);
2244
- }
2289
+ var getLogger = (context) => {
2290
+ var _a, _b, _c;
2291
+ return (_c = (_a = context.getWindowManager()) == null ? void 0 : _a.Logger) != null ? _c : (_b = context.getRoom()) == null ? void 0 : _b.logger;
2292
+ };
2293
+ var createLogger = (context) => {
2294
+ return (...args) => {
2295
+ var _a, _b;
2296
+ return (_b = (_a = getLogger(context)) == null ? void 0 : _a.info) == null ? void 0 : _b.call(_a, ...args);
2297
+ };
2245
2298
  };
2246
2299
  var createDiagnosticLogger = (context) => {
2247
- var _a;
2248
- const createAppLogger = context.createLogger;
2249
- if (typeof createAppLogger === "function") {
2250
- return createAppLogger.call(context, "camera", {
2251
- debounceTime: 300,
2252
- maxWaitTime: 2e3
2253
- });
2254
- }
2255
- const roomLogger = (_a = context.getRoom()) == null ? void 0 : _a.logger;
2256
2300
  const prefix = `[Presentation][${context.appId}][camera]`;
2257
2301
  const emit = (level, event, ...data) => {
2258
2302
  try {
2303
+ const roomLogger = getLogger(context);
2259
2304
  const printer = roomLogger == null ? void 0 : roomLogger[level];
2260
2305
  if (typeof printer === "function")
2261
2306
  printer.call(roomLogger, `${prefix}[${event}]`, ...data);
@@ -2320,9 +2365,11 @@ var NetlessAppPresentation = {
2320
2365
  );
2321
2366
  const options = context.getAppOptions() || {};
2322
2367
  const room = context.getRoom();
2323
- const log = options.log || createLogger(room);
2324
- const roomLogger = room == null ? void 0 : room.logger;
2325
- const warn = (...data) => (roomLogger == null ? void 0 : roomLogger.warn) ? roomLogger.warn(...data) : log(...data);
2368
+ const log = options.log || createLogger(context);
2369
+ const warn = (...data) => {
2370
+ var _a2, _b2;
2371
+ return (_b2 = (_a2 = getLogger(context)) == null ? void 0 : _a2.warn) == null ? void 0 : _b2.call(_a2, ...data);
2372
+ };
2326
2373
  const configuredOriginSize = context.storage.state.originSize;
2327
2374
  const originSize = isValidSize(configuredOriginSize) ? {
2328
2375
  width: configuredOriginSize.width,
@@ -2654,10 +2701,14 @@ var NetlessAppPresentation = {
2654
2701
  pageIndex$,
2655
2702
  view,
2656
2703
  options.thumbnail,
2657
- options.useClipView
2704
+ options.useClipView,
2705
+ (error) => {
2706
+ var _a2, _b2;
2707
+ return (_b2 = (_a2 = getLogger(context)) == null ? void 0 : _a2.error) == null ? void 0 : _b2.call(_a2, "[Presentation] thumbnail URL parsing failed", context.appId, error);
2708
+ }
2658
2709
  )
2659
2710
  );
2660
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.3";
2711
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13";
2661
2712
  app.scaleDocsToFit = scaleDocsToFit;
2662
2713
  app.log = log;
2663
2714
  app.warn = warn;
@@ -3129,11 +3180,15 @@ var NetlessAppPresentation = {
3129
3180
  const result = await waitForCurrentPageImage(
3130
3181
  () => findBackgroundImage(view),
3131
3182
  setupReadyTimeout,
3132
- () => disposed
3183
+ () => disposed,
3184
+ {
3185
+ waitForReady: () => Boolean(context.waitForActualSetupReady),
3186
+ onTimeout: () => warn(`[Presentation] setup page image still pending after ${setupReadyTimeout}ms`)
3187
+ }
3133
3188
  );
3134
- if (result === "timeout" && !disposed) {
3135
- warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
3136
- } else if (result === "error" && !disposed) {
3189
+ if (disposed)
3190
+ throw new Error("[Presentation] disposed before setup ready");
3191
+ if (result === "error") {
3137
3192
  warn("[Presentation] setup page image failed to load; continuing setup");
3138
3193
  }
3139
3194
  return controller;
@@ -3169,13 +3224,14 @@ var AppPresentation = class extends Presentation {
3169
3224
  }
3170
3225
  }
3171
3226
  };
3172
- function createPresentation(box, pages, jumpPage, pageIndex$, view, thumbnail, useClipView) {
3227
+ function createPresentation(box, pages, jumpPage, pageIndex$, view, thumbnail, useClipView, onThumbnailError) {
3173
3228
  box.mountStyles(style_default);
3174
3229
  const app = new AppPresentation({
3175
3230
  pages,
3176
3231
  readonly: box.readonly,
3177
3232
  jumpPage,
3178
- thumbnail
3233
+ thumbnail,
3234
+ onThumbnailError
3179
3235
  });
3180
3236
  app.box = box;
3181
3237
  box.mountContent(app.contentDOM);
@@ -3223,11 +3279,12 @@ var install = (register, options = {}) => {
3223
3279
  };
3224
3280
 
3225
3281
  // src/index.ts
3226
- var version = "0.1.13-beta.3";
3282
+ var version = "0.1.13";
3227
3283
 
3228
3284
  exports.NetlessAppPresentation = NetlessAppPresentation;
3229
3285
  exports.Presentation = Presentation;
3230
3286
  exports.Scrollbar = Scrollbar;
3287
+ exports.createDiagnosticLogger = createDiagnosticLogger;
3231
3288
  exports.default = NetlessAppPresentation;
3232
3289
  exports.install = install;
3233
3290
  exports.setupBlurThumbnailDegradation = setupBlurThumbnailDegradation;