@netless/app-slide 0.2.103 → 0.2.105-beta.0
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 +5 -0
- package/README.md +5 -0
- package/dist/index.d.ts +14 -2
- package/dist/main.cjs.js +147 -147
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +87 -43
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +131 -131
- package/dist/main.iife.js.map +1 -1
- package/package.json +3 -2
- package/src/DocsViewer/ResizableContainer.ts +35 -9
- package/src/SlideController/index.ts +1 -0
- package/src/SlideDocsViewer/index.ts +26 -18
- package/src/index.ts +34 -20
- package/src/typings.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.105-beta.0",
|
|
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",
|
|
@@ -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
|
-
|
|
191
|
-
this.
|
|
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
|
}
|
|
@@ -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 (
|
|
491
|
-
this.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
width,
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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,13 +107,15 @@ 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;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult>
|
|
116
|
+
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> & {
|
|
117
|
+
teardown(context: import("@netless/window-manager").AppContext): void;
|
|
118
|
+
} = {
|
|
117
119
|
kind: "Slide",
|
|
118
120
|
setup(context) {
|
|
119
121
|
console.log("[Slide] setup @ " + version);
|
|
@@ -247,7 +249,14 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
247
249
|
});
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
let disposed = false;
|
|
253
|
+
let offDestroy: (() => void) | undefined;
|
|
254
|
+
const teardown = () => {
|
|
255
|
+
if (disposed) return;
|
|
256
|
+
disposed = true;
|
|
257
|
+
const removeDestroy = offDestroy;
|
|
258
|
+
offDestroy = undefined;
|
|
259
|
+
removeDestroy?.();
|
|
251
260
|
log("[Slide] destroy", context.appId);
|
|
252
261
|
if (useFreezer) apps.delete(context.appId);
|
|
253
262
|
sideEffect.flushAll();
|
|
@@ -255,32 +264,33 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
255
264
|
docsViewer.destroy();
|
|
256
265
|
docsViewer = null;
|
|
257
266
|
}
|
|
258
|
-
}
|
|
267
|
+
};
|
|
268
|
+
offDestroy = context.emitter.on("destroy", teardown);
|
|
259
269
|
|
|
260
270
|
docsViewer.mount();
|
|
261
271
|
|
|
272
|
+
(SlideApp as any).__teardownByContext ||= new WeakMap<object, () => void>();
|
|
273
|
+
(SlideApp as any).__teardownByContext.set(context, teardown);
|
|
274
|
+
|
|
262
275
|
return {
|
|
263
276
|
onScaleChanged: (cb: (scale: number) => void) => {
|
|
264
277
|
if (!docsViewer) {
|
|
265
|
-
return;
|
|
278
|
+
return () => void 0;
|
|
266
279
|
}
|
|
267
|
-
docsViewer.resizableContainer.
|
|
280
|
+
return docsViewer.resizableContainer.addScaleChangedListener(cb);
|
|
268
281
|
},
|
|
269
282
|
scaleView: (to: number) => {
|
|
270
|
-
|
|
271
|
-
if (Number.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
context.storage.setState({ slideScale: applyScale });
|
|
282
|
-
context.storage.setState({ translateX: 0.5, translateY: 0.5 });
|
|
283
|
-
docsViewer?.resizableContainer.scaleContainer(applyScale);
|
|
283
|
+
const applyScale = Number(to);
|
|
284
|
+
if (!Number.isFinite(applyScale) || applyScale <= 0) return;
|
|
285
|
+
const container = docsViewer?.resizableContainer;
|
|
286
|
+
if (!container || Math.abs(container.getScale() - applyScale) < 0.001) return;
|
|
287
|
+
|
|
288
|
+
context.storage.setState({
|
|
289
|
+
slideScale: applyScale,
|
|
290
|
+
translateX: 0.5,
|
|
291
|
+
translateY: 0.5,
|
|
292
|
+
});
|
|
293
|
+
container.scaleContainer(applyScale);
|
|
284
294
|
},
|
|
285
295
|
getViewScale: () => {
|
|
286
296
|
return docsViewer?.resizableContainer.getScale();
|
|
@@ -387,6 +397,10 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
|
|
|
387
397
|
},
|
|
388
398
|
};
|
|
389
399
|
},
|
|
400
|
+
teardown(context) {
|
|
401
|
+
(SlideApp as any).__teardownByContext?.get(context)?.();
|
|
402
|
+
(SlideApp as any).__teardownByContext?.delete(context);
|
|
403
|
+
},
|
|
390
404
|
};
|
|
391
405
|
|
|
392
406
|
export default SlideApp;
|