@netless/app-slide 0.2.2 → 0.3.0-canary.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.
Files changed (58) hide show
  1. package/README-zh.md +1 -4
  2. package/README.md +1 -4
  3. package/dist/Refrigerator.d.ts +13 -0
  4. package/dist/SlideViewer.d.ts +63 -0
  5. package/dist/constants.d.ts +2 -0
  6. package/dist/{DocsViewer/icons → icons}/arrow-left.d.ts +0 -0
  7. package/dist/{DocsViewer/icons → icons}/arrow-right.d.ts +0 -0
  8. package/dist/{DocsViewer/icons → icons}/pause.d.ts +0 -0
  9. package/dist/{DocsViewer/icons → icons}/play.d.ts +0 -0
  10. package/dist/{DocsViewer/icons → icons}/sidebar.d.ts +0 -0
  11. package/dist/index.d.ts +10 -35
  12. package/dist/main.cjs.js +177 -177
  13. package/dist/main.cjs.js.map +1 -1
  14. package/dist/main.es.js +1901 -2479
  15. package/dist/main.es.js.map +1 -1
  16. package/dist/main.iife.js +254 -254
  17. package/dist/main.iife.js.map +1 -1
  18. package/dist/typings.d.ts +21 -9
  19. package/dist/utils.d.ts +1 -0
  20. package/package.json +2 -2
  21. package/src/Refrigerator.ts +66 -0
  22. package/src/SlideViewer.ts +543 -0
  23. package/src/constants.ts +3 -0
  24. package/src/{DocsViewer/icons → icons}/arrow-left.ts +0 -0
  25. package/src/{DocsViewer/icons → icons}/arrow-right.ts +0 -0
  26. package/src/{DocsViewer/icons → icons}/pause.ts +0 -0
  27. package/src/{DocsViewer/icons → icons}/play.ts +0 -0
  28. package/src/{DocsViewer/icons → icons}/sidebar.ts +0 -0
  29. package/src/index.ts +210 -205
  30. package/src/style.scss +245 -5
  31. package/src/typings.ts +23 -10
  32. package/src/utils.ts +3 -0
  33. package/dist/DocsViewer/index.d.ts +0 -53
  34. package/dist/SlideController/helpers.d.ts +0 -6
  35. package/dist/SlideController/index.d.ts +0 -60
  36. package/dist/SlideDocsViewer/index.d.ts +0 -45
  37. package/dist/SlidePreviewer/index.d.ts +0 -52
  38. package/dist/utils/bgcolor.d.ts +0 -3
  39. package/dist/utils/freezer.d.ts +0 -22
  40. package/dist/utils/helpers.d.ts +0 -6
  41. package/dist/utils/logger.d.ts +0 -39
  42. package/src/DocsViewer/icons/arrow-left.svg +0 -4
  43. package/src/DocsViewer/icons/arrow-right.svg +0 -4
  44. package/src/DocsViewer/icons/pause.svg +0 -4
  45. package/src/DocsViewer/icons/play.svg +0 -4
  46. package/src/DocsViewer/icons/sidebar.svg +0 -4
  47. package/src/DocsViewer/index.ts +0 -368
  48. package/src/DocsViewer/style.scss +0 -228
  49. package/src/DocsViewer/variables.scss +0 -1
  50. package/src/SlideController/helpers.ts +0 -48
  51. package/src/SlideController/index.ts +0 -356
  52. package/src/SlideDocsViewer/index.ts +0 -210
  53. package/src/SlideDocsViewer/style.scss +0 -11
  54. package/src/SlidePreviewer/index.ts +0 -226
  55. package/src/utils/bgcolor.ts +0 -45
  56. package/src/utils/freezer.ts +0 -75
  57. package/src/utils/helpers.ts +0 -26
  58. package/src/utils/logger.ts +0 -106
package/src/index.ts CHANGED
@@ -1,234 +1,239 @@
1
- import type { NetlessApp } from "@netless/window-manager";
2
- import type { RoomState } from "white-web-sdk";
3
- import type { Slide } from "@netless/slide";
4
- import type { MountSlideOptions } from "./SlideDocsViewer";
5
- import type { Attributes, MagixEvents } from "./typings";
6
- import type { AddHooks, FreezableSlide } from "./utils/freezer";
1
+ import type { AppContext, NetlessApp, Player, WhiteBoardView } from "@netless/window-manager";
2
+ import type { AddHooks, AppOptions, Attributes, MagixEvents, SlideState } from "./typings";
3
+ import type { SlideViewerOptions } from "./SlideViewer";
7
4
 
8
- import { SideEffectManager } from "side-effect-manager";
9
- import {
10
- DefaultUrl,
11
- EmptyAttributes,
12
- syncSceneWithSlide,
13
- SlideController,
14
- } from "./SlideController";
15
- import { SlideDocsViewer } from "./SlideDocsViewer";
16
- import { apps, FreezerLength, addHooks, useFreezer } from "./utils/freezer";
17
- import { log, logger } from "./utils/logger";
18
- import styles from "./style.scss?inline";
19
-
20
- export type { PreviewParams } from "./SlidePreviewer";
21
- export { SlidePreviewer, default as previewSlide } from "./SlidePreviewer";
22
-
23
- export type { Attributes, AddHooks, FreezableSlide };
5
+ export type { SlideState, AddHooks, AppOptions, Attributes, MagixEvents, SlideViewerOptions };
24
6
 
25
- export const version = __APP_VERSION__;
7
+ import ColorString from "color-string";
8
+ import { SideEffectManager } from "side-effect-manager";
9
+ import { SlideViewer } from "./SlideViewer";
10
+ import { refrigerator, addHooks } from "./Refrigerator";
11
+ import { noop } from "./utils";
26
12
 
27
- export { DefaultUrl, apps, FreezerLength, addHooks };
28
-
29
- export interface AppOptions {
30
- /** show debug controller */
31
- debug?: boolean;
32
- /** scale */
33
- resolution?: number;
34
- /** background color for slide animations */
35
- bgColor?: string;
36
- /** minimal fps @default 25 */
37
- minFPS?: number;
38
- /** maximal fps @default 30 */
39
- maxFPS?: number;
40
- /** automatically decrease fps @default true */
41
- autoFPS?: boolean;
42
- /** whether to re-scale automatically @default true */
43
- autoResolution?: boolean;
44
- }
13
+ export { SlideViewer, refrigerator, addHooks };
45
14
 
46
- export interface Controller {
47
- slide: () => Slide | undefined;
48
- position: () => [page: number, pageCount: number] | undefined;
49
- nextStep: () => boolean;
50
- prevStep: () => boolean;
51
- nextPage: () => boolean;
52
- prevPage: () => boolean;
53
- jumpToPage: (page: number) => boolean;
54
- }
15
+ export const version = __APP_VERSION__;
55
16
 
56
- const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, Controller> = {
17
+ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
57
18
  kind: "Slide",
58
19
  setup(context) {
59
- console.log("[Slide] setup @ " + version);
60
-
61
- if (context.getIsWritable()) {
62
- context.storage.ensureState(EmptyAttributes);
63
- }
20
+ console.log("[Slide] setup @ " + __APP_VERSION__);
64
21
 
65
22
  if (!context.storage.state.taskId) {
66
23
  throw new Error("[Slide] no taskId");
67
24
  }
68
25
 
69
- const view = context.getView();
70
- if (!view) {
71
- throw new Error("[Slide] no view, please set scenePath on addApp()");
72
- }
73
- view.disableCameraTransform = true;
74
-
75
- const box = context.getBox();
76
- box.mountStyles(styles);
77
- try {
78
- box.$content.dataset.appSlideVersion = version;
79
- } catch {
80
- // ignore
81
- }
26
+ const options = context.getAppOptions() || {};
27
+ const debug = Boolean(options.debug);
82
28
 
83
- // must exist because of view
84
- const baseScenePath = context.getInitScenePath() as string;
85
-
86
- let docsViewer: SlideDocsViewer | null = null;
87
-
88
- const onPageChanged = (page: number) => {
89
- const room = context.getRoom();
90
- if (docsViewer && docsViewer.slideController) {
91
- let synced = false;
92
- if (room && context.getIsWritable()) {
93
- syncSceneWithSlide(room, context, docsViewer.slideController.slide, baseScenePath);
94
- synced = true;
95
- }
96
- log("[Slide] page to", page, synced ? "(synced)" : "");
97
- docsViewer.viewer.setPageIndex(page - 1);
98
- }
99
- };
100
-
101
- const mountSlideController = (options: MountSlideOptions): SlideController => {
102
- const slideController = new SlideController({
103
- context,
104
- ...options,
105
- onPageChanged,
106
- });
107
- if (useFreezer) apps.set(context.appId, slideController);
108
- logger.setAppController(context.appId, slideController);
109
- if (import.meta.env.DEV) {
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- (window as any).slideController = slideController;
112
- }
113
- slideController.readyPromise.then(options.onReady).then(() => {
114
- const room = context.getRoom();
115
- let synced = false;
116
- if (room && context.getIsWritable()) {
117
- syncSceneWithSlide(room, context, slideController.slide, baseScenePath);
118
- synced = true;
119
- }
120
- const page = slideController.slide.slideState.currentSlideIndex;
121
- log("[Slide] page to", page, synced ? "(synced)" : "", "(on ready)");
122
- });
123
- return slideController;
124
- };
125
-
126
- docsViewer = new SlideDocsViewer({
127
- box,
128
- view,
129
- mountSlideController,
130
- mountWhiteboard: context.mountView.bind(context),
29
+ const sideEffect = new SideEffectManager();
30
+
31
+ // create ui
32
+ const viewer = new SlideViewer({
33
+ interactive: true,
34
+ mode: "interactive",
35
+ resize: true,
36
+ enableGlobalClick: true,
37
+ controller: debug,
38
+ timestamp: make_timestamp(context),
39
+ logger: make_logger(context),
40
+ renderOptions: {
41
+ minFPS: options.minFPS || 25,
42
+ maxFPS: options.maxFPS || 30,
43
+ autoFPS: options.autoFPS ?? true,
44
+ autoResolution: options.autoResolution ?? true,
45
+ resolution: options.resolution,
46
+ transactionBgColor: options.bgColor || make_bg_color(context.box.$content),
47
+ },
48
+
49
+ taskId: context.storage.state.taskId,
50
+ url: context.storage.state.url,
131
51
  });
132
52
 
53
+ // For debugging.
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ (viewer as any).context = context;
56
+
57
+ sideEffect.addDisposer(refrigerator.set(context.appId, viewer));
58
+
133
59
  if (import.meta.env.DEV) {
134
60
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
- (window as any).slideDoc = docsViewer;
61
+ (window as any).slideViewer = viewer;
136
62
  }
137
63
 
138
- const room = context.getRoom();
139
- const sideEffect = new SideEffectManager();
64
+ // mount ui
65
+ context.box.mountStyles(SlideViewer.styles);
66
+ context.box.mountStage(viewer.$slide);
67
+ context.box.mountFooter(viewer.$footer);
68
+ context.box.mountContent(viewer.$content);
69
+ sideEffect.addDisposer(() => viewer.destroy());
140
70
 
141
- sideEffect.add(() => {
142
- logger.setAppContext(context.appId, context);
143
- logger.enable = context.getAppOptions()?.debug || import.meta.env.DEV;
144
- logger.level = import.meta.env.DEV ? "verbose" : "debug";
145
- return () => logger.deleteApp(context.appId);
71
+ // connect ui with netless app
72
+ connect({ context, viewer, sideEffect, debug });
73
+
74
+ onUnmount(context, () => {
75
+ console.log("[Slide] destroy");
76
+ sideEffect.flushAll();
146
77
  });
78
+ },
79
+ };
147
80
 
148
- if (room) {
149
- docsViewer.toggleClickThrough(room.state.memberState.currentApplianceName);
150
- sideEffect.add(() => {
151
- const onRoomStateChanged = (e: Partial<RoomState>) => {
152
- if (e.memberState && docsViewer) {
153
- docsViewer.toggleClickThrough(e.memberState.currentApplianceName);
154
- }
155
- };
156
- room.callbacks.on("onRoomStateChanged", onRoomStateChanged);
157
- return () => room.callbacks.off("onRoomStateChanged", onRoomStateChanged);
158
- });
81
+ export default SlideApp;
82
+
83
+ export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
84
+ const { container } = options;
85
+ const viewer = new SlideViewer(options);
86
+ container.appendChild(viewer.$content);
87
+ return viewer;
88
+ }
89
+
90
+ function make_timestamp(context: AppContext): () => number {
91
+ const room = context.room;
92
+ const player = context.displayer as Player;
93
+ if (room) {
94
+ return () => room.calibrationTimestamp;
95
+ } else if (player) {
96
+ return () => player.beginTimestamp + player.progressTime;
97
+ } else {
98
+ return () => Date.now();
99
+ }
100
+ }
101
+
102
+ function make_logger(context: AppContext) {
103
+ if (import.meta.env.DEV) {
104
+ return { info: console.debug.bind(console) };
105
+ }
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ return (context.displayer as any).logger;
108
+ }
109
+
110
+ function make_bg_color(el: HTMLElement): string {
111
+ try {
112
+ let bg = window.getComputedStyle(el).backgroundColor;
113
+ if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
114
+ bg = hex(bg);
115
+ console.log("[Slide] guess bg color:", bg);
116
+ return bg;
159
117
  }
118
+ if (el.parentElement) {
119
+ return make_bg_color(el.parentElement);
120
+ }
121
+ } catch {
122
+ // ignored
123
+ }
124
+ return "#ffffff";
125
+ }
160
126
 
161
- context.emitter.on("destroy", () => {
162
- log("[Slide] destroy", context.appId);
163
- if (useFreezer) apps.delete(context.appId);
164
- sideEffect.flushAll();
165
- if (docsViewer) {
166
- docsViewer.destroy();
167
- docsViewer = null;
168
- }
169
- });
127
+ // https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
128
+ function hex(color: string): string {
129
+ const result = ColorString.get(color);
130
+ if (result && result.model === "rgb") {
131
+ const args = result.value;
132
+
133
+ const integer =
134
+ ((Math.round(args[0]) & 0xff) << 16) +
135
+ ((Math.round(args[1]) & 0xff) << 8) +
136
+ (Math.round(args[2]) & 0xff);
137
+
138
+ const string = integer.toString(16);
139
+ return "#" + "000000".substring(string.length) + string;
140
+ } else {
141
+ return color;
142
+ }
143
+ }
170
144
 
171
- docsViewer.mount();
145
+ function onUnmount(context: AppContext, callback: () => void) {
146
+ context.emitter.on("destroy", callback);
147
+ }
172
148
 
173
- return {
174
- slide: () => {
175
- return docsViewer?.slideController?.slide;
176
- },
177
- nextStep: () => {
178
- if (docsViewer && docsViewer.slideController) {
179
- docsViewer?.slideController?.slide.nextStep();
180
- return true;
181
- }
182
- return false;
183
- },
184
- prevStep: () => {
185
- if (docsViewer && docsViewer.slideController) {
186
- docsViewer?.slideController?.slide.prevStep();
187
- return true;
188
- }
189
- return false;
190
- },
191
- position: () => {
192
- const controller = docsViewer?.slideController;
193
- if (controller) {
194
- return [controller.page, controller.pageCount] as [page: number, pageCount: number];
195
- }
196
- },
197
- nextPage: () => {
198
- const controller = docsViewer?.slideController;
199
- if (controller) {
200
- const { page, pageCount } = controller;
201
- if (pageCount > 0 && page < pageCount) {
202
- controller.jumpToPage(page + 1);
203
- return true;
204
- }
205
- }
206
- return false;
207
- },
208
- prevPage: () => {
209
- const controller = docsViewer?.slideController;
210
- if (controller) {
211
- const { page, pageCount } = controller;
212
- if (pageCount > 0 && page > 1) {
213
- controller.jumpToPage(page - 1);
214
- return true;
149
+ interface ConnectParams {
150
+ context: AppContext;
151
+ viewer: SlideViewer;
152
+ sideEffect: SideEffectManager;
153
+ debug: boolean;
154
+ }
155
+
156
+ function connect({ context, viewer, sideEffect, debug }: ConnectParams) {
157
+ const log = debug ? console.log.bind(console) : noop;
158
+
159
+ let whiteboard: WhiteBoardView | null = null;
160
+
161
+ viewer.prepare(({ width, height, slideCount }) => {
162
+ if (context.destroyed) return;
163
+ width && context.box.setStageRatio(height / width);
164
+ whiteboard = context.createWhiteBoardView(slideCount);
165
+ kick_start();
166
+ });
167
+
168
+ const { slide } = viewer;
169
+
170
+ function kick_start() {
171
+ if (context.storage.state.state) {
172
+ log("[Slide] restore", { ...context.storage.state.state });
173
+ slide.setSlideState(context.storage.state.state);
174
+ } else if (context.isAddApp) {
175
+ log("[Slide] kick start");
176
+ slide.renderSlide(1);
177
+ } else {
178
+ log("[Slide] wait for restore");
179
+ const timeoutID = sideEffect.add(() => {
180
+ const timer = setInterval(() => {
181
+ if (!context.storage.state.state) {
182
+ log("[Slide] timeout");
183
+ slide.renderSlide(1);
215
184
  }
216
- }
217
- return false;
218
- },
219
- jumpToPage: (newPage: number) => {
220
- const controller = docsViewer?.slideController;
221
- if (controller) {
222
- const { page, pageCount } = controller;
223
- if (pageCount > 0 && page > 0 && page <= pageCount) {
224
- controller.jumpToPage(newPage);
225
- return true;
185
+ }, 15 * 1000);
186
+ return () => clearInterval(timer);
187
+ });
188
+ const disposerID = sideEffect.addDisposer(
189
+ context.storage.addStateChangedListener(() => {
190
+ if (context.storage.state.state) {
191
+ log("[Slide] restore", { ...context.storage.state.state });
192
+ slide.setSlideState(context.storage.state.state);
193
+ sideEffect.flush(disposerID);
194
+ sideEffect.flush(timeoutID);
226
195
  }
227
- }
228
- return false;
229
- },
230
- };
231
- },
232
- };
196
+ })
197
+ );
198
+ }
199
+ }
233
200
 
234
- export default SlideApp;
201
+ // setup sync event
202
+ slide.on("stateChange", () => {
203
+ if (context.isWritable) {
204
+ log("[Slide] save state", { ...slide.slideState });
205
+ context.storage.setState({ state: slide.slideState });
206
+ }
207
+ });
208
+ slide.on("syncDispatch", payload => {
209
+ if (context.isWritable) {
210
+ log("[Slide] dispatch", { ...payload });
211
+ context.dispatchMagixEvent("syncDispatch", payload);
212
+ }
213
+ });
214
+ sideEffect.addDisposer(
215
+ context.addMagixEventListener(
216
+ "syncDispatch",
217
+ ({ payload }) => {
218
+ log("[Slide] receive", { ...payload });
219
+ slide.emit("syncReceive", payload);
220
+ },
221
+ { fireSelfEventAfterCommit: true }
222
+ )
223
+ );
224
+
225
+ // save state
226
+ slide.on("slideChange", page => {
227
+ log("[Slide] page to", page);
228
+ whiteboard && whiteboard.jumpPage(page - 1);
229
+ viewer.setPage(page);
230
+ });
231
+
232
+ // handle ui events
233
+ viewer.setReadonly(!context.isWritable);
234
+ sideEffect.addDisposer(
235
+ context.emitter.on("writableChange", w => {
236
+ viewer.setReadonly(!w);
237
+ })
238
+ );
239
+ }