@netless/app-presentation 0.1.13-beta.4 → 0.1.14
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 +11 -0
- package/README.md +13 -0
- package/build.ts +19 -9
- package/dist/index.d.ts +13 -2
- package/dist/index.global.js +46 -35
- package/dist/index.js +46 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/app-presentation.ts +37 -42
- package/src/camera-reference.ts +1 -1
- package/src/presentation.ts +8 -1
package/src/app-presentation.ts
CHANGED
|
@@ -7,10 +7,10 @@ import type {
|
|
|
7
7
|
ReadonlyTeleBox,
|
|
8
8
|
Room,
|
|
9
9
|
SceneDefinition,
|
|
10
|
-
Size,
|
|
11
10
|
View,
|
|
12
11
|
WindowManager,
|
|
13
12
|
} from "@netless/window-manager";
|
|
13
|
+
import type { Size } from "white-web-sdk";
|
|
14
14
|
|
|
15
15
|
import { disposableStore } from "@wopjs/disposable";
|
|
16
16
|
import { listen } from "@wopjs/dom";
|
|
@@ -263,12 +263,11 @@ export const setupBlurThumbnailDegradation = (config: {
|
|
|
263
263
|
const { context, view, pages, pageIndex$, app, dispose } = config;
|
|
264
264
|
const box = context.getBox();
|
|
265
265
|
const boxBlurClass = box.wrapClassName("blur");
|
|
266
|
-
//
|
|
267
|
-
// without a thumbnail don't end it, so a later page degrades again), while
|
|
268
|
-
// `reDegradeEnabled` only turns off when the degraded thumbnail itself
|
|
269
|
-
// fails, so we don't fight the SDK's background onError retry.
|
|
266
|
+
// A failed thumbnail is pinned only for its page during this blur episode.
|
|
270
267
|
let blurEpisode = false;
|
|
271
|
-
let
|
|
268
|
+
let failedThumbnailPageIndex: number | null = null;
|
|
269
|
+
const canReDegrade = (): boolean =>
|
|
270
|
+
blurEpisode && failedThumbnailPageIndex !== pageIndex$.value;
|
|
272
271
|
// Duplicate focus(false) deliveries are unavoidable (multiple sources in
|
|
273
272
|
// the host). Preview-resource clearing is the only non-idempotent DOM op
|
|
274
273
|
// in the blur path, so gate it on this flag until focus restores it.
|
|
@@ -340,15 +339,18 @@ export const setupBlurThumbnailDegradation = (config: {
|
|
|
340
339
|
app.suspendPreviewResources();
|
|
341
340
|
}
|
|
342
341
|
const page = pages[pageIndex$.value];
|
|
343
|
-
if (!page || !hasUsableThumbnail(page) ||
|
|
342
|
+
if (!page || !hasUsableThumbnail(page) || canReDegrade()) {
|
|
344
343
|
degrade();
|
|
345
344
|
}
|
|
346
345
|
};
|
|
347
346
|
|
|
348
347
|
const restoreFocusedResources = (): void => {
|
|
348
|
+
// TeleBox can restore before the host delivers its activity/focus event.
|
|
349
|
+
// Keep the applied state aligned so the next blur starts a new episode.
|
|
350
|
+
appliedFocusState = true;
|
|
349
351
|
if (!blurEpisode) return;
|
|
350
352
|
blurEpisode = false;
|
|
351
|
-
|
|
353
|
+
failedThumbnailPageIndex = null;
|
|
352
354
|
previewSuspended = false;
|
|
353
355
|
restore();
|
|
354
356
|
app.preload.resume(pageIndex$.value);
|
|
@@ -374,18 +376,23 @@ export const setupBlurThumbnailDegradation = (config: {
|
|
|
374
376
|
};
|
|
375
377
|
|
|
376
378
|
const onImgLoad = (ev: Event): void => {
|
|
377
|
-
if (!
|
|
379
|
+
if (!canReDegrade()) return;
|
|
378
380
|
if (!ownsTarget(ev.target)) return;
|
|
379
381
|
degrade();
|
|
380
382
|
};
|
|
381
383
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
// still force-restores afterwards.
|
|
384
|
+
// Stop re-degrading this page when its thumbnail fails, so the SDK's
|
|
385
|
+
// onError retry can settle on the full image. Other pages remain eligible.
|
|
385
386
|
const onImgError = (ev: Event): void => {
|
|
386
|
-
if (!
|
|
387
|
+
if (!canReDegrade()) return;
|
|
387
388
|
if (!ownsTarget(ev.target)) return;
|
|
388
|
-
|
|
389
|
+
const thumbnailURL = pages[pageIndex$.value]?.thumbnail;
|
|
390
|
+
if (
|
|
391
|
+
thumbnailURL &&
|
|
392
|
+
resolveImageUrl(ev.target.src) === resolveImageUrl(thumbnailURL)
|
|
393
|
+
) {
|
|
394
|
+
failedThumbnailPageIndex = pageIndex$.value;
|
|
395
|
+
}
|
|
389
396
|
};
|
|
390
397
|
|
|
391
398
|
const applyActivity = (): void => {
|
|
@@ -397,7 +404,7 @@ export const setupBlurThumbnailDegradation = (config: {
|
|
|
397
404
|
restoreFocusedResources();
|
|
398
405
|
} else {
|
|
399
406
|
blurEpisode = true;
|
|
400
|
-
|
|
407
|
+
failedThumbnailPageIndex = null;
|
|
401
408
|
degrade();
|
|
402
409
|
app.preload.pause();
|
|
403
410
|
suspendCachedResources();
|
|
@@ -447,27 +454,16 @@ export const setupBlurThumbnailDegradation = (config: {
|
|
|
447
454
|
});
|
|
448
455
|
};
|
|
449
456
|
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
457
|
+
const getLogger = (context: AppContext) =>
|
|
458
|
+
(context.getWindowManager() as any)?.Logger ?? (context.getRoom() as any)?.logger;
|
|
459
|
+
|
|
460
|
+
const createLogger = (context: AppContext): Logger => {
|
|
461
|
+
return (...args) => getLogger(context)?.info?.(...args);
|
|
456
462
|
};
|
|
457
463
|
|
|
458
|
-
const createDiagnosticLogger = (
|
|
464
|
+
export const createDiagnosticLogger = (
|
|
459
465
|
context: AppContext,
|
|
460
466
|
): PresentationDiagnosticLogger => {
|
|
461
|
-
const createAppLogger = (context as any).createLogger;
|
|
462
|
-
if (typeof createAppLogger === "function") {
|
|
463
|
-
return createAppLogger.call(context, "camera", {
|
|
464
|
-
debounceTime: 300,
|
|
465
|
-
maxWaitTime: 2000,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Compatibility fallback for WindowManager versions without AppContext.createLogger().
|
|
470
|
-
const roomLogger = (context.getRoom() as any)?.logger;
|
|
471
467
|
const prefix = `[Presentation][${context.appId}][camera]`;
|
|
472
468
|
const emit = (
|
|
473
469
|
level: "info" | "warn" | "error",
|
|
@@ -475,6 +471,7 @@ const createDiagnosticLogger = (
|
|
|
475
471
|
...data: unknown[]
|
|
476
472
|
) => {
|
|
477
473
|
try {
|
|
474
|
+
const roomLogger = getLogger(context);
|
|
478
475
|
const printer = roomLogger?.[level];
|
|
479
476
|
if (typeof printer === "function")
|
|
480
477
|
printer.call(roomLogger, `${prefix}[${event}]`, ...data);
|
|
@@ -554,10 +551,8 @@ export const NetlessAppPresentation: NetlessApp<
|
|
|
554
551
|
|
|
555
552
|
const options = context.getAppOptions() || {};
|
|
556
553
|
const room = context.getRoom();
|
|
557
|
-
const log = options.log || createLogger(
|
|
558
|
-
const
|
|
559
|
-
const warn: Logger = (...data) =>
|
|
560
|
-
roomLogger?.warn ? roomLogger.warn(...data) : log(...data);
|
|
554
|
+
const log = options.log || createLogger(context);
|
|
555
|
+
const warn: Logger = (...data) => getLogger(context)?.warn?.(...data);
|
|
561
556
|
const configuredOriginSize = context.storage.state.originSize;
|
|
562
557
|
const originSize = isValidSize(configuredOriginSize)
|
|
563
558
|
? {
|
|
@@ -927,6 +922,7 @@ export const NetlessAppPresentation: NetlessApp<
|
|
|
927
922
|
view,
|
|
928
923
|
options.thumbnail,
|
|
929
924
|
options.useClipView,
|
|
925
|
+
error => getLogger(context)?.error?.("[Presentation] thumbnail URL parsing failed", context.appId, error),
|
|
930
926
|
),
|
|
931
927
|
);
|
|
932
928
|
app.contentDOM.dataset.appPresentationVersion = __VERSION__;
|
|
@@ -1522,8 +1518,8 @@ export const NetlessAppPresentation: NetlessApp<
|
|
|
1522
1518
|
// `SetupResult | Promise<SetupResult>` declaration is the minimum version.
|
|
1523
1519
|
const setupReadyTimeout = options.setupReadyTimeout ?? DEFAULT_SETUP_READY_TIMEOUT;
|
|
1524
1520
|
return prepareScenesPromise.then(async () => {
|
|
1525
|
-
//
|
|
1526
|
-
//
|
|
1521
|
+
// Wait for the current page image or its first failure. The SDK owns
|
|
1522
|
+
// background-image retries, so a failed request must not fail setup.
|
|
1527
1523
|
const result = await waitForCurrentPageImage(
|
|
1528
1524
|
() => findBackgroundImage(view), setupReadyTimeout, () => disposed,
|
|
1529
1525
|
{
|
|
@@ -1532,10 +1528,7 @@ export const NetlessAppPresentation: NetlessApp<
|
|
|
1532
1528
|
},
|
|
1533
1529
|
);
|
|
1534
1530
|
if (disposed) throw new Error("[Presentation] disposed before setup ready");
|
|
1535
|
-
if (result === "error"
|
|
1536
|
-
throw new Error("[Presentation] setup page image failed to load");
|
|
1537
|
-
}
|
|
1538
|
-
if (result === "error" && !disposed) {
|
|
1531
|
+
if (result === "error") {
|
|
1539
1532
|
warn("[Presentation] setup page image failed to load; continuing setup");
|
|
1540
1533
|
}
|
|
1541
1534
|
return controller;
|
|
@@ -1597,6 +1590,7 @@ function createPresentation(
|
|
|
1597
1590
|
view: View,
|
|
1598
1591
|
thumbnail?: (src: string) => string,
|
|
1599
1592
|
useClipView?: boolean,
|
|
1593
|
+
onThumbnailError?: (error: unknown) => void,
|
|
1600
1594
|
): AppPresentation {
|
|
1601
1595
|
box.mountStyles(styles);
|
|
1602
1596
|
|
|
@@ -1605,6 +1599,7 @@ function createPresentation(
|
|
|
1605
1599
|
readonly: box.readonly,
|
|
1606
1600
|
jumpPage,
|
|
1607
1601
|
thumbnail,
|
|
1602
|
+
onThumbnailError,
|
|
1608
1603
|
});
|
|
1609
1604
|
app.box = box;
|
|
1610
1605
|
box.mountContent(app.contentDOM);
|
package/src/camera-reference.ts
CHANGED
package/src/presentation.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface PresentationConfig {
|
|
|
18
18
|
readonly pages: PresentationPage[]
|
|
19
19
|
readonly readonly?: boolean
|
|
20
20
|
readonly thumbnail?: (src: string) => string
|
|
21
|
+
readonly onThumbnailError?: (error: unknown) => void
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -59,6 +60,7 @@ export class Presentation implements IDisposable<void> {
|
|
|
59
60
|
readonly dispose = disposableStore()
|
|
60
61
|
readonly pages: PresentationPage[]
|
|
61
62
|
readonly preload: Preload
|
|
63
|
+
readonly onThumbnailError?: (error: unknown) => void
|
|
62
64
|
|
|
63
65
|
dom: Element | DocumentFragment
|
|
64
66
|
contentDOM: HTMLDivElement
|
|
@@ -81,6 +83,7 @@ export class Presentation implements IDisposable<void> {
|
|
|
81
83
|
constructor(config: PresentationConfig) {
|
|
82
84
|
this.pages = config.pages
|
|
83
85
|
this.preload = new Preload(this.pages)
|
|
86
|
+
this.onThumbnailError = config.onThumbnailError
|
|
84
87
|
if (config.thumbnail) this.thumbnail = config.thumbnail
|
|
85
88
|
this.dispose.add(this.preload)
|
|
86
89
|
this.readonly = config.readonly ?? false
|
|
@@ -329,7 +332,11 @@ export class Presentation implements IDisposable<void> {
|
|
|
329
332
|
url.searchParams.set('x-oss-process', 'image/resize,l_50')
|
|
330
333
|
return url.toString()
|
|
331
334
|
} catch (err) {
|
|
332
|
-
|
|
335
|
+
try {
|
|
336
|
+
this.onThumbnailError?.(err)
|
|
337
|
+
} catch {
|
|
338
|
+
// A diagnostic failure must not change the thumbnail fallback.
|
|
339
|
+
}
|
|
333
340
|
return src
|
|
334
341
|
}
|
|
335
342
|
}
|