@netless/app-slide 0.2.102 → 0.2.104

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.102",
3
+ "version": "0.2.104",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  "types": "node scripts/build-types.mjs",
9
9
  "build": "vite build && npm run types",
10
10
  "build:dev": "vite build --mode development && npm run types",
11
- "cleanup": "rimraf ./dist"
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
13
  },
13
14
  "files": [
14
15
  "src",
@@ -16,7 +17,7 @@
16
17
  "README-zh.md"
17
18
  ],
18
19
  "devDependencies": {
19
- "@netless/slide": "1.4.56",
20
+ "@netless/slide": "1.4.57",
20
21
  "@types/color-string": "^1.5.2",
21
22
  "color-string": "^1.9.1",
22
23
  "jspdf": "2.5.1",
@@ -1,4 +1,4 @@
1
- import { Slide } from "@netless/slide";
1
+ import type { Slide } from "@netless/slide";
2
2
  import { ScrollBar } from "./ScrollBar";
3
3
  import type { AppContext, ReadonlyTeleBox } from "@netless/window-manager";
4
4
  import type { Attributes, MagixEvents } from "../typings";
@@ -8,8 +8,10 @@ export class ResizableContainer {
8
8
  private root: HTMLDivElement;
9
9
  public container: HTMLDivElement;
10
10
  private scrollContainer: HTMLDivElement;
11
+ /** @deprecated Use addScaleChangedListener() for multiple observers. */
11
12
  public onScaleChanged: ((scale: number) => void) | null = null;
12
13
  public onLayoutUpdated: (() => void) | null = null;
14
+ private scaleChangedListeners = new Set<(scale: number) => void>();
13
15
 
14
16
  private parent: HTMLElement;
15
17
  private whiteboardContainer: HTMLDivElement | null = null;
@@ -18,6 +20,7 @@ export class ResizableContainer {
18
20
  private slideWidth = 1;
19
21
  private slideHeight = 1;
20
22
  private scrollBar: ScrollBar | null = null;
23
+ private layoutFrameId: number | undefined;
21
24
  private context: AppContext<Attributes, MagixEvents, AppOptions>;
22
25
 
23
26
  // 每次 scale 后, 重置为居中
@@ -30,7 +33,7 @@ export class ResizableContainer {
30
33
  parent: HTMLElement,
31
34
  context: AppContext<Attributes, MagixEvents, AppOptions>,
32
35
  enableResize: boolean,
33
- _box?: ReadonlyTeleBox,
36
+ _box?: ReadonlyTeleBox
34
37
  ) {
35
38
  this.enableResize = enableResize;
36
39
  this.parent = parent;
@@ -56,7 +59,7 @@ export class ResizableContainer {
56
59
  // 初始化滚动条
57
60
  this.scrollBar = new ScrollBar(this.root, this);
58
61
  }
59
- };
62
+ }
60
63
 
61
64
  public getTranslate(): { x: number; y: number } {
62
65
  return { x: this.translateX, y: this.translateY };
@@ -66,6 +69,13 @@ export class ResizableContainer {
66
69
  return this.scale;
67
70
  }
68
71
 
72
+ public addScaleChangedListener(listener: (scale: number) => void): () => void {
73
+ this.scaleChangedListeners.add(listener);
74
+ return () => {
75
+ this.scaleChangedListeners.delete(listener);
76
+ };
77
+ }
78
+
69
79
  private renderScrollBar(
70
80
  width: number,
71
81
  overflowWidth: number,
@@ -97,6 +107,7 @@ export class ResizableContainer {
97
107
  };
98
108
 
99
109
  public updateResizableContainer() {
110
+ this.cancelScheduledLayoutUpdate();
100
111
  const parentBounds = this.scrollContainer.getBoundingClientRect();
101
112
  this.container.style.width = `${parentBounds.width * this.scale}px`;
102
113
  this.container.style.height = `${parentBounds.height * this.scale}px`;
@@ -183,13 +194,26 @@ export class ResizableContainer {
183
194
  this.scrollContainer.style.height = "100%";
184
195
  }
185
196
  applyScale = this.enableResize ? applyScale : 1;
186
- if (this.onScaleChanged && this.enableResize) {
187
- this.onScaleChanged(applyScale);
188
- }
189
197
  this.scale = applyScale;
190
- setTimeout(() => {
191
- this.updateResizableContainer();
192
- });
198
+ if (this.enableResize) {
199
+ this.onScaleChanged?.(this.scale);
200
+ for (const listener of this.scaleChangedListeners) {
201
+ listener(this.scale);
202
+ }
203
+ }
204
+ if (this.layoutFrameId === undefined) {
205
+ this.layoutFrameId = requestAnimationFrame(() => {
206
+ this.layoutFrameId = undefined;
207
+ this.updateResizableContainer();
208
+ });
209
+ }
210
+ }
211
+
212
+ private cancelScheduledLayoutUpdate(): void {
213
+ if (this.layoutFrameId !== undefined) {
214
+ cancelAnimationFrame(this.layoutFrameId);
215
+ this.layoutFrameId = undefined;
216
+ }
193
217
  }
194
218
 
195
219
  public addSlideContainer(slideContainer: HTMLDivElement) {
@@ -206,7 +230,9 @@ export class ResizableContainer {
206
230
  }
207
231
 
208
232
  public destroy(_box?: ReadonlyTeleBox): void {
233
+ this.cancelScheduledLayoutUpdate();
209
234
  this.onLayoutUpdated = null;
235
+ this.scaleChangedListeners.clear();
210
236
  this.scrollBar?.destroy();
211
237
  }
212
238
  }
@@ -33,6 +33,7 @@ export const EmptyAttributes: Attributes = {
33
33
  slideScale: 1,
34
34
  translateX: 0.5,
35
35
  translateY: 0.5,
36
+ originSize: null,
36
37
  };
37
38
 
38
39
  export interface SlideControllerOptions {
@@ -486,24 +486,32 @@ export class SlideDocsViewer {
486
486
 
487
487
  protected scaleDocsToFit = () => {
488
488
  if (this.slideController) {
489
- const { width, height } = this.slideController.slide;
490
- if (width && height) {
491
- this.whiteboardView.moveCameraToContain({
492
- originX: -width / 2,
493
- originY: -height / 2,
494
- width,
495
- height,
496
- animationMode: "immediately" as AnimationMode.Immediately,
497
- });
498
- this.whiteboardView.setCameraBound({
499
- damping: 1,
500
- maxContentMode: () => this.whiteboardView.camera.scale,
501
- minContentMode: () => this.whiteboardView.camera.scale,
502
- centerX: 0,
503
- centerY: 0,
504
- width,
505
- height,
506
- });
489
+ const { width: slideWidth, height: slideHeight } = this.slideController.slide;
490
+ if (slideWidth && slideHeight) {
491
+ const originSize = this.context.storage.state.originSize;
492
+ const { width, height } = originSize && originSize.width > 0 && originSize.height > 0
493
+ ? originSize
494
+ : { width: slideWidth, height: slideHeight };
495
+ if (width > 0 && height > 0) {
496
+ this.whiteboardView.moveCameraToContain({
497
+ originX: -width / 2,
498
+ originY: -height / 2,
499
+ width,
500
+ height,
501
+ animationMode: "immediately" as AnimationMode.Immediately,
502
+ });
503
+
504
+ const scale = this.whiteboardView.camera.scale;
505
+ this.whiteboardView.setCameraBound({
506
+ damping: 1,
507
+ maxContentMode: () => scale,
508
+ minContentMode: () => scale,
509
+ centerX: 0,
510
+ centerY: 0,
511
+ width: slideWidth,
512
+ height: slideHeight,
513
+ });
514
+ }
507
515
  if (!this.isViewMounted) {
508
516
  this.isViewMounted = true;
509
517
  console.log("[Slide] mount whiteboard view");
package/src/index.ts CHANGED
@@ -107,7 +107,7 @@ export interface AppResult {
107
107
  jumpToPage: (page: number) => boolean;
108
108
  setSildeReadonly: (bol: boolean) => void;
109
109
  setSyncEventQueuePolicy: (policy: NonNullable<ISlideConfig["syncEventQueuePolicy"]>) => void;
110
- onScaleChanged: (cb: (scale: number) => void) => void;
110
+ onScaleChanged: (cb: (scale: number) => void) => () => void;
111
111
  scaleView: (to: number) => void;
112
112
  getViewScale: () => number | undefined;
113
113
  translateView: (offsetX: number, offsetY: number) => void;
@@ -262,25 +262,22 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
262
262
  return {
263
263
  onScaleChanged: (cb: (scale: number) => void) => {
264
264
  if (!docsViewer) {
265
- return;
265
+ return () => void 0;
266
266
  }
267
- docsViewer.resizableContainer.onScaleChanged = cb;
267
+ return docsViewer.resizableContainer.addScaleChangedListener(cb);
268
268
  },
269
269
  scaleView: (to: number) => {
270
- let applyScale = Number(to);
271
- if (Number.isNaN(applyScale)) {
272
- applyScale = 1;
273
- }
274
- if (applyScale < 1.0) {
275
- applyScale = 1.0;
276
- }
277
- if (applyScale > 4.0) {
278
- applyScale = 4.0;
279
- }
280
-
281
- context.storage.setState({ slideScale: applyScale });
282
- context.storage.setState({ translateX: 0.5, translateY: 0.5 });
283
- docsViewer?.resizableContainer.scaleContainer(applyScale);
270
+ const applyScale = Number(to);
271
+ if (!Number.isFinite(applyScale) || applyScale <= 0) return;
272
+ const container = docsViewer?.resizableContainer;
273
+ if (!container || Math.abs(container.getScale() - applyScale) < 0.001) return;
274
+
275
+ context.storage.setState({
276
+ slideScale: applyScale,
277
+ translateX: 0.5,
278
+ translateY: 0.5,
279
+ });
280
+ container.scaleContainer(applyScale);
284
281
  },
285
282
  getViewScale: () => {
286
283
  return docsViewer?.resizableContainer.getScale();
package/src/typings.ts CHANGED
@@ -15,6 +15,10 @@ export interface Attributes {
15
15
  slideScale: number;
16
16
  translateX: number;
17
17
  translateY: number;
18
+ originSize: {
19
+ width: number;
20
+ height: number;
21
+ } | null;
18
22
  }
19
23
 
20
24
  export type MagixPayload = {