@netless/app-slide 0.2.2 → 0.3.0-canary.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.
Files changed (59) 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 +66 -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 +1918 -2478
  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 +557 -0
  23. package/src/constants.ts +3 -0
  24. package/src/fancy-scrollbar.scss +57 -0
  25. package/src/{DocsViewer/icons → icons}/arrow-left.ts +0 -0
  26. package/src/{DocsViewer/icons → icons}/arrow-right.ts +0 -0
  27. package/src/{DocsViewer/icons → icons}/pause.ts +0 -0
  28. package/src/{DocsViewer/icons → icons}/play.ts +0 -0
  29. package/src/{DocsViewer/icons → icons}/sidebar.ts +0 -0
  30. package/src/index.ts +216 -205
  31. package/src/style.scss +247 -5
  32. package/src/typings.ts +23 -10
  33. package/src/utils.ts +3 -0
  34. package/dist/DocsViewer/index.d.ts +0 -53
  35. package/dist/SlideController/helpers.d.ts +0 -6
  36. package/dist/SlideController/index.d.ts +0 -60
  37. package/dist/SlideDocsViewer/index.d.ts +0 -45
  38. package/dist/SlidePreviewer/index.d.ts +0 -52
  39. package/dist/utils/bgcolor.d.ts +0 -3
  40. package/dist/utils/freezer.d.ts +0 -22
  41. package/dist/utils/helpers.d.ts +0 -6
  42. package/dist/utils/logger.d.ts +0 -39
  43. package/src/DocsViewer/icons/arrow-left.svg +0 -4
  44. package/src/DocsViewer/icons/arrow-right.svg +0 -4
  45. package/src/DocsViewer/icons/pause.svg +0 -4
  46. package/src/DocsViewer/icons/play.svg +0 -4
  47. package/src/DocsViewer/icons/sidebar.svg +0 -4
  48. package/src/DocsViewer/index.ts +0 -368
  49. package/src/DocsViewer/style.scss +0 -228
  50. package/src/DocsViewer/variables.scss +0 -1
  51. package/src/SlideController/helpers.ts +0 -48
  52. package/src/SlideController/index.ts +0 -356
  53. package/src/SlideDocsViewer/index.ts +0 -210
  54. package/src/SlideDocsViewer/style.scss +0 -11
  55. package/src/SlidePreviewer/index.ts +0 -226
  56. package/src/utils/bgcolor.ts +0 -45
  57. package/src/utils/freezer.ts +0 -75
  58. package/src/utils/helpers.ts +0 -26
  59. package/src/utils/logger.ts +0 -106
@@ -0,0 +1,57 @@
1
+ $namespace: "netless-app-slide";
2
+
3
+ $scrollbar-size: 8px;
4
+ $scrollbar-ff-width: auto; // FF-only accepts auto, thin, none
5
+ $scrollbar-minlength: 50px; // Minimum length of scrollbar thumb
6
+ $scrollbar-track-color: transparent;
7
+ $scrollbar-color: rgba(68, 78, 96, 0.1);
8
+ $scrollbar-color-hover: rgba(68, 78, 96, 0.2);
9
+ $scrollbar-color-active: rgba(68, 78, 96, 0.2);
10
+
11
+ .#{$namespace}-tele-fancy-scrollbar {
12
+ overscroll-behavior: contain;
13
+ overflow: auto;
14
+ overflow-y: scroll;
15
+ overflow-y: overlay; // for webkit
16
+ -webkit-overflow-scrolling: touch;
17
+ -ms-overflow-style: -ms-autohiding-scrollbar;
18
+ scrollbar-width: $scrollbar-ff-width;
19
+ // Firefox has incorrect track color
20
+ // scrollbar-color: $scrollbar-color $scrollbar-track-color;
21
+
22
+ &::-webkit-scrollbar {
23
+ height: $scrollbar-size;
24
+ width: $scrollbar-size;
25
+ }
26
+
27
+ &::-webkit-scrollbar-track {
28
+ background-color: $scrollbar-track-color;
29
+ }
30
+
31
+ &::-webkit-scrollbar-thumb {
32
+ background-color: $scrollbar-color;
33
+ background-color: $scrollbar-track-color;
34
+ border-radius: $scrollbar-size * 0.5;
35
+ transition: background-color 0.4s;
36
+ }
37
+
38
+ &:hover::-webkit-scrollbar-thumb {
39
+ background-color: $scrollbar-color;
40
+ }
41
+
42
+ &::-webkit-scrollbar-thumb:hover {
43
+ background-color: $scrollbar-color-hover;
44
+ }
45
+
46
+ &::-webkit-scrollbar-thumb:active {
47
+ background-color: $scrollbar-color-active;
48
+ }
49
+
50
+ &::-webkit-scrollbar-thumb:vertical {
51
+ min-height: $scrollbar-minlength;
52
+ }
53
+
54
+ &::-webkit-scrollbar-thumb:horizontal {
55
+ min-width: $scrollbar-minlength;
56
+ }
57
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/index.ts CHANGED
@@ -1,234 +1,245 @@
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";
26
11
 
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
- }
12
+ export { SlideViewer, refrigerator, addHooks };
45
13
 
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
- }
14
+ export const version = __APP_VERSION__;
55
15
 
56
- const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, Controller> = {
16
+ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
57
17
  kind: "Slide",
58
18
  setup(context) {
59
- console.log("[Slide] setup @ " + version);
60
-
61
- if (context.getIsWritable()) {
62
- context.storage.ensureState(EmptyAttributes);
63
- }
19
+ console.log("[Slide] setup @ " + __APP_VERSION__);
64
20
 
65
21
  if (!context.storage.state.taskId) {
66
22
  throw new Error("[Slide] no taskId");
67
23
  }
68
24
 
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
- }
25
+ const options = context.getAppOptions() || {};
26
+ const debug = Boolean(options.debug);
27
+ const logger = make_logger(context);
28
+ logger.info("[Slide] setup @ " + __APP_VERSION__);
82
29
 
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),
30
+ const sideEffect = new SideEffectManager();
31
+
32
+ // Create UI
33
+ const viewer = new SlideViewer({
34
+ interactive: true,
35
+ mode: "interactive",
36
+ resize: true,
37
+ enableGlobalClick: true,
38
+ controller: debug,
39
+ timestamp: make_timestamp(context),
40
+ logger,
41
+ renderOptions: {
42
+ minFPS: options.minFPS || 25,
43
+ maxFPS: options.maxFPS || 30,
44
+ autoFPS: options.autoFPS ?? true,
45
+ autoResolution: options.autoResolution ?? true,
46
+ resolution: options.resolution,
47
+ transactionBgColor: options.bgColor || make_bg_color(context.box.$content),
48
+ },
49
+
50
+ taskId: context.storage.state.taskId,
51
+ url: context.storage.state.url,
131
52
  });
132
53
 
54
+ // For debugging.
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ (viewer as any).context = context;
57
+
58
+ sideEffect.addDisposer(refrigerator.set(context.appId, viewer));
59
+
133
60
  if (import.meta.env.DEV) {
134
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
- (window as any).slideDoc = docsViewer;
62
+ (window as any).slideViewer = viewer;
136
63
  }
137
64
 
138
- const room = context.getRoom();
139
- const sideEffect = new SideEffectManager();
65
+ // Mount UI
66
+ context.box.mountStyles(SlideViewer.styles);
67
+ context.box.mountStage(viewer.$slide);
68
+ context.box.mountFooter(viewer.$footer);
69
+ context.box.mountContent(viewer.$content);
70
+ sideEffect.addDisposer(() => viewer.destroy());
140
71
 
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);
72
+ // Connect UI with Netless App
73
+ connect({ context, viewer, sideEffect, logger });
74
+
75
+ onUnmount(context, () => {
76
+ logger.info("[Slide] destroy");
77
+ sideEffect.flushAll();
146
78
  });
79
+ },
80
+ };
147
81
 
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
- });
82
+ export default SlideApp;
83
+
84
+ export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
85
+ const { container } = options;
86
+ container.style.cssText = "display:flex;flex-direction:column";
87
+ const viewer = new SlideViewer(options);
88
+ container.appendChild(document.createElement("style")).textContent = SlideViewer.styles;
89
+ container.appendChild(viewer.$content);
90
+ container.appendChild(viewer.$footer);
91
+ viewer.prepare(() => viewer.slide.renderSlide(1));
92
+ return viewer;
93
+ }
94
+
95
+ function make_timestamp(context: AppContext): () => number {
96
+ const room = context.room;
97
+ const player = context.displayer as Player;
98
+ if (room) {
99
+ return () => room.calibrationTimestamp;
100
+ } else if (player) {
101
+ return () => player.beginTimestamp + player.progressTime;
102
+ } else {
103
+ return () => Date.now();
104
+ }
105
+ }
106
+
107
+ function make_logger(context: AppContext): { info: (msg: string) => void } {
108
+ if (import.meta.env.DEV) {
109
+ return { info: console.debug.bind(console) };
110
+ }
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ return (context.displayer as any).logger;
113
+ }
114
+
115
+ function make_bg_color(el: HTMLElement): string {
116
+ try {
117
+ let bg = window.getComputedStyle(el).backgroundColor;
118
+ if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
119
+ bg = hex(bg);
120
+ console.log("[Slide] guess bg color:", bg);
121
+ return bg;
122
+ }
123
+ if (el.parentElement) {
124
+ return make_bg_color(el.parentElement);
159
125
  }
126
+ } catch {
127
+ // ignored
128
+ }
129
+ return "#ffffff";
130
+ }
160
131
 
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
- });
132
+ // https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
133
+ function hex(color: string): string {
134
+ const result = ColorString.get(color);
135
+ if (result && result.model === "rgb") {
136
+ const args = result.value;
137
+
138
+ const integer =
139
+ ((Math.round(args[0]) & 0xff) << 16) +
140
+ ((Math.round(args[1]) & 0xff) << 8) +
141
+ (Math.round(args[2]) & 0xff);
142
+
143
+ const string = integer.toString(16);
144
+ return "#" + "000000".substring(string.length) + string;
145
+ } else {
146
+ return color;
147
+ }
148
+ }
170
149
 
171
- docsViewer.mount();
150
+ function onUnmount(context: AppContext, callback: () => void) {
151
+ context.emitter.on("destroy", callback);
152
+ }
172
153
 
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;
154
+ interface ConnectParams {
155
+ context: AppContext;
156
+ viewer: SlideViewer;
157
+ sideEffect: SideEffectManager;
158
+ logger: { info: (msg: string) => void };
159
+ }
160
+
161
+ function connect({ context, viewer, sideEffect, logger }: ConnectParams) {
162
+ const log = import.meta.env.DEV ? console.log.bind(console) : logger.info.bind(logger);
163
+
164
+ let whiteboard: WhiteBoardView | null = null;
165
+
166
+ viewer.prepare(({ width, height, slideCount }) => {
167
+ if (context.destroyed) return;
168
+ width && context.box.setStageRatio(height / width);
169
+ whiteboard = context.createWhiteBoardView(slideCount);
170
+ kick_start();
171
+ });
172
+
173
+ const { slide } = viewer;
174
+
175
+ function kick_start() {
176
+ if (context.storage.state.state) {
177
+ log("[Slide] restore", { ...context.storage.state.state });
178
+ slide.setSlideState(context.storage.state.state);
179
+ } else if (context.isAddApp) {
180
+ log("[Slide] kick start");
181
+ slide.renderSlide(1);
182
+ } else {
183
+ log("[Slide] wait for restore");
184
+ const timeoutID = sideEffect.add(() => {
185
+ const timer = setInterval(() => {
186
+ if (!context.storage.state.state) {
187
+ log("[Slide] timeout");
188
+ slide.renderSlide(1);
215
189
  }
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;
190
+ }, 15 * 1000);
191
+ return () => clearInterval(timer);
192
+ });
193
+ const disposerID = sideEffect.addDisposer(
194
+ context.storage.addStateChangedListener(() => {
195
+ if (context.storage.state.state) {
196
+ log("[Slide] restore", { ...context.storage.state.state });
197
+ slide.setSlideState(context.storage.state.state);
198
+ sideEffect.flush(disposerID);
199
+ sideEffect.flush(timeoutID);
226
200
  }
227
- }
228
- return false;
229
- },
230
- };
231
- },
232
- };
201
+ })
202
+ );
203
+ }
204
+ }
233
205
 
234
- export default SlideApp;
206
+ // setup sync event
207
+ slide.on("stateChange", () => {
208
+ if (context.isWritable) {
209
+ log("[Slide] save state", { ...slide.slideState });
210
+ context.storage.setState({ state: slide.slideState });
211
+ }
212
+ });
213
+ slide.on("syncDispatch", payload => {
214
+ if (context.isWritable) {
215
+ log("[Slide] dispatch", { ...payload });
216
+ context.dispatchMagixEvent("syncDispatch", payload);
217
+ }
218
+ });
219
+ sideEffect.addDisposer(
220
+ context.addMagixEventListener(
221
+ "syncDispatch",
222
+ ({ payload }) => {
223
+ log("[Slide] receive", { ...payload });
224
+ slide.emit("syncReceive", payload);
225
+ },
226
+ { fireSelfEventAfterCommit: true }
227
+ )
228
+ );
229
+
230
+ // save state
231
+ slide.on("slideChange", page => {
232
+ if (context.isWritable && whiteboard) {
233
+ log("[Slide] page to", page);
234
+ whiteboard.jumpPage(page - 1);
235
+ }
236
+ });
237
+
238
+ // handle ui events
239
+ viewer.setReadonly(!context.isWritable);
240
+ sideEffect.addDisposer(
241
+ context.emitter.on("writableChange", w => {
242
+ viewer.setReadonly(!w);
243
+ })
244
+ );
245
+ }