@netless/app-slide 0.2.106-beta.1 → 0.2.106-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-slide",
3
- "version": "0.2.106-beta.1",
3
+ "version": "0.2.106-beta.2",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "build": "vite build && npm run types",
10
10
  "build:dev": "vite build --mode development && npm run types",
11
11
  "cleanup": "rimraf ./dist",
12
- "test": "rimraf test/.cache && esbuild test/ResizableContainer.test.ts --bundle --platform=node --format=cjs --outfile=test/.cache/ResizableContainer.test.cjs && node test/.cache/ResizableContainer.test.cjs"
12
+ "test": "rimraf test/.cache && esbuild test/ResizableContainer.test.ts test/NavigationButtonMode.test.ts test/DocsViewerNavigation.integration.test.ts --bundle --platform=node --format=cjs --outdir=test/.cache && node test/.cache/ResizableContainer.test.js && node test/.cache/NavigationButtonMode.test.js && node test/.cache/DocsViewerNavigation.integration.test.js"
13
13
  },
14
14
  "files": [
15
15
  "src",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "devDependencies": {
20
20
  "@juggle/resize-observer": "^3.3.1",
21
- "@netless/slide": "1.4.61-beta.0",
21
+ "@netless/slide": "1.4.61",
22
22
  "@types/color-string": "^1.5.2",
23
23
  "color-string": "^1.9.1",
24
24
  "jspdf": "2.5.1",
@@ -12,10 +12,11 @@ import { SideEffectManager } from "side-effect-manager";
12
12
  import { createDocsViewerPages } from "../SlideController";
13
13
  import { DocsViewer, type DocsViewerPage } from "../DocsViewer";
14
14
  import { ResizableContainer } from "../DocsViewer/ResizableContainer";
15
+ import { navigateWithNavigationButton, resolveNavigationButtonMode } from "./navigation";
15
16
  import { logger } from "../utils/logger";
16
17
  import { isEditable } from "../utils/helpers";
17
18
  import type { Attributes, MagixEvents } from "../typings";
18
- import type { AppOptions } from "..";
19
+ import type { AppOptions, NavigationButtonMode } from "..";
19
20
  import type { SyncEventQueuePolicy } from "@netless/slide";
20
21
  import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
21
22
 
@@ -55,6 +56,7 @@ export interface SlideDocsViewerConfig {
55
56
  appId: string;
56
57
  urlInterrupter?: (url: string) => Promise<string>;
57
58
  enableScale?: boolean;
59
+ navigationButtonMode?: NavigationButtonMode;
58
60
  onPagesReady?: (pages: DocsViewerPage[]) => void;
59
61
  onNavigate?: (index: number, origin?: string) => void;
60
62
  }
@@ -79,6 +81,7 @@ export class SlideDocsViewer {
79
81
  protected isViewMounted = false;
80
82
  protected justSildeReadonly = false;
81
83
  protected syncEventQueuePolicy: SyncEventQueuePolicy = "fifo";
84
+ private readonly navigationButtonMode: NavigationButtonMode;
82
85
  private enableScale: boolean;
83
86
  private boxSizeEventCount = 0;
84
87
  private latestBoxSize: BoxSize | undefined;
@@ -98,6 +101,7 @@ export class SlideDocsViewer {
98
101
  appId,
99
102
  urlInterrupter,
100
103
  enableScale,
104
+ navigationButtonMode,
101
105
  onPagesReady,
102
106
  onNavigate,
103
107
  }: SlideDocsViewerConfig) {
@@ -109,6 +113,7 @@ export class SlideDocsViewer {
109
113
  this.onNavigate = onNavigate || noop;
110
114
  this.baseScenePath = baseScenePath;
111
115
  this.enableScale = enableScale ?? false;
116
+ this.navigationButtonMode = resolveNavigationButtonMode(navigationButtonMode);
112
117
  this.appId = appId;
113
118
  this.viewer = new DocsViewer({
114
119
  readonly: box.readonly,
@@ -298,6 +303,7 @@ export class SlideDocsViewer {
298
303
  }
299
304
 
300
305
  public mount() {
306
+ this.unmountPromise = undefined;
301
307
  this.box.mountContent(this.viewer.$content);
302
308
  this.box.mountFooter(this.viewer.$footer);
303
309
 
@@ -465,29 +471,42 @@ export class SlideDocsViewer {
465
471
  return (page > 0 ? page : 1) - 1;
466
472
  }
467
473
 
468
- public async unmount(): Promise<this> {
469
- this.offBoxSizeChange?.();
470
- this.offBoxSizeChange = undefined;
471
- this.contentResizeObserver?.disconnect();
472
- this.contentResizeObserver = undefined;
473
- if (this.slideController) {
474
- const controller = this.slideController;
475
- this.slideController = null;
476
- await controller.destroy();
474
+ protected unmountPromise?: Promise<this>;
475
+ protected destroyPromise?: Promise<void>;
476
+
477
+ public unmount(): Promise<this> {
478
+ if (!this.unmountPromise) {
479
+ this.unmountPromise = Promise.resolve().then(async () => {
480
+ this.offBoxSizeChange?.();
481
+ this.offBoxSizeChange = undefined;
482
+ this.contentResizeObserver?.disconnect();
483
+ this.contentResizeObserver = undefined;
484
+ if (this.slideController) {
485
+ const controller = this.slideController;
486
+ this.slideController = null;
487
+ await controller.destroy();
488
+ }
489
+ this.viewer.unmount();
490
+ this.resizableContainer.destroy(this.box);
491
+ return this;
492
+ });
477
493
  }
478
- this.viewer.unmount();
479
- this.resizableContainer.destroy(this.box);
480
- return this;
494
+ return this.unmountPromise;
481
495
  }
482
496
 
483
497
  public setReadonly(readonly: boolean) {
484
498
  this.viewer.setReadonly(readonly);
485
499
  }
486
500
 
487
- public async destroy(): Promise<void> {
488
- this.sideEffect.flushAll();
489
- await this.unmount();
490
- this.viewer.destroy();
501
+ public destroy(): Promise<void> {
502
+ if (!this.destroyPromise) {
503
+ this.destroyPromise = Promise.resolve().then(async () => {
504
+ this.sideEffect.flushAll();
505
+ await this.unmount();
506
+ this.viewer.destroy();
507
+ });
508
+ }
509
+ return this.destroyPromise;
491
510
  }
492
511
 
493
512
  public toggleClickThrough(tool?: string) {
@@ -541,7 +560,14 @@ export class SlideDocsViewer {
541
560
 
542
561
  protected onNewPageIndex = (index: number, origin?: string) => {
543
562
  if (this.slideController) {
544
- this.slideController.jumpToPage(index + 1, origin);
563
+ navigateWithNavigationButton(
564
+ this.navigationButtonMode,
565
+ origin,
566
+ this.viewer.pageIndex,
567
+ index,
568
+ this.slideController,
569
+ this.onNavigate
570
+ );
545
571
  }
546
572
  };
547
573
 
@@ -0,0 +1,33 @@
1
+ import type { NavigationButtonMode } from "..";
2
+
3
+ export type NavigationButtonAction = "prevStep" | "nextStep";
4
+
5
+ export function resolveNavigationButtonMode(
6
+ mode: NavigationButtonMode | undefined
7
+ ): NavigationButtonMode {
8
+ return mode ?? "page";
9
+ }
10
+
11
+ interface NavigationController {
12
+ readonly page: number;
13
+ readonly slide: Record<NavigationButtonAction, () => void>;
14
+ jumpToPage(page: number, origin?: string): void;
15
+ }
16
+
17
+ export function navigateWithNavigationButton(
18
+ mode: NavigationButtonMode,
19
+ origin: string | undefined,
20
+ currentPageIndex: number,
21
+ targetPageIndex: number,
22
+ controller: NavigationController,
23
+ onNavigate: (page: number, origin?: string) => void
24
+ ): void {
25
+ if (mode === "step" && origin === "navigation") {
26
+ const action: NavigationButtonAction =
27
+ targetPageIndex < currentPageIndex ? "prevStep" : "nextStep";
28
+ controller.slide[action]();
29
+ onNavigate(controller.page, origin);
30
+ return;
31
+ }
32
+ controller.jumpToPage(targetPageIndex + 1, origin);
33
+ }
package/src/index.ts CHANGED
@@ -43,6 +43,8 @@ export { DefaultUrl, apps, FreezerLength, addHooks, useFreezer, log, logger };
43
43
 
44
44
  export { setFreezerLength, getFreezerLength, onCreated, onDestroyed } from "./utils/freezer";
45
45
 
46
+ export type NavigationButtonMode = "page" | "step";
47
+
46
48
  export interface AppOptions
47
49
  extends Pick<
48
50
  ISlideConfig,
@@ -86,6 +88,8 @@ export interface AppOptions
86
88
  invisibleBehavior?: "frozen" | "pause";
87
89
  /** just readonly, no operate silder */
88
90
  justSildeReadonly?: true;
91
+ /** footer navigation button behavior @default 'page' */
92
+ navigationButtonMode?: NavigationButtonMode;
89
93
  enableScale?: boolean;
90
94
  resourceMaxRetries?: number;
91
95
  onResourceMaxRetries: (url: string, error: Error) => void;
@@ -235,6 +239,7 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
235
239
  appId: context.appId,
236
240
  urlInterrupter: context.getAppOptions()?.urlInterrupter,
237
241
  enableScale: context.getAppOptions()?.enableScale ?? false,
242
+ navigationButtonMode: context.getAppOptions()?.navigationButtonMode,
238
243
  onPagesReady: ({ length }) => {
239
244
  const index = docsViewer?.viewer.pageIndex || 0;
240
245
  context.dispatchAppEvent("pageStateChange", { index, length });
@@ -313,20 +318,25 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
313
318
 
314
319
  let disposed = false;
315
320
  let offDestroy: (() => void) | undefined;
316
- const teardown = async (): Promise<void> => {
317
- if (disposed) return;
321
+ let teardownPromise: Promise<void> | undefined;
322
+ const teardown = (): Promise<void> => {
323
+ if (teardownPromise) return teardownPromise;
318
324
  disposed = true;
319
- const removeDestroy = offDestroy;
320
- offDestroy = undefined;
321
- removeDestroy?.();
322
- log("[Slide] destroy", context.appId);
323
- if (useFreezer) apps.delete(context.appId);
324
- sideEffect.flushAll();
325
- if (docsViewer) {
326
- const viewer = docsViewer;
327
- docsViewer = null;
328
- await viewer.destroy();
329
- }
325
+ // Store the promise before cleanup can synchronously trigger another teardown.
326
+ teardownPromise = Promise.resolve().then(async () => {
327
+ const removeDestroy = offDestroy;
328
+ offDestroy = undefined;
329
+ removeDestroy?.();
330
+ log("[Slide] destroy", context.appId);
331
+ if (useFreezer) apps.delete(context.appId);
332
+ sideEffect.flushAll();
333
+ if (docsViewer) {
334
+ const viewer = docsViewer;
335
+ docsViewer = null;
336
+ await viewer.destroy();
337
+ }
338
+ });
339
+ return teardownPromise;
330
340
  };
331
341
  offDestroy = context.emitter.on("destroy", teardown);
332
342