@netless/app-slide 0.0.17 → 0.0.21
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/DocsViewer/index.d.ts +1 -0
- package/dist/SlideController/helpers.d.ts +6 -0
- package/dist/SlideController/index.d.ts +51 -0
- package/dist/SlideDocsViewer/index.d.ts +13 -7
- package/dist/index.d.ts +4 -10
- package/dist/main.cjs.js +4194 -2292
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +29691 -25580
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +4194 -2292
- package/dist/main.iife.js.map +1 -1
- package/dist/typings.d.ts +14 -0
- package/dist/utils/helpers.d.ts +1 -0
- package/package.json +7 -2
- package/src/DocsViewer/index.ts +12 -7
- package/src/SlideController/helpers.ts +39 -0
- package/src/SlideController/index.ts +257 -0
- package/src/SlideDocsViewer/index.ts +41 -13
- package/src/index.ts +49 -139
- package/src/typings.ts +17 -0
- package/src/utils/helpers.ts +4 -0
- package/dist/utils/slide.d.ts +0 -47
- package/playground.ts +0 -31
- package/src/utils/slide.ts +0 -203
- package/tsconfig.json +0 -7
- package/vite.config.ts +0 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Slide, SLIDE_EVENTS, SyncEvent } from "@netless/slide";
|
|
2
|
+
export declare type SlideState = Slide["slideState"];
|
|
3
|
+
export interface Attributes {
|
|
4
|
+
/** convert task id */
|
|
5
|
+
taskId: string;
|
|
6
|
+
/** base url of converted resources */
|
|
7
|
+
url: string;
|
|
8
|
+
/** internal state of slide, do not change */
|
|
9
|
+
state: SlideState | null;
|
|
10
|
+
}
|
|
11
|
+
export declare type MagixPayload = {
|
|
12
|
+
type: typeof SLIDE_EVENTS.syncDispatch;
|
|
13
|
+
payload: SyncEvent;
|
|
14
|
+
};
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export declare function flattenEvent(ev: MouseEvent | TouchEvent): MouseEvent |
|
|
|
3
3
|
export declare function preventEvent(ev: Event): void;
|
|
4
4
|
export declare function isObj(obj: unknown): boolean;
|
|
5
5
|
export declare function wait(ms: number): Promise<void>;
|
|
6
|
+
export declare function deepClone<T>(obj: T): T;
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"main": "dist/main.cjs.js",
|
|
5
5
|
"module": "dist/main.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"dist",
|
|
10
|
+
"README-zh.md"
|
|
11
|
+
],
|
|
7
12
|
"devDependencies": {
|
|
8
13
|
"@netless/app-shared": "^0.1.1",
|
|
9
|
-
"@netless/slide": "^0.1.
|
|
14
|
+
"@netless/slide": "^0.1.25",
|
|
10
15
|
"side-effect-manager": "^0.1.3",
|
|
11
16
|
"vanilla-lazyload": "^17.5.0"
|
|
12
17
|
},
|
package/src/DocsViewer/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { playSVG } from "./icons/play";
|
|
|
5
5
|
import { pauseSVG } from "./icons/pause";
|
|
6
6
|
|
|
7
7
|
import type { ReadonlyTeleBox } from "@netless/window-manager";
|
|
8
|
+
import type { ILazyLoadInstance } from "vanilla-lazyload";
|
|
8
9
|
import LazyLoad from "vanilla-lazyload";
|
|
9
10
|
import { SideEffectManager } from "side-effect-manager";
|
|
10
11
|
|
|
@@ -127,11 +128,21 @@ export class DocsViewer {
|
|
|
127
128
|
return this.$content;
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
private previewLazyLoad?: ILazyLoadInstance;
|
|
132
|
+
|
|
130
133
|
protected renderPreview(): HTMLElement {
|
|
131
134
|
if (!this.$preview) {
|
|
132
135
|
const $preview = document.createElement("div");
|
|
133
136
|
$preview.className = this.wrapClassName("preview") + " tele-fancy-scrollbar";
|
|
134
137
|
this.$preview = $preview;
|
|
138
|
+
this.sideEffect.add(() => {
|
|
139
|
+
this.previewLazyLoad = new LazyLoad({
|
|
140
|
+
container: this.$preview,
|
|
141
|
+
elements_selector: `.${this.wrapClassName("preview-page>img")}`,
|
|
142
|
+
});
|
|
143
|
+
return () => this.previewLazyLoad?.destroy();
|
|
144
|
+
});
|
|
145
|
+
|
|
135
146
|
this.refreshPreview();
|
|
136
147
|
|
|
137
148
|
this.sideEffect.addEventListener($preview, "click", ev => {
|
|
@@ -189,13 +200,7 @@ export class DocsViewer {
|
|
|
189
200
|
$preview.appendChild($page);
|
|
190
201
|
});
|
|
191
202
|
|
|
192
|
-
this.
|
|
193
|
-
const previewLazyLoad = new LazyLoad({
|
|
194
|
-
container: this.$preview,
|
|
195
|
-
elements_selector: `.${this.wrapClassName("preview-page>img")}`,
|
|
196
|
-
});
|
|
197
|
-
return () => previewLazyLoad.destroy();
|
|
198
|
-
}, "preview-lazyload");
|
|
203
|
+
this.previewLazyLoad?.update();
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
protected renderPreviewMask(): HTMLElement {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AppContext, Room } from "@netless/window-manager";
|
|
2
|
+
import type { ScenePathType } from "white-web-sdk";
|
|
3
|
+
import type { Slide } from "@netless/slide";
|
|
4
|
+
import type { DocsViewerPage } from "../DocsViewer";
|
|
5
|
+
import type { Attributes } from "../typings";
|
|
6
|
+
|
|
7
|
+
export function createDocsViewerPages(slide: Slide): DocsViewerPage[] {
|
|
8
|
+
const { width, height, slideCount, slideState } = slide;
|
|
9
|
+
const { taskId, url } = slideState;
|
|
10
|
+
const pages: DocsViewerPage[] = [];
|
|
11
|
+
for (let i = 1; i <= slideCount; ++i) {
|
|
12
|
+
pages.push({ width, height, thumbnail: `${url}/${taskId}/preview/${i}.png`, src: "ppt" });
|
|
13
|
+
}
|
|
14
|
+
return pages;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// because `renderSlide()` is slow, but switching between scenes in whiteboard is fast
|
|
18
|
+
// so here we make scenes sync with slide page
|
|
19
|
+
export function syncSceneWithSlide(
|
|
20
|
+
room: Room,
|
|
21
|
+
context: AppContext<Attributes>,
|
|
22
|
+
slide: Slide,
|
|
23
|
+
baseScenePath: string
|
|
24
|
+
) {
|
|
25
|
+
const page = slide.slideState.currentSlideIndex;
|
|
26
|
+
if (!(page > 0) || !context.getIsWritable()) return;
|
|
27
|
+
|
|
28
|
+
const scenePath = [baseScenePath, page].join("/");
|
|
29
|
+
|
|
30
|
+
if (room.scenePathType(scenePath) !== ("page" as ScenePathType.Page)) {
|
|
31
|
+
room.removeScenes(baseScenePath);
|
|
32
|
+
const count = slide.slideCount;
|
|
33
|
+
const scenes: { name: string }[] = [];
|
|
34
|
+
for (let i = 1; i <= count; ++i) scenes.push({ name: `${i}` });
|
|
35
|
+
room.putScenes(baseScenePath, scenes);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
context.setScenePath(scenePath);
|
|
39
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// this controller does these things:
|
|
2
|
+
// 1. map slide events to ui
|
|
3
|
+
// 2. make sure to init correctly
|
|
4
|
+
// - the one with (context.isAddApp === true) should call renderSlide(1)
|
|
5
|
+
// - others wait for first sync event and restore from sync state
|
|
6
|
+
// - if none of above happen, force doing renderSlide(1) after timeout
|
|
7
|
+
// 3. send/receive slide sync events
|
|
8
|
+
// 4. automatically re-create scenes to sync strokes, a view must be existing
|
|
9
|
+
// 5. pages information are loaded dynamically by the slide package
|
|
10
|
+
|
|
11
|
+
import type { Event as MagixEvent } from "white-web-sdk";
|
|
12
|
+
import type { AppContext, Player, Room } from "@netless/window-manager";
|
|
13
|
+
import type { SyncEvent } from "@netless/slide";
|
|
14
|
+
import type { Attributes, MagixPayload, SlideState } from "../typings";
|
|
15
|
+
|
|
16
|
+
import { SideEffectManager } from "side-effect-manager";
|
|
17
|
+
import { Slide, SLIDE_EVENTS } from "@netless/slide";
|
|
18
|
+
import { clamp, deepClone, isObj } from "../utils/helpers";
|
|
19
|
+
export { syncSceneWithSlide, createDocsViewerPages } from "./helpers";
|
|
20
|
+
|
|
21
|
+
export const DefaultUrl = "https://convertcdn.netless.link/dynamicConvert";
|
|
22
|
+
export const MaxPollCount = 40; // 500ms * 40 times = 20s
|
|
23
|
+
export const EmptyAttributes: Attributes = {
|
|
24
|
+
taskId: "",
|
|
25
|
+
url: "",
|
|
26
|
+
state: null,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface SlideControllerOptions {
|
|
30
|
+
context: AppContext<Attributes>;
|
|
31
|
+
anchor: HTMLDivElement;
|
|
32
|
+
onPageChanged: (page: number) => void;
|
|
33
|
+
onTransitionStart: () => void;
|
|
34
|
+
onTransitionEnd: () => void;
|
|
35
|
+
onError: (args: { error: Error }) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class SlideController {
|
|
39
|
+
public readonly context: SlideControllerOptions["context"];
|
|
40
|
+
public readonly slide: Slide;
|
|
41
|
+
public readonly debug: boolean;
|
|
42
|
+
|
|
43
|
+
private readonly channel: string;
|
|
44
|
+
private readonly room?: Room;
|
|
45
|
+
private readonly player?: Player;
|
|
46
|
+
private readonly sideEffect = new SideEffectManager();
|
|
47
|
+
|
|
48
|
+
private readonly onPageChanged: SlideControllerOptions["onPageChanged"];
|
|
49
|
+
private readonly onTransitionStart: SlideControllerOptions["onTransitionStart"];
|
|
50
|
+
private readonly onTransitionEnd: SlideControllerOptions["onTransitionEnd"];
|
|
51
|
+
private readonly onError: SlideControllerOptions["onError"];
|
|
52
|
+
|
|
53
|
+
public constructor({
|
|
54
|
+
context,
|
|
55
|
+
anchor,
|
|
56
|
+
onPageChanged,
|
|
57
|
+
onTransitionStart,
|
|
58
|
+
onTransitionEnd,
|
|
59
|
+
onError,
|
|
60
|
+
}: SlideControllerOptions) {
|
|
61
|
+
this.onPageChanged = onPageChanged;
|
|
62
|
+
this.onTransitionStart = onTransitionStart;
|
|
63
|
+
this.onTransitionEnd = onTransitionEnd;
|
|
64
|
+
this.onError = onError;
|
|
65
|
+
this.context = context;
|
|
66
|
+
this.channel = `channel-${context.appId}`;
|
|
67
|
+
this.room = context.getRoom();
|
|
68
|
+
this.player = this.room ? undefined : (context.getDisplayer() as Player);
|
|
69
|
+
this.debug = import.meta.env.DEV || !!context.getAppOptions()?.debug;
|
|
70
|
+
this.slide = this.createSlide(anchor);
|
|
71
|
+
this.initialize();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public jumpToPage(page: number) {
|
|
75
|
+
if (this.ready) {
|
|
76
|
+
page = clamp(page, 1, this.pageCount);
|
|
77
|
+
this.slide.renderSlide(page);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private initialize() {
|
|
82
|
+
this.registerEventListeners();
|
|
83
|
+
this.kickStart();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private kickStart() {
|
|
87
|
+
const { context, slide } = this;
|
|
88
|
+
const { taskId, url, state } = { ...EmptyAttributes, ...this.context.getAttributes() };
|
|
89
|
+
slide.setResource(taskId, url || DefaultUrl);
|
|
90
|
+
if (state) {
|
|
91
|
+
// if we already have state, try restore from it
|
|
92
|
+
if (this.debug) {
|
|
93
|
+
console.log("[Slide] init with state", deepClone(state));
|
|
94
|
+
}
|
|
95
|
+
slide.setSlideState(deepClone(state));
|
|
96
|
+
} else if (context.isAddApp) {
|
|
97
|
+
// otherwise, maybe this slide is just added, let the adder kick start first render
|
|
98
|
+
if (this.debug) {
|
|
99
|
+
console.log("[Slide] init by renderSlide", 1);
|
|
100
|
+
}
|
|
101
|
+
slide.renderSlide(1);
|
|
102
|
+
}
|
|
103
|
+
// there's still some risk that the adder is left and no first render
|
|
104
|
+
// so anyway, we start polling the slide's "ready state"
|
|
105
|
+
// if in the next 20 seconds the slide is not ready, start render first page
|
|
106
|
+
this.pollReadyState();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private registerEventListeners() {
|
|
110
|
+
const { context, slide } = this;
|
|
111
|
+
|
|
112
|
+
this.sideEffect.add(() => {
|
|
113
|
+
const displayer = context.getDisplayer();
|
|
114
|
+
displayer.addMagixEventListener(this.channel, this.magixEventListener, {
|
|
115
|
+
fireSelfEventAfterCommit: true,
|
|
116
|
+
});
|
|
117
|
+
// here we do not pass second param -- the listener
|
|
118
|
+
// because channel name is already unique, just make it simpler
|
|
119
|
+
return () => displayer.removeMagixEventListener(this.channel);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
slide.on(SLIDE_EVENTS.slideChange, this.onPageChanged);
|
|
123
|
+
slide.on(SLIDE_EVENTS.renderStart, this.onTransitionStart);
|
|
124
|
+
slide.on(SLIDE_EVENTS.renderEnd, this.onTransitionEnd);
|
|
125
|
+
slide.on(SLIDE_EVENTS.mainSeqStepStart, this.onTransitionStart);
|
|
126
|
+
slide.on(SLIDE_EVENTS.mainSeqStepEnd, this.onTransitionEnd);
|
|
127
|
+
slide.on(SLIDE_EVENTS.renderError, this.onError);
|
|
128
|
+
slide.on(SLIDE_EVENTS.stateChange, this.onStateChange);
|
|
129
|
+
slide.on(SLIDE_EVENTS.syncDispatch, this.onSyncDispatch);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private onSyncDispatch = (event: SyncEvent) => {
|
|
133
|
+
if (this.context.getIsWritable() && this.room) {
|
|
134
|
+
const payload: MagixPayload = {
|
|
135
|
+
type: SLIDE_EVENTS.syncDispatch,
|
|
136
|
+
payload: event,
|
|
137
|
+
};
|
|
138
|
+
if (this.debug) {
|
|
139
|
+
console.log("[Slide] dispatch", event);
|
|
140
|
+
}
|
|
141
|
+
this.room.dispatchMagixEvent(this.channel, payload);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
private magixEventListener = (ev: MagixEvent) => {
|
|
146
|
+
if (ev.event === this.channel && isObj(ev.payload)) {
|
|
147
|
+
const { type, payload } = ev.payload as MagixPayload;
|
|
148
|
+
if (type === SLIDE_EVENTS.syncDispatch) {
|
|
149
|
+
this.syncStateOnce();
|
|
150
|
+
if (this.debug) {
|
|
151
|
+
console.log("[Slide] receive", payload);
|
|
152
|
+
}
|
|
153
|
+
this.slide.emit(SLIDE_EVENTS.syncReceive, payload);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
private syncStateOnceFlag = true;
|
|
159
|
+
private syncStateOnce() {
|
|
160
|
+
// sync state before the first event, so that they can be in the correct order
|
|
161
|
+
if (this.syncStateOnceFlag) {
|
|
162
|
+
const { state } = { ...EmptyAttributes, ...this.context.getAttributes() };
|
|
163
|
+
if (state) {
|
|
164
|
+
if (this.debug) {
|
|
165
|
+
console.log("[Slide] sync with state (once)", deepClone(state));
|
|
166
|
+
}
|
|
167
|
+
this.slide.setSlideState(deepClone(state));
|
|
168
|
+
this.syncStateOnceFlag = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private onStateChange = (state: SlideState) => {
|
|
174
|
+
if (this.context.getIsWritable()) {
|
|
175
|
+
this.context.updateAttributes(["state"], state);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
private resolveReady!: () => void;
|
|
180
|
+
public readonly readyPromise = new Promise<void>(resolve => {
|
|
181
|
+
this.resolveReady = resolve;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
private pollCount = 0;
|
|
185
|
+
private pollReadyState = () => {
|
|
186
|
+
if (this.ready) {
|
|
187
|
+
this.resolveReady();
|
|
188
|
+
} else if (this.pollCount < MaxPollCount) {
|
|
189
|
+
this.pollCount++;
|
|
190
|
+
setTimeout(this.pollReadyState, 500);
|
|
191
|
+
} else {
|
|
192
|
+
this.pollCount = 0;
|
|
193
|
+
if (this.debug) {
|
|
194
|
+
console.log("[Slide] renderSlide (retry after timeout)", 1);
|
|
195
|
+
}
|
|
196
|
+
this.slide.renderSlide(1);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
public get ready() {
|
|
201
|
+
return this.slide.slideCount > 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public get pageCount() {
|
|
205
|
+
return this.slide.slideCount;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public get page() {
|
|
209
|
+
return this.slide.slideState.currentSlideIndex;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private createSlide(anchor: HTMLDivElement) {
|
|
213
|
+
const slide = new Slide({
|
|
214
|
+
anchor,
|
|
215
|
+
interactive: true,
|
|
216
|
+
mode: "interactive",
|
|
217
|
+
resize: true,
|
|
218
|
+
controller: this.debug,
|
|
219
|
+
renderOptions: {
|
|
220
|
+
minFPS: 25,
|
|
221
|
+
maxFPS: 30,
|
|
222
|
+
autoFPS: true,
|
|
223
|
+
autoResolution: true,
|
|
224
|
+
resolution: this.context.getAppOptions()?.resolution,
|
|
225
|
+
},
|
|
226
|
+
timestamp: this.timestamp,
|
|
227
|
+
});
|
|
228
|
+
if (this.debug) {
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
+
(window as any).slide = slide;
|
|
231
|
+
}
|
|
232
|
+
return slide;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private destroyed = false;
|
|
236
|
+
|
|
237
|
+
public destroy() {
|
|
238
|
+
this.sideEffect.flushAll();
|
|
239
|
+
if (!this.destroyed) {
|
|
240
|
+
if (this.debug) {
|
|
241
|
+
console.log("[Slide] destroy (once)");
|
|
242
|
+
}
|
|
243
|
+
this.slide.destroy();
|
|
244
|
+
this.destroyed = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public timestamp = () => {
|
|
249
|
+
if (this.room && this.room.calibrationTimestamp) {
|
|
250
|
+
return this.room.calibrationTimestamp;
|
|
251
|
+
} else if (this.player) {
|
|
252
|
+
return this.player.beginTimestamp + this.player.progressTime;
|
|
253
|
+
} else {
|
|
254
|
+
return Date.now();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type { ApplianceNames } from "white-web-sdk";
|
|
2
1
|
import type { ReadonlyTeleBox, AnimationMode, View } from "@netless/window-manager";
|
|
3
|
-
import type {
|
|
4
|
-
import type { SlideController } from "../utils/slide";
|
|
2
|
+
import type { SlideController, SlideControllerOptions } from "../SlideController";
|
|
5
3
|
|
|
6
4
|
import { SideEffectManager } from "side-effect-manager";
|
|
7
|
-
import { createDocsViewerPages } from "../
|
|
5
|
+
import { createDocsViewerPages } from "../SlideController";
|
|
8
6
|
import { DocsViewer } from "../DocsViewer";
|
|
9
7
|
|
|
10
|
-
const ClickThroughAppliances = new Set(["clicker", "selector"]);
|
|
8
|
+
export const ClickThroughAppliances = new Set(["clicker", "selector"]);
|
|
9
|
+
|
|
10
|
+
export type MountSlideOptions = Omit<SlideControllerOptions, "context" | "onPageChanged"> & {
|
|
11
|
+
onReady: () => void;
|
|
12
|
+
};
|
|
11
13
|
|
|
12
14
|
export interface SlideDocsViewerConfig {
|
|
13
15
|
box: ReadonlyTeleBox;
|
|
14
16
|
view: View;
|
|
15
|
-
mountSlideController: (
|
|
17
|
+
mountSlideController: (options: MountSlideOptions) => SlideController;
|
|
16
18
|
mountWhiteboard: (dom: HTMLDivElement) => void;
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -38,6 +40,14 @@ export class SlideDocsViewer {
|
|
|
38
40
|
onPlay: this.onPlay,
|
|
39
41
|
});
|
|
40
42
|
|
|
43
|
+
this.sideEffect.add(() => {
|
|
44
|
+
const handler = (readonly: boolean): void => {
|
|
45
|
+
this.setReadonly(readonly);
|
|
46
|
+
};
|
|
47
|
+
box.events.on("readonly", handler);
|
|
48
|
+
return () => box.events.off("readonly", handler);
|
|
49
|
+
});
|
|
50
|
+
|
|
41
51
|
this.render();
|
|
42
52
|
}
|
|
43
53
|
|
|
@@ -87,11 +97,16 @@ export class SlideDocsViewer {
|
|
|
87
97
|
return this.$whiteboardView;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
public
|
|
100
|
+
public mount() {
|
|
91
101
|
this.viewer.mount();
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
102
|
+
|
|
103
|
+
this.slideController = this.mountSlideController({
|
|
104
|
+
anchor: this.$slide,
|
|
105
|
+
onTransitionStart: this.viewer.setPlaying,
|
|
106
|
+
onTransitionEnd: this.viewer.setPaused,
|
|
107
|
+
onReady: this.refreshPages,
|
|
108
|
+
onError: this.onError,
|
|
109
|
+
});
|
|
95
110
|
|
|
96
111
|
this.scaleDocsToFit();
|
|
97
112
|
this.sideEffect.add(() => {
|
|
@@ -102,8 +117,21 @@ export class SlideDocsViewer {
|
|
|
102
117
|
return this;
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
protected
|
|
106
|
-
|
|
120
|
+
protected onError = ({ error }: { error: Error }) => {
|
|
121
|
+
this.viewer.setPaused();
|
|
122
|
+
console.warn("[Slide] render error", error);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
protected refreshPages = () => {
|
|
126
|
+
if (this.slideController) {
|
|
127
|
+
this.viewer.pages = createDocsViewerPages(this.slideController.slide);
|
|
128
|
+
this.viewer.setPageIndex(this.getPageIndex(this.slideController.page));
|
|
129
|
+
this.scaleDocsToFit();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
protected getPageIndex(page: number) {
|
|
134
|
+
return (page > 0 ? page : 1) - 1;
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
public unmount() {
|
|
@@ -125,7 +153,7 @@ export class SlideDocsViewer {
|
|
|
125
153
|
this.viewer.destroy();
|
|
126
154
|
}
|
|
127
155
|
|
|
128
|
-
public toggleClickThrough(tool?:
|
|
156
|
+
public toggleClickThrough(tool?: string) {
|
|
129
157
|
this.$whiteboardView.style.pointerEvents =
|
|
130
158
|
!tool || ClickThroughAppliances.has(tool) ? "none" : "auto";
|
|
131
159
|
}
|