@netless/fastboard 0.0.4 → 0.0.8

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.
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var a=require("svelte/store"),n=require("./index.cjs.js");require("@netless/window-manager");require("white-web-sdk");require("i18next");require("react");require("react-dom");require("clsx");require("@tippyjs/react");require("rc-slider");function o(t,i){return a.readable(null,s=>{let e=null;n.createWhiteboardApp(i).then(r=>{s(e=r)});const u=t.subscribe(r=>{r&&e&&e.bindElement(r)});return()=>{u(),e&&e.dispose()}})}exports.useFastboard=o;
2
+ //# sourceMappingURL=svelte.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte.cjs.js","sources":["../src/svelte.ts"],"sourcesContent":["import { readable, type Readable } from \"svelte/store\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\n/**\n * @example\n * ```svelte\n * <script>\n * let ref = writable(null)\n * let app = useFastboard(ref, { sdkConfig, joinRoom })\n * if (app) {\n * app.insertDocs({...})\n * }\n * </script>\n * <div style=\"width: 100%; height: 100%\" bind:this={$ref} />\n * ```\n */\nexport function useFastboard(\n ref: Readable<HTMLElement | null>,\n config: FastBoardConfig\n): Readable<WhiteboardApp | null> {\n return readable<WhiteboardApp | null>(null, set => {\n let app_: WhiteboardApp | null = null;\n\n createWhiteboardApp(config).then(app => {\n set((app_ = app));\n });\n\n const dispose = ref.subscribe(div => {\n if (div && app_) {\n app_.bindElement(div);\n }\n });\n\n return () => {\n dispose();\n if (app_) {\n app_.dispose();\n }\n };\n });\n}\n"],"names":["readable"],"mappings":"kWAqBE,EACA,EACgC,OACzBA,YAA+B,KAAM,GAAO,IAC7C,GAA6B,2BAEb,GAAQ,KAAK,GAAO,GACjC,EAAO,UAGR,GAAU,EAAI,UAAU,GAAO,CAC/B,GAAO,KACJ,YAAY,WAId,IAAM,KAEP,KACG"}
@@ -0,0 +1,31 @@
1
+ import { readable } from "svelte/store";
2
+ import { createWhiteboardApp } from "./index.es.js";
3
+ import "@netless/window-manager";
4
+ import "white-web-sdk";
5
+ import "i18next";
6
+ import "react";
7
+ import "react-dom";
8
+ import "clsx";
9
+ import "@tippyjs/react";
10
+ import "rc-slider";
11
+ function useFastboard(ref, config) {
12
+ return readable(null, (set) => {
13
+ let app_ = null;
14
+ createWhiteboardApp(config).then((app) => {
15
+ set(app_ = app);
16
+ });
17
+ const dispose = ref.subscribe((div) => {
18
+ if (div && app_) {
19
+ app_.bindElement(div);
20
+ }
21
+ });
22
+ return () => {
23
+ dispose();
24
+ if (app_) {
25
+ app_.dispose();
26
+ }
27
+ };
28
+ });
29
+ }
30
+ export { useFastboard };
31
+ //# sourceMappingURL=svelte.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte.es.js","sources":["../src/svelte.ts"],"sourcesContent":["import { readable, type Readable } from \"svelte/store\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\n/**\n * @example\n * ```svelte\n * <script>\n * let ref = writable(null)\n * let app = useFastboard(ref, { sdkConfig, joinRoom })\n * if (app) {\n * app.insertDocs({...})\n * }\n * </script>\n * <div style=\"width: 100%; height: 100%\" bind:this={$ref} />\n * ```\n */\nexport function useFastboard(\n ref: Readable<HTMLElement | null>,\n config: FastBoardConfig\n): Readable<WhiteboardApp | null> {\n return readable<WhiteboardApp | null>(null, set => {\n let app_: WhiteboardApp | null = null;\n\n createWhiteboardApp(config).then(app => {\n set((app_ = app));\n });\n\n const dispose = ref.subscribe(div => {\n if (div && app_) {\n app_.bindElement(div);\n }\n });\n\n return () => {\n dispose();\n if (app_) {\n app_.dispose();\n }\n };\n });\n}\n"],"names":[],"mappings":";;;;;;;;;;sBAqBE,KACA,QACgC;SACzB,SAA+B,MAAM,SAAO;QAC7C,OAA6B;wBAEb,QAAQ,KAAK,SAAO;UACjC,OAAO;AAAA;UAGR,UAAU,IAAI,UAAU,SAAO;UAC/B,OAAO,MAAM;aACV,YAAY;AAAA;AAAA;WAId,MAAM;;UAEP,MAAM;aACH;AAAA;AAAA;AAAA;AAAA;;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var t=require("vue-demi"),s=require("./index.cjs.js");require("@netless/window-manager");require("white-web-sdk");require("i18next");require("react");require("react-dom");require("clsx");require("@tippyjs/react");require("rc-slider");function n(u){var e;const r=t.unref(u);return(e=r==null?void 0:r.$el)!=null?e:r}function o(u){return t.getCurrentScope()?(t.onScopeDispose(u),!0):!1}function l(u,r){const e=t.shallowRef(null),i=t.computed(()=>n(u));return s.createWhiteboardApp(r).then(a=>{e.value=a}),t.watchEffect(()=>{i.value&&e.value&&e.value.bindElement(i.value)}),o(()=>{e.value&&e.value.dispose()}),e}exports.unrefElement=n;exports.useFastboard=l;
2
+ //# sourceMappingURL=vue.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.cjs.js","sources":["../src/vue.ts"],"sourcesContent":["import type { ComponentPublicInstance, Ref } from \"vue-demi\";\nimport {\n computed,\n getCurrentScope,\n onScopeDispose,\n shallowRef,\n unref,\n watchEffect,\n} from \"vue-demi\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\nexport type MaybeRef<T> = T | Ref<T>;\nexport type VueInstance = ComponentPublicInstance;\nexport type MaybeElementRef = MaybeRef<\n HTMLElement | SVGElement | VueInstance | undefined | null\n>;\n\n/**\n * Get the dom element of a ref of element or Vue component instance\n */\nexport function unrefElement(elRef: MaybeElementRef) {\n const plain = unref(elRef);\n return (plain as VueInstance)?.$el ?? plain;\n}\n\nfunction tryOnScopeDispose(fn: () => void) {\n if (getCurrentScope()) {\n onScopeDispose(fn);\n return true;\n }\n return false;\n}\n\n/**\n * @example\n * ```vue\n * <script setup>\n * const el = ref(null)\n * const app = useFastboard(el, { sdkConfig, joinRoom })\n * if (app.value) {\n * app.value.insertDocs({...})\n * }\n * </script>\n * <template>\n * <div style=\"width: 100%; height: 100%\" ref=\"el\"></div>\n * </template>\n * ```\n */\nexport function useFastboard(el: MaybeElementRef, config: FastBoardConfig) {\n const app = shallowRef<WhiteboardApp | null>(null);\n const target = computed(() => unrefElement(el));\n\n createWhiteboardApp(config).then(app_ => {\n app.value = app_;\n });\n\n watchEffect(() => {\n if (target.value && app.value) {\n app.value.bindElement(target.value);\n }\n });\n\n tryOnScopeDispose(() => {\n if (app.value) {\n app.value.dispose();\n }\n });\n\n return app;\n}\n"],"names":["unref","getCurrentScope","shallowRef","computed"],"mappings":"8VAwB6B,EAAwB,YAC7C,GAAQA,QAAM,SACZ,oBAAuB,MAAvB,OAA8B,EAGxC,WAA2B,EAAgB,OACrCC,uCACa,GACR,IAEF,cAkBoB,EAAqB,EAAyB,MACnE,GAAMC,aAAiC,MACvC,EAASC,WAAS,IAAM,EAAa,iCAEvB,GAAQ,KAAK,GAAQ,GACnC,MAAQ,kBAGF,IAAM,CACZ,EAAO,OAAS,EAAI,SAClB,MAAM,YAAY,EAAO,WAIf,IAAM,CAClB,EAAI,SACF,MAAM,YAIP"}
package/dist/vue.es.js ADDED
@@ -0,0 +1,42 @@
1
+ import { unref, shallowRef, computed, watchEffect, getCurrentScope, onScopeDispose } from "vue-demi";
2
+ import { createWhiteboardApp } from "./index.es.js";
3
+ import "@netless/window-manager";
4
+ import "white-web-sdk";
5
+ import "i18next";
6
+ import "react";
7
+ import "react-dom";
8
+ import "clsx";
9
+ import "@tippyjs/react";
10
+ import "rc-slider";
11
+ function unrefElement(elRef) {
12
+ var _a;
13
+ const plain = unref(elRef);
14
+ return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
15
+ }
16
+ function tryOnScopeDispose(fn) {
17
+ if (getCurrentScope()) {
18
+ onScopeDispose(fn);
19
+ return true;
20
+ }
21
+ return false;
22
+ }
23
+ function useFastboard(el, config) {
24
+ const app = shallowRef(null);
25
+ const target = computed(() => unrefElement(el));
26
+ createWhiteboardApp(config).then((app_) => {
27
+ app.value = app_;
28
+ });
29
+ watchEffect(() => {
30
+ if (target.value && app.value) {
31
+ app.value.bindElement(target.value);
32
+ }
33
+ });
34
+ tryOnScopeDispose(() => {
35
+ if (app.value) {
36
+ app.value.dispose();
37
+ }
38
+ });
39
+ return app;
40
+ }
41
+ export { unrefElement, useFastboard };
42
+ //# sourceMappingURL=vue.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.es.js","sources":["../src/vue.ts"],"sourcesContent":["import type { ComponentPublicInstance, Ref } from \"vue-demi\";\nimport {\n computed,\n getCurrentScope,\n onScopeDispose,\n shallowRef,\n unref,\n watchEffect,\n} from \"vue-demi\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\nexport type MaybeRef<T> = T | Ref<T>;\nexport type VueInstance = ComponentPublicInstance;\nexport type MaybeElementRef = MaybeRef<\n HTMLElement | SVGElement | VueInstance | undefined | null\n>;\n\n/**\n * Get the dom element of a ref of element or Vue component instance\n */\nexport function unrefElement(elRef: MaybeElementRef) {\n const plain = unref(elRef);\n return (plain as VueInstance)?.$el ?? plain;\n}\n\nfunction tryOnScopeDispose(fn: () => void) {\n if (getCurrentScope()) {\n onScopeDispose(fn);\n return true;\n }\n return false;\n}\n\n/**\n * @example\n * ```vue\n * <script setup>\n * const el = ref(null)\n * const app = useFastboard(el, { sdkConfig, joinRoom })\n * if (app.value) {\n * app.value.insertDocs({...})\n * }\n * </script>\n * <template>\n * <div style=\"width: 100%; height: 100%\" ref=\"el\"></div>\n * </template>\n * ```\n */\nexport function useFastboard(el: MaybeElementRef, config: FastBoardConfig) {\n const app = shallowRef<WhiteboardApp | null>(null);\n const target = computed(() => unrefElement(el));\n\n createWhiteboardApp(config).then(app_ => {\n app.value = app_;\n });\n\n watchEffect(() => {\n if (target.value && app.value) {\n app.value.bindElement(target.value);\n }\n });\n\n tryOnScopeDispose(() => {\n if (app.value) {\n app.value.dispose();\n }\n });\n\n return app;\n}\n"],"names":[],"mappings":";;;;;;;;;;sBAwB6B,OAAwB;;QAC7C,QAAQ,MAAM;SACZ,qCAAuB,QAAvB,YAA8B;AAAA;AAGxC,2BAA2B,IAAgB;MACrC,mBAAmB;mBACN;WACR;AAAA;SAEF;AAAA;sBAkBoB,IAAqB,QAAyB;QACnE,MAAM,WAAiC;QACvC,SAAS,SAAS,MAAM,aAAa;sBAEvB,QAAQ,KAAK,UAAQ;QACnC,QAAQ;AAAA;cAGF,MAAM;QACZ,OAAO,SAAS,IAAI,OAAO;UACzB,MAAM,YAAY,OAAO;AAAA;AAAA;oBAIf,MAAM;QAClB,IAAI,OAAO;UACT,MAAM;AAAA;AAAA;SAIP;AAAA;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/fastboard",
3
- "version": "0.0.4",
3
+ "version": "0.0.8",
4
4
  "description": "An open sourced sdk based on white-web-sdk.",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "module": "./dist/index.es.js",
@@ -14,6 +14,14 @@
14
14
  "default": "./dist/index.es.js",
15
15
  "types": "./src/index.ts"
16
16
  },
17
+ "./vue": {
18
+ "node": {
19
+ "import": "./dist/vue.es.js",
20
+ "require": "./dist/vue.cjs.js"
21
+ },
22
+ "default": "./dist/vue.es.js",
23
+ "types": "./src/vue.ts"
24
+ },
17
25
  "./package.json": "./package.json"
18
26
  },
19
27
  "files": [
@@ -37,8 +45,11 @@
37
45
  "@netless/app-monaco": "*",
38
46
  "@netless/app-slide": "*",
39
47
  "@netless/window-manager": "*",
48
+ "@vue/composition-api": "*",
40
49
  "react": "*",
41
50
  "react-dom": "*",
51
+ "svelte": "*",
52
+ "vue": "*",
42
53
  "white-web-sdk": "*"
43
54
  },
44
55
  "peerDependenciesMeta": {
@@ -53,6 +64,15 @@
53
64
  },
54
65
  "@netless/app-geogebra": {
55
66
  "optional": true
67
+ },
68
+ "@vue/composition-api": {
69
+ "optional": true
70
+ },
71
+ "svelte": {
72
+ "optional": true
73
+ },
74
+ "vue": {
75
+ "optional": true
56
76
  }
57
77
  },
58
78
  "dependencies": {
@@ -60,7 +80,8 @@
60
80
  "clsx": "^1.1.1",
61
81
  "i18next": "^21.6.5",
62
82
  "rc-slider": "^9.7.5",
63
- "tippy.js": "^6.3.7"
83
+ "tippy.js": "^6.3.7",
84
+ "vue-demi": "^0.12.1"
64
85
  },
65
86
  "readme": "# @netless/fastboard\n\nA whiteboard starter, based on [white-web-sdk](https://www.npmjs.com/package/white-web-sdk), [@netless/window-manager](https://www.npmjs.com/package/@netless/window-manager)\nand [netless-app](https://github.com/netless-io/netless-app).\n\n## 目录\n\n- [安装](#install)\n- [使用](#usage)\n- [进阶](./docs/advanced.md)\n- [开发](#开发)\n\n<h2 id=\"install\">安装</h2>\n\n```bash\nnpm add @netless/fastboard @netless/window-manager white-web-sdk react react-dom\n```\n\n<h2 id=\"usage\">使用</h2>\n\n<h3 id=\"mount-whiteboard\">挂载白板</h3>\n\n> 原生 `javascript`\n\n```js\nimport { createWhiteboardApp } from \"@netless/fastboard\";\n\nlet whiteboard = await createWhiteboardApp({\n target: document.getElementById(\"whiteboard\"),\n // [1]\n sdkConfig: {\n appIdentifier: \"whiteboard-appid\",\n },\n // [2]\n joinRoom: {\n uid: \"unique_id_for_each_client\",\n uuid: \"room-uuid\",\n roomToken: \"NETLESSROOM_...\",\n },\n // [3]\n managerConfig: {\n cursor: true,\n },\n});\n```\n\n> 使用 `React`\n\n```typescript\nimport { useFastboard, FastBoardConfig } from \"@netless/fastboard\";\nimport ReactDOM from \"react-dom\";\n\nconst config: FastBoardConfig = {\n sdkConfig: {\n appIdentifier: \"whiteboard-appid\",\n },\n joinRoom: {\n uid: \"unique_id_for_each_client\",\n uuid: \"room-uuid\",\n roomToken: \"NETLESSROOM_...\",\n },\n};\n\nfunction App() {\n const [app, ref] = useFastboard(config);\n return <div ref={ref} />;\n}\nReactDOM.render(<App />, document.getElementById(\"root\"));\n```\n\n[1] 关于 SDK 更多配置请看 [构造 WhiteWebSDK](https://developer.netless.link/javascript-zh/home/construct-white-web-sdk)\n\n[2] 加入房间更多配置请看 [构造 Room 与 Player 对象](https://developer.netless.link/javascript-zh/home/construct-room-and-player)\n\n[3] 配置 `WindowManager` 请看 [WindowManager](https://github.com/netless-io/window-manager#mount)\n\n### 使用 APPS\n\n**注意:** 需要先安装对应的 APP\n\n```bash\nnpm add @netless/app-slide\n```\n\n```typescript\n// 插入动态 PPTX 至白板\nconst appId = await whiteboard.insertDocs({\n fileType: \"pptx\",\n params: {\n scenePath: `/ppt/${uuid}`, // [1]\n title: \"a.pptx\",\n taskId: \"1234567...\", // [2]\n url: \"https://convertcdn.netless.link/dynamicConvert\", // [3]\n },\n});\n\n// 插入 PDF/静态 PPT 至白板\nconst appId = await whiteboard.insertDocs({\n fileType: \"pdf\", // or ppt\n options: {\n scenePath: `/pdf${uuid}`,\n title: \"a.pdf\", // 可选\n scenes: [], // SceneDefinition[] 静态/动态 Scene 数据\n },\n});\n\n// 插入音频/视频至白板\nconst appId = await whiteboard.manager.addApp({\n kind: \"MediaPlayer\",\n options: {\n title: \"test.mp3\", // 可选\n },\n attributes: {\n src: \"xxxx\", // 音视频 url\n },\n});\n```\n\n更多 `app` 请看 [netless-app](#https://github.com/netless-io/netless-app)\n\n## Develop\n\n```bash\npnpm i\n# upgrade dependencies\npnpm up -Li\n# build and see bundle size\npnpm build\nopen node_modules/.visualizer/stats.html\n```\n\n## License\n\nMIT @ [netless](https://github.com/netless-io)\n"
66
87
  }
@@ -1,11 +1,12 @@
1
1
  import type {
2
2
  InsertDocsParams,
3
3
  Language,
4
+ Layout,
4
5
  WhiteboardAppConfig,
5
6
  } from "./internal";
6
7
  import { Instance } from "./internal";
7
8
 
8
- export type { WhiteboardAppConfig, InsertDocsParams };
9
+ export type { Language, Layout, WhiteboardAppConfig, InsertDocsParams };
9
10
 
10
11
  export class WhiteboardApp {
11
12
  private readonly _instance: Instance;
@@ -14,14 +15,61 @@ export class WhiteboardApp {
14
15
  this._instance = new Instance(config);
15
16
  }
16
17
 
17
- public bindElement(target?: HTMLElement | null) {
18
- this._instance.target = target || null;
18
+ get room() {
19
+ return this._instance.room;
20
+ }
21
+
22
+ get manager() {
23
+ return this._instance.manager;
24
+ }
25
+
26
+ get sdk() {
27
+ return this._instance.sdk;
28
+ }
29
+
30
+ get i18n() {
31
+ return this._instance.i18n;
32
+ }
33
+
34
+ public get target(): HTMLElement | null {
35
+ return this._instance.target;
36
+ }
37
+
38
+ public get collector(): HTMLElement | null {
39
+ return this._instance.collector;
40
+ }
41
+
42
+ public bindElement(
43
+ target?: HTMLElement | null,
44
+ collector?: HTMLElement | null
45
+ ) {
46
+ this._instance.bindElement(target || null, collector || null);
47
+ }
48
+
49
+ public get layout() {
50
+ return this._instance.config.layout;
51
+ }
52
+
53
+ public updateLayout(layout?: Layout | undefined) {
54
+ this._instance.updateLayout(layout);
19
55
  }
20
56
 
21
57
  public insertDocs(params: InsertDocsParams) {
22
58
  return this._instance.insertDocs(params);
23
59
  }
24
60
 
61
+ public insertCodeEditor() {
62
+ return this._instance.insertCodeEditor();
63
+ }
64
+
65
+ public insertGeoGebra() {
66
+ return this._instance.insertGeoGebra();
67
+ }
68
+
69
+ public insertCountdown() {
70
+ return this._instance.insertCountdown();
71
+ }
72
+
25
73
  public changeLanguage(language: Language) {
26
74
  return this._instance.changeLanguage(language);
27
75
  }
@@ -36,14 +36,14 @@ export function PageControl({
36
36
  if (manager && room) {
37
37
  await manager.switchMainViewToWriter();
38
38
  const path = room.state.sceneState.contextPath;
39
- room.putScenes(path, [{}], pageCount);
40
- await manager.setMainViewSceneIndex(pageIndex);
39
+ room.putScenes(path, [{}], pageIndex + 1);
40
+ await manager.setMainViewSceneIndex(pageIndex + 1);
41
41
  } else if (!manager && room) {
42
42
  const path = room.state.sceneState.contextPath;
43
- room.putScenes(path, [{}], pageCount);
44
- room.setSceneIndex(pageIndex);
43
+ room.putScenes(path, [{}], pageIndex + 1);
44
+ room.setSceneIndex(pageIndex + 1);
45
45
  }
46
- }, [room, manager, pageCount, pageIndex]);
46
+ }, [room, manager, pageIndex]);
47
47
 
48
48
  const prevPage = useCallback(() => {
49
49
  if (room?.isWritable) {
@@ -10,7 +10,7 @@ export interface RootProps {
10
10
  instance: Instance;
11
11
  }
12
12
 
13
- export default function Root({ instance: app }: RootProps) {
13
+ export function Root({ instance: app }: RootProps) {
14
14
  const [mux] = useState(() => new Lock());
15
15
 
16
16
  const useWhiteboard = useCallback(
@@ -21,21 +21,40 @@ export default function Root({ instance: app }: RootProps) {
21
21
  [app, mux]
22
22
  );
23
23
 
24
+ const {
25
+ Toolbar: toolbar = true,
26
+ RedoUndo: redo_undo = true,
27
+ ZoomControl: zoom_control = true,
28
+ PageControl: page_control = true,
29
+ } = app.config.layout || {};
30
+
31
+ const props = {
32
+ room: app.room,
33
+ manager: app.manager,
34
+ i18n: app.i18n,
35
+ };
36
+
24
37
  return (
25
38
  <Instance.Context.Provider value={app}>
26
39
  <div className="fastboard-root">
27
40
  {!app.room && <div className="fastboard-loading">Loading&hellip;</div>}
28
41
  <div className="fastboard-view" ref={useWhiteboard} />
29
- <div className="fastboard-left">
30
- <Toolbar room={app.room} manager={app.manager} i18n={app.i18n} />
31
- </div>
32
- <div className="fastboard-bottom-left">
33
- <RedoUndo room={app.room} manager={app.manager} i18n={app.i18n} />
34
- <ZoomControl room={app.room} manager={app.manager} i18n={app.i18n} />
35
- </div>
36
- <div className="fastboard-bottom-right">
37
- <PageControl room={app.room} manager={app.manager} i18n={app.i18n} />
38
- </div>
42
+ {toolbar && (
43
+ <div className="fastboard-left">
44
+ <Toolbar {...props} />
45
+ </div>
46
+ )}
47
+ {(redo_undo || zoom_control) && (
48
+ <div className="fastboard-bottom-left">
49
+ {redo_undo && <RedoUndo {...props} />}
50
+ {zoom_control && <ZoomControl {...props} />}
51
+ </div>
52
+ )}
53
+ {page_control && (
54
+ <div className="fastboard-bottom-right">
55
+ <PageControl {...props} />
56
+ </div>
57
+ )}
39
58
  </div>
40
59
  </Instance.Context.Provider>
41
60
  );
package/src/hooks.ts CHANGED
@@ -15,13 +15,16 @@ export type FastBoardConfig = WhiteboardAppConfig;
15
15
  */
16
16
  export function useFastboard(config: FastBoardConfig): readonly [
17
17
  app: WhiteboardApp | null,
18
- ref: (div: HTMLDivElement | null) => void
18
+ ref: (div: HTMLDivElement | null) => void,
19
+ collectorRef: (div: HTMLDivElement | null) => void
19
20
  ] & {
20
21
  readonly app: WhiteboardApp | null;
21
22
  readonly ref: (div: HTMLDivElement | null) => void;
23
+ readonly collectorRef: (div: HTMLDivElement | null) => void;
22
24
  } {
23
25
  const [app, setApp] = useState<WhiteboardApp | null>(null);
24
26
  const [currentTarget, ref] = useState<HTMLDivElement | null>(null);
27
+ const [collector, collectorRef] = useState<HTMLDivElement | null>(null);
25
28
 
26
29
  useEffect(() => {
27
30
  let isMounted = true;
@@ -38,9 +41,13 @@ export function useFastboard(config: FastBoardConfig): readonly [
38
41
 
39
42
  useEffect(() => {
40
43
  if (app) {
41
- app.bindElement(currentTarget);
44
+ app.bindElement(currentTarget, collector);
42
45
  }
43
- }, [app, currentTarget]);
46
+ }, [app, collector, currentTarget]);
44
47
 
45
- return Object.assign([app, ref] as const, { app, ref });
48
+ return Object.assign([app, ref, collectorRef] as const, {
49
+ app,
50
+ ref,
51
+ collectorRef,
52
+ });
46
53
  }
package/src/index.ts CHANGED
@@ -10,6 +10,10 @@ export { PageControl, type PageControlProps } from "./components/PageControl";
10
10
  export { RedoUndo, type RedoUndoProps } from "./components/RedoUndo";
11
11
  export { Toolbar, type ToolbarProps } from "./components/Toolbar";
12
12
  export { ZoomControl, type ZoomControlProps } from "./components/ZoomControl";
13
+ export {
14
+ PlayerControl,
15
+ type PlayerControlProps,
16
+ } from "./components/PlayerControl";
13
17
  export * from "./WhiteboardApp";
14
18
  export * from "./hooks";
15
19
 
@@ -1,3 +1,4 @@
1
+ import type { Mutable } from "type-fest";
1
2
  import type { WindowManager } from "@netless/window-manager";
2
3
  import type { Room, SceneDefinition, WhiteWebSdk } from "white-web-sdk";
3
4
  import type { JoinRoom, ManagerConfig, SdkConfig } from "./mount-whiteboard";
@@ -6,7 +7,7 @@ import type { i18n } from "i18next";
6
7
  import React, { createContext, useContext } from "react";
7
8
  import ReactDOM from "react-dom";
8
9
 
9
- import Root from "../components/Root";
10
+ import { Root } from "../components/Root";
10
11
  import { mountWhiteboard } from "./mount-whiteboard";
11
12
  import { noop } from "./helpers";
12
13
 
@@ -36,10 +37,18 @@ export type InsertDocsParams = InsertDocsStatic | InsertDocsDynamic;
36
37
 
37
38
  export type Language = "zh-CN" | "en-US";
38
39
 
40
+ export interface Layout {
41
+ Toolbar?: boolean;
42
+ PageControl?: boolean;
43
+ RedoUndo?: boolean;
44
+ ZoomControl?: boolean;
45
+ }
46
+
39
47
  export interface WhiteboardAppConfig {
40
48
  readonly sdkConfig: SdkConfig;
41
49
  readonly joinRoom: JoinRoom;
42
50
  readonly managerConfig?: Omit<ManagerConfig, "container">;
51
+ readonly layout?: Layout;
43
52
  readonly toolbar?: {
44
53
  apps?: {
45
54
  enable?: boolean;
@@ -60,7 +69,7 @@ export interface Essentials {
60
69
  export class Instance {
61
70
  static readonly Context = createContext<Instance | null>(null);
62
71
 
63
- readonly config: WhiteboardAppConfig;
72
+ config: Mutable<WhiteboardAppConfig>;
64
73
 
65
74
  sdk: WhiteWebSdk | null = null;
66
75
  room: Room | null = null;
@@ -82,13 +91,11 @@ export class Instance {
82
91
  }
83
92
 
84
93
  constructor(config: WhiteboardAppConfig) {
85
- this.config = config;
94
+ this.config = { ...config };
86
95
  this.refreshReadyPromise();
87
96
  this.initialize();
88
97
  }
89
98
 
90
- private _target: HTMLElement | null = null;
91
-
92
99
  async initialize() {
93
100
  const essentials = await mountWhiteboard(
94
101
  this.config.sdkConfig,
@@ -100,15 +107,20 @@ export class Instance {
100
107
  this.resolveReady();
101
108
  }
102
109
 
103
- get target(): HTMLElement | null {
104
- return this._target;
105
- }
110
+ target: HTMLElement | null = null;
111
+ collector: HTMLElement | null = null;
106
112
 
107
- set target(value: HTMLElement | null) {
108
- if (this._target && value) {
109
- ReactDOM.unmountComponentAtNode(this._target);
113
+ bindElement(target: HTMLElement | null, collector: HTMLElement | null) {
114
+ if (this.target && target) {
115
+ ReactDOM.unmountComponentAtNode(this.target);
110
116
  }
111
- this._target = value;
117
+ this.target = target;
118
+ this.collector = collector;
119
+ this.forceUpdate();
120
+ }
121
+
122
+ updateLayout(layout: Layout | undefined) {
123
+ this.config.layout = layout;
112
124
  this.forceUpdate();
113
125
  }
114
126
 
@@ -139,7 +151,12 @@ export class Instance {
139
151
 
140
152
  async mount(node: HTMLElement) {
141
153
  await this.readyPromise;
142
- if (this.manager) {
154
+ if (!this.manager) {
155
+ throw new Error(`[WhiteboardApp] mounted, but not found window manager`);
156
+ }
157
+ if (this.collector) {
158
+ this.manager.bindContainer(node, this.collector);
159
+ } else {
143
160
  this.manager.bindContainer(node);
144
161
  }
145
162
  }
@@ -4,7 +4,7 @@ import type {
4
4
  RoomCallbacks,
5
5
  WhiteWebSdkConfiguration,
6
6
  } from "white-web-sdk";
7
- import type { Essentials, Language } from "./instance";
7
+ import type { Essentials, Language } from "./Instance";
8
8
 
9
9
  import { WindowManager } from "@netless/window-manager";
10
10
  import { DefaultHotKeys, WhiteWebSdk } from "white-web-sdk";
package/src/svelte.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { readable, type Readable } from "svelte/store";
2
+ import { createWhiteboardApp } from "./index";
3
+
4
+ import type { WhiteboardApp, WhiteboardAppConfig } from "./WhiteboardApp";
5
+
6
+ export type FastBoardConfig = WhiteboardAppConfig;
7
+
8
+ /**
9
+ * @example
10
+ * ```svelte
11
+ * <script>
12
+ * let ref = writable(null)
13
+ * let app = useFastboard(ref, { sdkConfig, joinRoom })
14
+ * if (app) {
15
+ * app.insertDocs({...})
16
+ * }
17
+ * </script>
18
+ * <div style="width: 100%; height: 100%" bind:this={$ref} />
19
+ * ```
20
+ */
21
+ export function useFastboard(
22
+ ref: Readable<HTMLElement | null>,
23
+ config: FastBoardConfig
24
+ ): Readable<WhiteboardApp | null> {
25
+ return readable<WhiteboardApp | null>(null, set => {
26
+ let app_: WhiteboardApp | null = null;
27
+
28
+ createWhiteboardApp(config).then(app => {
29
+ set((app_ = app));
30
+ });
31
+
32
+ const dispose = ref.subscribe(div => {
33
+ if (div && app_) {
34
+ app_.bindElement(div);
35
+ }
36
+ });
37
+
38
+ return () => {
39
+ dispose();
40
+ if (app_) {
41
+ app_.dispose();
42
+ }
43
+ };
44
+ });
45
+ }
package/src/vue.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { ComponentPublicInstance, Ref } from "vue-demi";
2
+ import {
3
+ computed,
4
+ getCurrentScope,
5
+ onScopeDispose,
6
+ shallowRef,
7
+ unref,
8
+ watchEffect,
9
+ } from "vue-demi";
10
+ import { createWhiteboardApp } from "./index";
11
+
12
+ import type { WhiteboardApp, WhiteboardAppConfig } from "./WhiteboardApp";
13
+
14
+ export type FastBoardConfig = WhiteboardAppConfig;
15
+
16
+ export type MaybeRef<T> = T | Ref<T>;
17
+ export type VueInstance = ComponentPublicInstance;
18
+ export type MaybeElementRef = MaybeRef<
19
+ HTMLElement | SVGElement | VueInstance | undefined | null
20
+ >;
21
+
22
+ /**
23
+ * Get the dom element of a ref of element or Vue component instance
24
+ */
25
+ export function unrefElement(elRef: MaybeElementRef) {
26
+ const plain = unref(elRef);
27
+ return (plain as VueInstance)?.$el ?? plain;
28
+ }
29
+
30
+ function tryOnScopeDispose(fn: () => void) {
31
+ if (getCurrentScope()) {
32
+ onScopeDispose(fn);
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ /**
39
+ * @example
40
+ * ```vue
41
+ * <script setup>
42
+ * const el = ref(null)
43
+ * const app = useFastboard(el, { sdkConfig, joinRoom })
44
+ * if (app.value) {
45
+ * app.value.insertDocs({...})
46
+ * }
47
+ * </script>
48
+ * <template>
49
+ * <div style="width: 100%; height: 100%" ref="el"></div>
50
+ * </template>
51
+ * ```
52
+ */
53
+ export function useFastboard(el: MaybeElementRef, config: FastBoardConfig) {
54
+ const app = shallowRef<WhiteboardApp | null>(null);
55
+ const target = computed(() => unrefElement(el));
56
+
57
+ createWhiteboardApp(config).then(app_ => {
58
+ app.value = app_;
59
+ });
60
+
61
+ watchEffect(() => {
62
+ if (target.value && app.value) {
63
+ app.value.bindElement(target.value);
64
+ }
65
+ });
66
+
67
+ tryOnScopeDispose(() => {
68
+ if (app.value) {
69
+ app.value.dispose();
70
+ }
71
+ });
72
+
73
+ return app;
74
+ }