@netless/fastboard 0.0.11 → 0.1.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 (118) hide show
  1. package/LICENSE.txt +1 -1
  2. package/dist/index.js +426 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/index.mjs +393 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +16 -80
  7. package/src/base.ts +55 -0
  8. package/src/core.ts +307 -0
  9. package/src/emitter.ts +21 -0
  10. package/src/index.ts +73 -42
  11. package/src/{behaviors/register-apps.ts → register-apps.ts} +6 -14
  12. package/src/utils.ts +74 -0
  13. package/src/value.ts +74 -0
  14. package/README.md +0 -136
  15. package/dist/index.cjs.js +0 -14
  16. package/dist/index.cjs.js.map +0 -1
  17. package/dist/index.es.js +0 -2804
  18. package/dist/index.es.js.map +0 -1
  19. package/dist/svelte.cjs.js +0 -2
  20. package/dist/svelte.cjs.js.map +0 -1
  21. package/dist/svelte.es.js +0 -32
  22. package/dist/svelte.es.js.map +0 -1
  23. package/dist/vue.cjs.js +0 -2
  24. package/dist/vue.cjs.js.map +0 -1
  25. package/dist/vue.es.js +0 -43
  26. package/dist/vue.es.js.map +0 -1
  27. package/src/WhiteboardApp.ts +0 -146
  28. package/src/behaviors/style.ts +0 -17
  29. package/src/components/PageControl/PageControl.scss +0 -80
  30. package/src/components/PageControl/PageControl.tsx +0 -110
  31. package/src/components/PageControl/hooks.ts +0 -70
  32. package/src/components/PageControl/index.ts +0 -2
  33. package/src/components/PlayerControl/PlayerControl.scss +0 -145
  34. package/src/components/PlayerControl/PlayerControl.tsx +0 -157
  35. package/src/components/PlayerControl/components/Button.tsx +0 -55
  36. package/src/components/PlayerControl/hooks.ts +0 -88
  37. package/src/components/PlayerControl/icons/Loading.tsx +0 -13
  38. package/src/components/PlayerControl/icons/Pause.tsx +0 -13
  39. package/src/components/PlayerControl/icons/Play.tsx +0 -13
  40. package/src/components/PlayerControl/icons/index.ts +0 -10
  41. package/src/components/PlayerControl/index.ts +0 -2
  42. package/src/components/RedoUndo/RedoUndo.scss +0 -56
  43. package/src/components/RedoUndo/RedoUndo.tsx +0 -79
  44. package/src/components/RedoUndo/hooks.ts +0 -50
  45. package/src/components/RedoUndo/index.ts +0 -2
  46. package/src/components/Root.scss +0 -55
  47. package/src/components/Root.tsx +0 -65
  48. package/src/components/Toolbar/Content.tsx +0 -94
  49. package/src/components/Toolbar/Toolbar.scss +0 -281
  50. package/src/components/Toolbar/Toolbar.tsx +0 -132
  51. package/src/components/Toolbar/components/ApplianceButtons.tsx +0 -132
  52. package/src/components/Toolbar/components/AppsButton.tsx +0 -106
  53. package/src/components/Toolbar/components/Button.tsx +0 -54
  54. package/src/components/Toolbar/components/ColorBox.tsx +0 -56
  55. package/src/components/Toolbar/components/CutLine.tsx +0 -8
  56. package/src/components/Toolbar/components/Mask.tsx +0 -44
  57. package/src/components/Toolbar/components/PencilButton.tsx +0 -70
  58. package/src/components/Toolbar/components/ShapesButton.tsx +0 -143
  59. package/src/components/Toolbar/components/Slider.tsx +0 -27
  60. package/src/components/Toolbar/components/TextButton.tsx +0 -66
  61. package/src/components/Toolbar/components/UpDownButtons.tsx +0 -49
  62. package/src/components/Toolbar/components/assets/cocos.png +0 -0
  63. package/src/components/Toolbar/components/assets/collapsed.png +0 -0
  64. package/src/components/Toolbar/components/assets/countdown.png +0 -0
  65. package/src/components/Toolbar/components/assets/expanded.png +0 -0
  66. package/src/components/Toolbar/components/assets/geogebra.png +0 -0
  67. package/src/components/Toolbar/components/assets/vscode.png +0 -0
  68. package/src/components/Toolbar/const.ts +0 -32
  69. package/src/components/Toolbar/hooks.ts +0 -112
  70. package/src/components/Toolbar/icons/Apps.tsx +0 -16
  71. package/src/components/Toolbar/icons/Arrow.tsx +0 -16
  72. package/src/components/Toolbar/icons/Circle.tsx +0 -21
  73. package/src/components/Toolbar/icons/Clean.tsx +0 -16
  74. package/src/components/Toolbar/icons/Clicker.tsx +0 -19
  75. package/src/components/Toolbar/icons/Collapse.tsx +0 -17
  76. package/src/components/Toolbar/icons/Diamond.tsx +0 -17
  77. package/src/components/Toolbar/icons/Down.tsx +0 -17
  78. package/src/components/Toolbar/icons/Eraser.tsx +0 -16
  79. package/src/components/Toolbar/icons/Expand.tsx +0 -17
  80. package/src/components/Toolbar/icons/Line.tsx +0 -13
  81. package/src/components/Toolbar/icons/Pencil.tsx +0 -16
  82. package/src/components/Toolbar/icons/Rectangle.tsx +0 -13
  83. package/src/components/Toolbar/icons/Selector.tsx +0 -16
  84. package/src/components/Toolbar/icons/SpeechBalloon.tsx +0 -17
  85. package/src/components/Toolbar/icons/Star.tsx +0 -17
  86. package/src/components/Toolbar/icons/Text.tsx +0 -16
  87. package/src/components/Toolbar/icons/Triangle.tsx +0 -17
  88. package/src/components/Toolbar/icons/Up.tsx +0 -17
  89. package/src/components/Toolbar/icons/index.ts +0 -42
  90. package/src/components/Toolbar/index.ts +0 -2
  91. package/src/components/ZoomControl/ZoomControl.scss +0 -80
  92. package/src/components/ZoomControl/ZoomControl.tsx +0 -109
  93. package/src/components/ZoomControl/hooks.ts +0 -111
  94. package/src/components/ZoomControl/index.ts +0 -2
  95. package/src/components/hooks.ts +0 -80
  96. package/src/i18n/en.json +0 -31
  97. package/src/i18n/index.ts +0 -22
  98. package/src/i18n/zh-CN.json +0 -32
  99. package/src/icons/ChevronLeft.tsx +0 -21
  100. package/src/icons/ChevronRight.tsx +0 -21
  101. package/src/icons/FilePlus.tsx +0 -18
  102. package/src/icons/Minus.tsx +0 -21
  103. package/src/icons/Plus.tsx +0 -21
  104. package/src/icons/Redo.tsx +0 -24
  105. package/src/icons/Reset.tsx +0 -23
  106. package/src/icons/Undo.tsx +0 -24
  107. package/src/icons/index.tsx +0 -11
  108. package/src/internal/Instance.tsx +0 -275
  109. package/src/internal/helpers.ts +0 -86
  110. package/src/internal/hooks.ts +0 -9
  111. package/src/internal/index.ts +0 -3
  112. package/src/internal/mount-whiteboard.ts +0 -90
  113. package/src/react.tsx +0 -52
  114. package/src/style.scss +0 -35
  115. package/src/svelte.ts +0 -45
  116. package/src/theme/index.ts +0 -36
  117. package/src/types/index.ts +0 -22
  118. package/src/vue.ts +0 -74
package/src/core.ts ADDED
@@ -0,0 +1,307 @@
1
+ import type {
2
+ AnimationMode,
3
+ ApplianceNames,
4
+ Camera,
5
+ Color,
6
+ ConversionResponse,
7
+ MemberState,
8
+ Rectangle,
9
+ RoomState,
10
+ SceneDefinition,
11
+ ShapeType,
12
+ } from "white-web-sdk";
13
+
14
+ import { BuiltinApps } from "@netless/window-manager";
15
+ import { FastboardAppBase } from "./base";
16
+ import { convertedFileToScene, genUID, getImageSize, makeSlideParams } from "./utils";
17
+
18
+ export interface InsertDocsStatic {
19
+ readonly fileType: "pdf" | "ppt";
20
+ readonly scenePath: string;
21
+ readonly scenes: SceneDefinition[];
22
+ readonly title?: string;
23
+ }
24
+
25
+ export interface InsertDocsDynamic {
26
+ readonly fileType: "pptx";
27
+ readonly scenePath: string;
28
+ readonly taskId: string;
29
+ readonly title?: string;
30
+ readonly url?: string;
31
+ /** @example [{ name: '1' }, { name: '2' }, { name: '3' }] */
32
+ readonly scenes?: SceneDefinition[];
33
+ }
34
+
35
+ export interface InsertMediaParams {
36
+ title: string;
37
+ src: string;
38
+ }
39
+
40
+ export type InsertDocsParams = InsertDocsStatic | InsertDocsDynamic;
41
+
42
+ export type SetMemberStateFn = (partialMemberState: Partial<MemberState>) => void;
43
+
44
+ export type RoomStateChanged = (diff: Partial<RoomState>) => void;
45
+
46
+ export class FastboardApp extends FastboardAppBase {
47
+ /**
48
+ * Render this app to some DOM.
49
+ */
50
+ bindContainer(container: HTMLElement) {
51
+ this._assertNotDestroyed();
52
+ this.manager.bindContainer(container);
53
+ }
54
+
55
+ /**
56
+ * Move window-manager's collector to some place.
57
+ */
58
+ bindCollector(container: HTMLElement) {
59
+ this._assertNotDestroyed();
60
+ this.manager.bindCollectorContainer(container);
61
+ }
62
+
63
+ /**
64
+ * Is current room writable?
65
+ */
66
+ readonly writable = this.createValue(
67
+ this.room.isWritable,
68
+ set => this._addRoomListener("onEnableWriteNowChanged", () => set(this.room.isWritable)),
69
+ this.room.setWritable.bind(this.room)
70
+ );
71
+
72
+ /**
73
+ * Current window-manager's windows' state (is it maximized?).
74
+ */
75
+ readonly boxState = this.createValue(this.manager.boxState, set =>
76
+ this._addManagerListener("boxStateChange", set)
77
+ );
78
+
79
+ /**
80
+ * Current window-manager's focused app's id.
81
+ * @example "HelloWorld-1A2b3C4d"
82
+ */
83
+ readonly focusedApp = this.createValue(this.manager.focused, set =>
84
+ this._addManagerListener("focusedChange", set)
85
+ );
86
+
87
+ /**
88
+ * How many times can I call `app.redo()`?
89
+ */
90
+ readonly canRedoSteps = this.createValue(this.manager.mainView.canRedoSteps, set =>
91
+ this._addMainViewListener("onCanRedoStepsUpdate", set)
92
+ );
93
+
94
+ /**
95
+ * How many times can I call `app.undo()`?
96
+ */
97
+ readonly canUndoSteps = this.createValue(this.manager.mainView.canUndoSteps, set =>
98
+ this._addMainViewListener("onCanUndoStepsUpdate", set)
99
+ );
100
+
101
+ /**
102
+ * Current camera information of main view.
103
+ */
104
+ readonly camera = this.createValue(
105
+ this.manager.mainView.camera,
106
+ set => this._addMainViewListener("onCameraUpdated", set),
107
+ this.manager.moveCamera.bind(this.manager)
108
+ );
109
+
110
+ /**
111
+ * Current tool's info, like "is using pencil?", "what color?".
112
+ */
113
+ readonly memberState = this.createValue<MemberState, SetMemberStateFn>(
114
+ this.room.state.memberState,
115
+ set => this._addRoomListener<RoomStateChanged>("onRoomStateChanged", ({ memberState: m }) => m && set(m)),
116
+ this.manager.mainView.setMemberState.bind(this.manager.mainView)
117
+ );
118
+
119
+ /**
120
+ * Undo a step on main view.
121
+ */
122
+ undo() {
123
+ this._assertNotDestroyed();
124
+ this.manager.mainView.undo();
125
+ }
126
+
127
+ /**
128
+ * Redo a step on main view.
129
+ */
130
+ redo() {
131
+ this._assertNotDestroyed();
132
+ this.manager.mainView.redo();
133
+ }
134
+
135
+ /**
136
+ * Move current main view's camera position.
137
+ */
138
+ moveCamera(camera: Partial<Camera> & { animationMode?: AnimationMode | undefined }) {
139
+ this._assertNotDestroyed();
140
+ this.manager.moveCamera(camera);
141
+ }
142
+
143
+ /**
144
+ * Move current main view's camera to include a rectangle.
145
+ */
146
+ moveCameraToContain(rectangle: Rectangle & { animationMode?: AnimationMode }) {
147
+ this._assertNotDestroyed();
148
+ this.manager.moveCameraToContain(rectangle);
149
+ }
150
+
151
+ /**
152
+ * Delete all things on the main view.
153
+ */
154
+ cleanCurrentScene() {
155
+ this._assertNotDestroyed();
156
+ this.manager.mainView.cleanCurrentScene();
157
+ }
158
+
159
+ /**
160
+ * Set current tool, like "pencil".
161
+ */
162
+ setAppliance(appliance: ApplianceNames, shape?: ShapeType) {
163
+ this._assertNotDestroyed();
164
+ this.manager.mainView.setMemberState({ currentApplianceName: appliance, shapeType: shape });
165
+ }
166
+
167
+ setStrokeWidth(strokeWidth: number) {
168
+ this._assertNotDestroyed();
169
+ this.manager.mainView.setMemberState({ strokeWidth });
170
+ }
171
+
172
+ setStrokeColor(strokeColor: Color) {
173
+ this._assertNotDestroyed();
174
+ this.manager.mainView.setMemberState({ strokeColor });
175
+ }
176
+
177
+ /**
178
+ * Insert an image to the main view.
179
+ */
180
+ async insertImage(url: string) {
181
+ this._assertNotDestroyed();
182
+ await this.manager.switchMainViewToWriter();
183
+
184
+ const { divElement } = this.manager.mainView;
185
+ const containerSize = {
186
+ width: divElement?.scrollWidth || window.innerWidth,
187
+ height: divElement?.scrollHeight || window.innerHeight,
188
+ };
189
+
190
+ // 1. shrink the image a little to fit container **width**
191
+ const maxWidth = containerSize.width * 0.8;
192
+ let { width, height } = await getImageSize(url, containerSize);
193
+ const scale = Math.min(maxWidth / width, 1);
194
+ const uuid = genUID();
195
+ const { centerX, centerY } = this.manager.camera;
196
+ width *= scale;
197
+ height *= scale;
198
+ this.manager.mainView.insertImage({ uuid, centerX, centerY, width, height, locked: false });
199
+ this.manager.mainView.completeImageUpload(uuid, url);
200
+
201
+ // 2. move camera to fit image **height**
202
+ width /= 0.8;
203
+ height /= 0.8;
204
+ const originX = centerX - width / 2;
205
+ const originY = centerY - height / 2;
206
+ this.manager.moveCameraToContain({ originX, originY, width, height });
207
+ }
208
+
209
+ /**
210
+ * Insert PDF/PPTX from conversion result.
211
+ * @param status https://developer.netless.link/server-en/home/server-conversion#get-query-task-conversion-progress
212
+ */
213
+ insertDocs(filename: string, status: ConversionResponse): Promise<string | undefined>;
214
+
215
+ /**
216
+ * Manual way.
217
+ * @example
218
+ * app.insertDocs({
219
+ * fileType: 'pptx',
220
+ * scenePath: `/pptx/${conversion.taskId}`,
221
+ * taskId: conversion.taskId,
222
+ * title: 'Title',
223
+ * })
224
+ */
225
+ insertDocs(params: InsertDocsParams): Promise<string | undefined>;
226
+
227
+ insertDocs(arg1: string | InsertDocsParams, arg2?: ConversionResponse) {
228
+ if (typeof arg1 === "object" && "fileType" in arg1) {
229
+ return this._insertDocsImpl(arg1);
230
+ } else if (arg2 && arg2.status !== "Finished") {
231
+ throw new Error("[FastboardApp] Can not insert a converting doc.");
232
+ } else if (arg2 && arg2.progress) {
233
+ const scenes: SceneDefinition[] = arg2.progress.convertedFileList.map(convertedFileToScene);
234
+ const scenePath = `/${arg2.uuid}/${genUID()}`;
235
+ const { emptyScenes, taskId, url } = makeSlideParams(scenes);
236
+ if (taskId && url) {
237
+ const title = arg1;
238
+ return this._insertDocsImpl({ fileType: "pptx", scenePath, taskId, title, url, scenes: emptyScenes });
239
+ } else {
240
+ return this._insertDocsImpl({ fileType: "pdf", scenePath, scenes, title: arg1 });
241
+ }
242
+ }
243
+ }
244
+
245
+ private _insertDocsImpl({ fileType, scenePath, title, scenes, ...attributes }: InsertDocsParams) {
246
+ this._assertNotDestroyed();
247
+ switch (fileType) {
248
+ case "pdf":
249
+ case "ppt":
250
+ return this.manager.addApp({
251
+ kind: "DocsViewer",
252
+ options: { scenePath, title, scenes },
253
+ });
254
+ case "pptx":
255
+ return this.manager.addApp({
256
+ kind: "Slide",
257
+ options: { scenePath, title, scenes },
258
+ attributes,
259
+ });
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Insert the Monaco Code Editor app.
265
+ */
266
+ insertCodeEditor() {
267
+ this._assertNotDestroyed();
268
+ return this.manager.addApp({
269
+ kind: "Monaco",
270
+ options: { title: "Code Editor" },
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Insert the Countdown app.
276
+ */
277
+ insertCountdown() {
278
+ this._assertNotDestroyed();
279
+ return this.manager.addApp({
280
+ kind: "Countdown",
281
+ options: { title: "Countdown" },
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Insert the Media Player app.
287
+ */
288
+ insertMedia({ title, src }: InsertMediaParams) {
289
+ this._assertNotDestroyed();
290
+ return this.manager.addApp({
291
+ kind: BuiltinApps.MediaPlayer,
292
+ options: { title },
293
+ attributes: { src },
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Insert the GeoGebra app.
299
+ */
300
+ insertGeoGebra() {
301
+ this._assertNotDestroyed();
302
+ return this.manager.addApp({
303
+ kind: "GeoGebra",
304
+ options: { title: "GeoGebra" },
305
+ });
306
+ }
307
+ }
package/src/emitter.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type FastboardListener<T> = (event: T) => void;
2
+
3
+ export class FastboardEmitter<T> {
4
+ listeners = new Set<FastboardListener<T>>();
5
+
6
+ get length(): number {
7
+ return this.listeners.size;
8
+ }
9
+
10
+ dispatch(message: T) {
11
+ this.listeners.forEach(callback => callback(message));
12
+ }
13
+
14
+ addListener(listener: FastboardListener<T>) {
15
+ this.listeners.add(listener);
16
+ }
17
+
18
+ removeListener(listener: FastboardListener<T>) {
19
+ this.listeners.delete(listener);
20
+ }
21
+ }
package/src/index.ts CHANGED
@@ -1,49 +1,80 @@
1
+ import type { HotKeys, JoinRoomParams, RoomCallbacks, WhiteWebSdkConfiguration } from "white-web-sdk";
2
+ import type { MountParams } from "@netless/window-manager";
3
+
4
+ import { DefaultHotKeys, WhiteWebSdk } from "white-web-sdk";
1
5
  import { WindowManager } from "@netless/window-manager";
2
6
 
3
- import "./behaviors/register-apps";
4
- import "./behaviors/style";
5
-
6
- import { WhiteboardApp, type WhiteboardAppConfig } from "./WhiteboardApp";
7
-
8
- export { version } from "../package.json";
9
- export {
10
- PageControl,
11
- usePageControl,
12
- type PageControlProps,
13
- } from "./components/PageControl";
14
- export {
15
- RedoUndo,
16
- useRedoUndo,
17
- type RedoUndoProps,
18
- } from "./components/RedoUndo";
19
- export { Toolbar, useToolbar, type ToolbarProps } from "./components/Toolbar";
20
- export {
21
- ZoomControl,
22
- useZoomControl,
23
- type ZoomControlProps,
24
- } from "./components/ZoomControl";
25
- export {
26
- PlayerControl,
27
- usePlayerControl,
28
- type PlayerControlProps,
29
- } from "./components/PlayerControl";
30
- export {};
31
-
32
- export * from "./WhiteboardApp";
33
- export * from "./react";
34
-
35
- export const register = WindowManager.register.bind(WindowManager);
7
+ import "./register-apps";
8
+ import { FastboardApp } from "./core";
9
+ import { ensureWindowManager } from "./utils";
10
+
11
+ export type { FastboardReadable, FastboardWritable } from "./value";
12
+
13
+ export type { FastboardApp };
14
+
15
+ export interface FastboardOptions {
16
+ sdkConfig: Omit<WhiteWebSdkConfiguration, "useMobXState">;
17
+ joinRoom: Omit<JoinRoomParams, "useMultiViews" | "disableNewPencil" | "disableMagixEventDispatchLimit"> & {
18
+ callbacks?: Partial<RoomCallbacks>;
19
+ };
20
+ managerConfig?: Omit<MountParams, "room">;
21
+ }
36
22
 
37
23
  /**
24
+ * Create a FastboardApp instance.
38
25
  * @example
39
- * let app = await createWhiteboardApp(config)
40
- * app.bindElement(el)
26
+ * let app = await createFastboard({
27
+ * sdkConfig: {
28
+ * appIdentifier: import.meta.env.VITE_APPID,
29
+ * },
30
+ * joinRoom: {
31
+ * uid: unique_id,
32
+ * uuid: import.meta.env.VITE_ROOM_UUID,
33
+ * roomToken: import.meta.env.VITE_ROOM_TOKEN,
34
+ * },
35
+ * })
41
36
  */
42
- export async function createWhiteboardApp(
43
- config: WhiteboardAppConfig
44
- ): Promise<WhiteboardApp> {
45
- const app = new WhiteboardApp(config);
46
- // @ts-expect-error // eslint-disable-line
47
- await app._instance.readyPromise;
48
- return app;
37
+ export async function createFastboard({
38
+ sdkConfig,
39
+ joinRoom: { callbacks, ...joinRoomParams },
40
+ managerConfig,
41
+ }: FastboardOptions) {
42
+ const sdk = new WhiteWebSdk({
43
+ ...sdkConfig,
44
+ useMobXState: true,
45
+ });
46
+
47
+ const hotKeys: Partial<HotKeys> = {
48
+ ...DefaultHotKeys,
49
+ changeToSelector: "s",
50
+ changeToLaserPointer: "z",
51
+ changeToPencil: "p",
52
+ changeToRectangle: "r",
53
+ changeToEllipse: "c",
54
+ changeToEraser: "e",
55
+ changeToText: "t",
56
+ changeToStraight: "l",
57
+ changeToArrow: "a",
58
+ changeToHand: "h",
59
+ };
60
+
61
+ const room = await sdk.joinRoom(
62
+ {
63
+ floatBar: true,
64
+ hotKeys,
65
+ ...ensureWindowManager(joinRoomParams),
66
+ useMultiViews: true,
67
+ disableNewPencil: false,
68
+ disableMagixEventDispatchLimit: true,
69
+ },
70
+ callbacks
71
+ );
72
+
73
+ const manager = await WindowManager.mount({
74
+ cursor: true,
75
+ ...managerConfig,
76
+ room,
77
+ });
78
+
79
+ return new FastboardApp(sdk, room, manager, hotKeys);
49
80
  }
@@ -14,26 +14,18 @@ WindowManager.register({
14
14
 
15
15
  WindowManager.register({
16
16
  kind: "Monaco",
17
- src: async () => {
18
- const app = await import("@netless/app-monaco");
19
- return app.default ?? app;
20
- },
17
+ src: "https://cdn.jsdelivr.net/npm/@netless/app-monaco@latest/dist/main.iife.js",
21
18
  });
19
+
22
20
  WindowManager.register({
23
21
  kind: "Countdown",
24
- src: async () => {
25
- const app = await import("@netless/app-countdown");
26
- return app.default ?? app;
27
- },
22
+ src: "https://cdn.jsdelivr.net/npm/@netless/app-countdown@latest/dist/main.iife.js",
28
23
  });
24
+
29
25
  WindowManager.register({
30
26
  kind: "GeoGebra",
31
- src: async () => {
32
- const app = await import("@netless/app-geogebra");
33
- return app.default ?? app;
34
- },
27
+ src: "https://cdn.jsdelivr.net/npm/@netless/app-monaco@geogebra/dist/main.iife.js",
35
28
  appOptions: {
36
- HTML5Codebase:
37
- "https://flat-storage-cn-hz.whiteboard.agora.io/GeoGebra/HTML5/5.0/web3d",
29
+ HTML5Codebase: "https://flat-storage-cn-hz.whiteboard.agora.io/GeoGebra/HTML5/5.0/web3d",
38
30
  },
39
31
  });
package/src/utils.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { ConvertedFile, JoinRoomParams, SceneDefinition, Size } from "white-web-sdk";
2
+ import { WindowManager } from "@netless/window-manager";
3
+
4
+ export function noop() {
5
+ /* noop */
6
+ }
7
+
8
+ export function getImageSize(url: string, fallback: Size) {
9
+ return new Promise<Size>(resolve => {
10
+ const img = new Image();
11
+ img.onload = () => resolve(img);
12
+ img.onerror = () => resolve(fallback);
13
+ img.src = url;
14
+ });
15
+ }
16
+
17
+ export function makeSlideParams(scenes: SceneDefinition[]) {
18
+ const emptyScenes: SceneDefinition[] = [];
19
+ let taskId = "";
20
+ let url = "";
21
+
22
+ // e.g. "ppt(x)://cdn/prefix/dynamicConvert/{taskId}/1.slide"
23
+ const pptSrcRE = /^pptx?(?<prefix>:\/\/\S+?dynamicConvert)\/(?<taskId>\w+)\//;
24
+
25
+ for (const { name, ppt } of scenes) {
26
+ // make sure scenesWithoutPPT.length === scenes.length
27
+ emptyScenes.push({ name });
28
+
29
+ if (!ppt || !ppt.src.startsWith("ppt")) {
30
+ continue;
31
+ }
32
+ const match = pptSrcRE.exec(ppt.src);
33
+ if (!match || !match.groups) {
34
+ continue;
35
+ }
36
+ taskId = match.groups.taskId;
37
+ url = "https" + match.groups.prefix;
38
+ break;
39
+ }
40
+
41
+ return { emptyScenes, taskId, url };
42
+ }
43
+
44
+ export function convertedFileToScene(f: ConvertedFile, i: number) {
45
+ return {
46
+ name: String(i + 1),
47
+ ppt: {
48
+ src: f.conversionFileUrl,
49
+ width: f.width,
50
+ height: f.height,
51
+ previewURL: f.preview,
52
+ },
53
+ };
54
+ }
55
+
56
+ export function ensureWindowManager(joinRoom: JoinRoomParams) {
57
+ if (!joinRoom.invisiblePlugins || !joinRoom.invisiblePlugins.includes(WindowManager)) {
58
+ joinRoom.invisiblePlugins = [...(joinRoom.invisiblePlugins || []), WindowManager];
59
+ }
60
+ return joinRoom;
61
+ }
62
+
63
+ // Copy from https://github.com/crimx/side-effect-manager/blob/main/src/gen-uid.ts
64
+ const SOUP = "!#%()*+,-./:;=?@[]^_`{|}~" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
65
+ const SOUP_LEN = SOUP.length;
66
+ const ID_LEN = 20;
67
+ const reusedIdCarrier = /* @__PURE__ */ Array(ID_LEN);
68
+
69
+ export function genUID() {
70
+ for (let i = 0; i < ID_LEN; i++) {
71
+ reusedIdCarrier[i] = SOUP.charAt(Math.random() * SOUP_LEN);
72
+ }
73
+ return reusedIdCarrier.join("");
74
+ }
package/src/value.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { FastboardEmitter } from "./emitter";
2
+ import { noop } from "./utils";
3
+
4
+ export type FastboardDisposer = () => void;
5
+
6
+ export interface FastboardReadable<T> {
7
+ readonly value: T;
8
+ subscribe(callback: (value: T) => void): FastboardDisposer;
9
+ reaction(callback: (value: T) => void): FastboardDisposer;
10
+ }
11
+
12
+ export interface FastboardWritable<T, SetFn = (value: T) => void> extends FastboardReadable<T> {
13
+ setValue: SetFn;
14
+ }
15
+
16
+ export interface FastboardInternalValue<T> extends FastboardWritable<T> {
17
+ dispose: FastboardDisposer;
18
+ }
19
+
20
+ /**
21
+ * Create a readonly, reactive value.
22
+ * @example
23
+ * createValue(manager.getMainViewSceneIndex(), (set) => {
24
+ * manager.emitter.on("mainViewSceneIndexChanged", set)
25
+ * return () => manager.emitter.off("mainViewSceneIndexChanged", set)
26
+ * })
27
+ */
28
+ export function createValue<T>(
29
+ value: T,
30
+ effect: (set: (value: T) => void) => FastboardDisposer | void
31
+ ): FastboardReadable<T>;
32
+
33
+ /**
34
+ * Create a writable, reactive value.
35
+ * @example
36
+ * createValue(manager.getMainViewSceneIndex(), (set) => {
37
+ * manager.emitter.on("mainViewSceneIndexChanged", set)
38
+ * return () => manager.emitter.off("mainViewSceneIndexChanged", set)
39
+ * }, (newValue) => {
40
+ * manager.setMainViewSceneIndex(newValue)
41
+ * })
42
+ */
43
+ export function createValue<T, SetFn = (value: T) => void>(
44
+ value: T,
45
+ effect: (set: (value: T) => void) => FastboardDisposer | void,
46
+ set: (value: T) => void
47
+ ): FastboardWritable<T, SetFn>;
48
+
49
+ export function createValue<T>(
50
+ value: T,
51
+ effect: (set: (value: T) => void) => FastboardDisposer | void,
52
+ setValue: (value: T) => void = noop
53
+ ): FastboardInternalValue<T> {
54
+ const emitter = new FastboardEmitter<T>();
55
+
56
+ function set(newValue: T) {
57
+ emitter.dispatch((value = newValue));
58
+ }
59
+
60
+ const dispose = effect(set) || noop;
61
+
62
+ function subscribe(callback: (value: T) => void) {
63
+ emitter.addListener(callback);
64
+ callback(value);
65
+ return () => emitter.removeListener(callback);
66
+ }
67
+
68
+ function reaction(callback: (value: T) => void) {
69
+ emitter.addListener(callback);
70
+ return () => emitter.removeListener(callback);
71
+ }
72
+
73
+ return { value, subscribe, reaction, setValue, dispose };
74
+ }