@netless/fastboard 0.0.8 → 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 (107) 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 -71
  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 +70 -24
  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 -134
  15. package/dist/index.cjs.js +0 -14
  16. package/dist/index.cjs.js.map +0 -1
  17. package/dist/index.es.js +0 -2538
  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 -31
  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 -42
  26. package/dist/vue.es.js.map +0 -1
  27. package/src/WhiteboardApp.ts +0 -80
  28. package/src/behaviors/style.ts +0 -17
  29. package/src/components/PageControl.scss +0 -80
  30. package/src/components/PageControl.tsx +0 -181
  31. package/src/components/PlayerControl/PlayerControl.scss +0 -145
  32. package/src/components/PlayerControl/PlayerControl.tsx +0 -158
  33. package/src/components/PlayerControl/components/Button.tsx +0 -55
  34. package/src/components/PlayerControl/hooks.ts +0 -95
  35. package/src/components/PlayerControl/icons/Loading.tsx +0 -13
  36. package/src/components/PlayerControl/icons/Pause.tsx +0 -13
  37. package/src/components/PlayerControl/icons/Play.tsx +0 -13
  38. package/src/components/PlayerControl/icons/index.ts +0 -10
  39. package/src/components/PlayerControl/index.ts +0 -1
  40. package/src/components/RedoUndo.scss +0 -56
  41. package/src/components/RedoUndo.tsx +0 -95
  42. package/src/components/Root.scss +0 -55
  43. package/src/components/Root.tsx +0 -61
  44. package/src/components/Toolbar/Content.tsx +0 -93
  45. package/src/components/Toolbar/Toolbar.scss +0 -247
  46. package/src/components/Toolbar/Toolbar.tsx +0 -82
  47. package/src/components/Toolbar/components/ApplianceButtons.tsx +0 -132
  48. package/src/components/Toolbar/components/AppsButton.tsx +0 -106
  49. package/src/components/Toolbar/components/Button.tsx +0 -54
  50. package/src/components/Toolbar/components/ColorBox.tsx +0 -56
  51. package/src/components/Toolbar/components/CutLine.tsx +0 -8
  52. package/src/components/Toolbar/components/PencilButton.tsx +0 -70
  53. package/src/components/Toolbar/components/ShapesButton.tsx +0 -143
  54. package/src/components/Toolbar/components/Slider.tsx +0 -27
  55. package/src/components/Toolbar/components/TextButton.tsx +0 -66
  56. package/src/components/Toolbar/components/UpDownButtons.tsx +0 -49
  57. package/src/components/Toolbar/components/assets/cocos.png +0 -0
  58. package/src/components/Toolbar/components/assets/countdown.png +0 -0
  59. package/src/components/Toolbar/components/assets/geogebra.png +0 -0
  60. package/src/components/Toolbar/components/assets/vscode.png +0 -0
  61. package/src/components/Toolbar/const.ts +0 -32
  62. package/src/components/Toolbar/hooks.ts +0 -113
  63. package/src/components/Toolbar/icons/Apps.tsx +0 -16
  64. package/src/components/Toolbar/icons/Arrow.tsx +0 -16
  65. package/src/components/Toolbar/icons/Circle.tsx +0 -21
  66. package/src/components/Toolbar/icons/Clean.tsx +0 -16
  67. package/src/components/Toolbar/icons/Clicker.tsx +0 -19
  68. package/src/components/Toolbar/icons/Collapse.tsx +0 -17
  69. package/src/components/Toolbar/icons/Diamond.tsx +0 -17
  70. package/src/components/Toolbar/icons/Down.tsx +0 -17
  71. package/src/components/Toolbar/icons/Eraser.tsx +0 -16
  72. package/src/components/Toolbar/icons/Expand.tsx +0 -17
  73. package/src/components/Toolbar/icons/Line.tsx +0 -13
  74. package/src/components/Toolbar/icons/Pencil.tsx +0 -16
  75. package/src/components/Toolbar/icons/Rectangle.tsx +0 -13
  76. package/src/components/Toolbar/icons/Selector.tsx +0 -16
  77. package/src/components/Toolbar/icons/SpeechBalloon.tsx +0 -17
  78. package/src/components/Toolbar/icons/Star.tsx +0 -17
  79. package/src/components/Toolbar/icons/Text.tsx +0 -16
  80. package/src/components/Toolbar/icons/Triangle.tsx +0 -17
  81. package/src/components/Toolbar/icons/Up.tsx +0 -17
  82. package/src/components/Toolbar/icons/index.ts +0 -42
  83. package/src/components/Toolbar/index.ts +0 -1
  84. package/src/components/ZoomControl.scss +0 -80
  85. package/src/components/ZoomControl.tsx +0 -221
  86. package/src/hooks.ts +0 -53
  87. package/src/i18n/en.json +0 -31
  88. package/src/i18n/index.ts +0 -22
  89. package/src/i18n/zh-CN.json +0 -32
  90. package/src/icons/ChevronLeft.tsx +0 -21
  91. package/src/icons/ChevronRight.tsx +0 -21
  92. package/src/icons/FilePlus.tsx +0 -18
  93. package/src/icons/Minus.tsx +0 -21
  94. package/src/icons/Plus.tsx +0 -21
  95. package/src/icons/Redo.tsx +0 -24
  96. package/src/icons/Reset.tsx +0 -23
  97. package/src/icons/Undo.tsx +0 -24
  98. package/src/icons/index.tsx +0 -11
  99. package/src/internal/Instance.tsx +0 -251
  100. package/src/internal/helpers.ts +0 -42
  101. package/src/internal/index.ts +0 -3
  102. package/src/internal/mount-whiteboard.ts +0 -90
  103. package/src/style.scss +0 -29
  104. package/src/svelte.ts +0 -45
  105. package/src/theme/index.ts +0 -36
  106. package/src/types/index.ts +0 -22
  107. 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,34 +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";
7
+ import "./register-apps";
8
+ import { FastboardApp } from "./core";
9
+ import { ensureWindowManager } from "./utils";
5
10
 
6
- import { WhiteboardApp, type WhiteboardAppConfig } from "./WhiteboardApp";
11
+ export type { FastboardReadable, FastboardWritable } from "./value";
7
12
 
8
- export { version } from "../package.json";
9
- export { PageControl, type PageControlProps } from "./components/PageControl";
10
- export { RedoUndo, type RedoUndoProps } from "./components/RedoUndo";
11
- export { Toolbar, type ToolbarProps } from "./components/Toolbar";
12
- export { ZoomControl, type ZoomControlProps } from "./components/ZoomControl";
13
- export {
14
- PlayerControl,
15
- type PlayerControlProps,
16
- } from "./components/PlayerControl";
17
- export * from "./WhiteboardApp";
18
- export * from "./hooks";
13
+ export type { FastboardApp };
19
14
 
20
- export const register = WindowManager.register.bind(WindowManager);
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
+ }
21
22
 
22
23
  /**
24
+ * Create a FastboardApp instance.
23
25
  * @example
24
- * let app = await createWhiteboardApp(config)
25
- * 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
+ * })
26
36
  */
27
- export async function createWhiteboardApp(
28
- config: WhiteboardAppConfig
29
- ): Promise<WhiteboardApp> {
30
- const app = new WhiteboardApp(config);
31
- // @ts-expect-error // eslint-disable-line
32
- await app._instance.readyPromise;
33
- 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);
34
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
+ }