@netless/fastboard 0.0.7 → 0.0.11
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.
- package/README.md +21 -19
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +678 -410
- package/dist/index.es.js.map +1 -1
- package/dist/svelte.cjs.js +1 -1
- package/dist/svelte.cjs.js.map +1 -1
- package/dist/svelte.es.js +1 -0
- package/dist/svelte.es.js.map +1 -1
- package/dist/vue.cjs.js +1 -1
- package/dist/vue.cjs.js.map +1 -1
- package/dist/vue.es.js +1 -0
- package/dist/vue.es.js.map +1 -1
- package/package.json +11 -2
- package/src/WhiteboardApp.ts +91 -20
- package/src/components/{PageControl.scss → PageControl/PageControl.scss} +0 -0
- package/src/components/PageControl/PageControl.tsx +110 -0
- package/src/components/PageControl/hooks.ts +70 -0
- package/src/components/PageControl/index.ts +2 -0
- package/src/components/PlayerControl/PlayerControl.tsx +7 -8
- package/src/components/PlayerControl/hooks.ts +3 -10
- package/src/components/PlayerControl/index.ts +1 -0
- package/src/components/{RedoUndo.scss → RedoUndo/RedoUndo.scss} +0 -0
- package/src/components/{RedoUndo.tsx → RedoUndo/RedoUndo.tsx} +13 -29
- package/src/components/RedoUndo/hooks.ts +50 -0
- package/src/components/RedoUndo/index.ts +2 -0
- package/src/components/Root.tsx +10 -6
- package/src/components/Toolbar/Content.tsx +4 -3
- package/src/components/Toolbar/Toolbar.scss +35 -1
- package/src/components/Toolbar/Toolbar.tsx +78 -28
- package/src/components/Toolbar/components/Mask.tsx +44 -0
- package/src/components/Toolbar/components/assets/collapsed.png +0 -0
- package/src/components/Toolbar/components/assets/expanded.png +0 -0
- package/src/components/Toolbar/hooks.ts +28 -29
- package/src/components/Toolbar/index.ts +1 -0
- package/src/components/{ZoomControl.scss → ZoomControl/ZoomControl.scss} +0 -0
- package/src/components/ZoomControl/ZoomControl.tsx +109 -0
- package/src/components/ZoomControl/hooks.ts +111 -0
- package/src/components/ZoomControl/index.ts +2 -0
- package/src/components/hooks.ts +80 -0
- package/src/index.ts +20 -5
- package/src/internal/Instance.tsx +31 -7
- package/src/internal/helpers.ts +44 -0
- package/src/internal/hooks.ts +9 -0
- package/src/react.tsx +52 -0
- package/src/style.scss +9 -3
- package/src/components/PageControl.tsx +0 -181
- package/src/components/ZoomControl.tsx +0 -221
- package/src/hooks.ts +0 -53
package/dist/svelte.cjs.js
CHANGED
|
@@ -1,2 +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
|
|
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("framer-motion");require("clsx");require("@tippyjs/react");require("rc-slider");function o(i,t){return a.readable(null,s=>{let e=null;n.createWhiteboardApp(t).then(r=>{s(e=r)});const u=i.subscribe(r=>{r&&e&&e.bindElement(r)});return()=>{u(),e&&e.dispose()}})}exports.useFastboard=o;
|
|
2
2
|
//# sourceMappingURL=svelte.cjs.js.map
|
package/dist/svelte.cjs.js.map
CHANGED
|
@@ -1 +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":"
|
|
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":"2XAqBE,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"}
|
package/dist/svelte.es.js
CHANGED
package/dist/svelte.es.js.map
CHANGED
|
@@ -1 +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":"
|
|
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;;"}
|
package/dist/vue.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var t=require("vue-demi"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var t=require("vue-demi"),o=require("./index.cjs.js");require("@netless/window-manager");require("white-web-sdk");require("i18next");require("react");require("react-dom");require("framer-motion");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 s(u){return t.getCurrentScope()?(t.onScopeDispose(u),!0):!1}function l(u,r){const e=t.shallowRef(null),i=t.computed(()=>n(u));return o.createWhiteboardApp(r).then(a=>{e.value=a}),t.watchEffect(()=>{i.value&&e.value&&e.value.bindElement(i.value)}),s(()=>{e.value&&e.value.dispose()}),e}exports.unrefElement=n;exports.useFastboard=l;
|
|
2
2
|
//# sourceMappingURL=vue.cjs.js.map
|
package/dist/vue.cjs.js.map
CHANGED
|
@@ -1 +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":"
|
|
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":"uXAwB6B,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
CHANGED
package/dist/vue.es.js.map
CHANGED
|
@@ -1 +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":"
|
|
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.
|
|
3
|
+
"version": "0.0.11",
|
|
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",
|
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
"default": "./dist/vue.es.js",
|
|
23
23
|
"types": "./src/vue.ts"
|
|
24
24
|
},
|
|
25
|
+
"./svelte": {
|
|
26
|
+
"node": {
|
|
27
|
+
"import": "./dist/svelte.es.js",
|
|
28
|
+
"require": "./dist/svelte.cjs.js"
|
|
29
|
+
},
|
|
30
|
+
"default": "./dist/svelte.es.js",
|
|
31
|
+
"types": "./src/svelte.ts"
|
|
32
|
+
},
|
|
25
33
|
"./package.json": "./package.json"
|
|
26
34
|
},
|
|
27
35
|
"files": [
|
|
@@ -78,10 +86,11 @@
|
|
|
78
86
|
"dependencies": {
|
|
79
87
|
"@tippyjs/react": "^4.2.6",
|
|
80
88
|
"clsx": "^1.1.1",
|
|
89
|
+
"framer-motion": "^5.5.6",
|
|
81
90
|
"i18next": "^21.6.5",
|
|
82
91
|
"rc-slider": "^9.7.5",
|
|
83
92
|
"tippy.js": "^6.3.7",
|
|
84
93
|
"vue-demi": "^0.12.1"
|
|
85
94
|
},
|
|
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
|
|
95
|
+
"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 app = await createWhiteboardApp({\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\napp.bindElement(document.getElementById(\"whiteboard\"));\n```\n\n> 使用 `React`\n\n```jsx\nimport { createWhiteboardApp, Fastboard } from \"@netless/fastboard\";\nimport ReactDOM from \"react-dom\";\n\nlet app = await createWhiteboardApp({\n /* ... */\n});\n\nfunction App() {\n return <Fastboard app={app} />;\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关于窗口最小化后显示的小图标,可以通过 CSS 覆盖样式的方式修改它的位置:\n\n```css\n.telebox-collector {\n right: 20px;\n bottom: 40px;\n}\n```\n\n### 使用 APPS\n\n**注意:** 需要先安装对应的 APP\n\n```bash\nnpm add @netless/app-slide\n```\n\n```typescript\n// 插入动态 PPTX 至白板\nconst appId = await app.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 app.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 app.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"
|
|
87
96
|
}
|
package/src/WhiteboardApp.ts
CHANGED
|
@@ -1,56 +1,127 @@
|
|
|
1
|
+
import type { ConversionResponse, SceneDefinition } from "white-web-sdk";
|
|
1
2
|
import type {
|
|
2
3
|
InsertDocsParams,
|
|
4
|
+
InsertMediaParams,
|
|
3
5
|
Language,
|
|
6
|
+
Layout,
|
|
4
7
|
WhiteboardAppConfig,
|
|
5
8
|
} from "./internal";
|
|
6
|
-
import { Instance } from "./internal";
|
|
9
|
+
import { genUID, Instance, makeSlideParams } from "./internal";
|
|
7
10
|
|
|
8
|
-
export type {
|
|
11
|
+
export type {
|
|
12
|
+
Language,
|
|
13
|
+
Layout,
|
|
14
|
+
WhiteboardAppConfig,
|
|
15
|
+
InsertMediaParams,
|
|
16
|
+
InsertDocsParams,
|
|
17
|
+
};
|
|
9
18
|
|
|
10
19
|
export class WhiteboardApp {
|
|
11
20
|
private readonly _instance: Instance;
|
|
12
21
|
|
|
13
|
-
constructor(readonly config: WhiteboardAppConfig) {
|
|
22
|
+
public constructor(readonly config: WhiteboardAppConfig) {
|
|
14
23
|
this._instance = new Instance(config);
|
|
15
24
|
}
|
|
16
25
|
|
|
17
|
-
get room() {
|
|
26
|
+
public get room() {
|
|
18
27
|
return this._instance.room;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
get manager() {
|
|
30
|
+
public get manager() {
|
|
22
31
|
return this._instance.manager;
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
get sdk() {
|
|
34
|
+
public get sdk() {
|
|
26
35
|
return this._instance.sdk;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
get i18n() {
|
|
38
|
+
public get i18n() {
|
|
30
39
|
return this._instance.i18n;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
private _target: HTMLElement | null = null;
|
|
34
42
|
public get target(): HTMLElement | null {
|
|
35
|
-
return this.
|
|
43
|
+
return this._instance.target;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
private _collector: HTMLElement | null = null;
|
|
39
46
|
public get collector(): HTMLElement | null {
|
|
40
|
-
return this.
|
|
47
|
+
return this._instance.collector;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
public bindElement(
|
|
44
|
-
target
|
|
45
|
-
collector?: HTMLElement | null
|
|
46
|
-
) {
|
|
47
|
-
this._target = target || null;
|
|
48
|
-
this._collector = collector || null;
|
|
49
|
-
this._instance.bindElement(this._target, this._collector);
|
|
50
|
+
public bindElement(target?: HTMLElement | null) {
|
|
51
|
+
this._instance.bindElement(target || null);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
public
|
|
53
|
-
|
|
54
|
+
public bindCollector(collector?: HTMLElement | null) {
|
|
55
|
+
this._instance.bindCollector(collector || null);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public get layout() {
|
|
59
|
+
return this._instance.config.layout;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public updateLayout(layout?: Layout | undefined) {
|
|
63
|
+
this._instance.updateLayout(layout);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public insertMedia(params: InsertMediaParams) {
|
|
67
|
+
this._instance.insertMedia(params);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Insert PDF/PPTX from conversion result.
|
|
72
|
+
* @param status https://developer.netless.link/server-en/home/server-conversion#get-query-task-conversion-progress
|
|
73
|
+
*/
|
|
74
|
+
public insertDocs(
|
|
75
|
+
filename: string,
|
|
76
|
+
status: ConversionResponse
|
|
77
|
+
): Promise<string | undefined>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Manual way.
|
|
81
|
+
*/
|
|
82
|
+
public insertDocs(params: InsertDocsParams): Promise<string | undefined>;
|
|
83
|
+
|
|
84
|
+
public insertDocs(
|
|
85
|
+
arg1: string | InsertDocsParams,
|
|
86
|
+
arg2?: ConversionResponse
|
|
87
|
+
) {
|
|
88
|
+
if (typeof arg1 === "object" && "fileType" in arg1) {
|
|
89
|
+
return this._instance.insertDocs(arg1);
|
|
90
|
+
} else if (arg2 && arg2.status !== "Finished") {
|
|
91
|
+
throw new Error("[WhiteboardApp] cannot insert a converting doc");
|
|
92
|
+
} else if (arg2 && arg2.progress) {
|
|
93
|
+
const scenes: SceneDefinition[] = arg2.progress.convertedFileList.map(
|
|
94
|
+
(f, i) => ({
|
|
95
|
+
name: String(i + 1),
|
|
96
|
+
ppt: {
|
|
97
|
+
src: f.conversionFileUrl,
|
|
98
|
+
width: f.width,
|
|
99
|
+
height: f.height,
|
|
100
|
+
previewURL: f.preview,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
const uid = genUID();
|
|
105
|
+
const scenePath = `/${arg2.uuid}/${uid}`;
|
|
106
|
+
const { scenesWithoutPPT, taskId, url } = makeSlideParams(scenes);
|
|
107
|
+
if (taskId && url) {
|
|
108
|
+
return this._instance.insertDocs({
|
|
109
|
+
fileType: "pptx",
|
|
110
|
+
scenePath,
|
|
111
|
+
taskId,
|
|
112
|
+
title: arg1,
|
|
113
|
+
url,
|
|
114
|
+
scenes: scenesWithoutPPT,
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
return this._instance.insertDocs({
|
|
118
|
+
fileType: "pdf",
|
|
119
|
+
scenePath,
|
|
120
|
+
scenes,
|
|
121
|
+
title: arg1,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
54
125
|
}
|
|
55
126
|
|
|
56
127
|
public insertCodeEditor() {
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { CommonProps, GenericIcon } from "../../types";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import Tippy from "@tippyjs/react";
|
|
6
|
+
|
|
7
|
+
import { TopOffset } from "../../theme";
|
|
8
|
+
import { Icon } from "../../icons";
|
|
9
|
+
import { FilePlus } from "../../icons/FilePlus";
|
|
10
|
+
import { ChevronLeft } from "../../icons/ChevronLeft";
|
|
11
|
+
import { ChevronRight } from "../../icons/ChevronRight";
|
|
12
|
+
import { useWritable } from "../hooks";
|
|
13
|
+
import { usePageControl } from "./hooks";
|
|
14
|
+
|
|
15
|
+
export const name = "fastboard-page-control";
|
|
16
|
+
|
|
17
|
+
export type PageControlProps = CommonProps &
|
|
18
|
+
GenericIcon<"add" | "prev" | "next">;
|
|
19
|
+
|
|
20
|
+
export function PageControl({
|
|
21
|
+
room,
|
|
22
|
+
manager,
|
|
23
|
+
theme = "light",
|
|
24
|
+
addIcon,
|
|
25
|
+
addIconDisable,
|
|
26
|
+
prevIcon,
|
|
27
|
+
prevIconDisable,
|
|
28
|
+
nextIcon,
|
|
29
|
+
nextIconDisable,
|
|
30
|
+
i18n,
|
|
31
|
+
}: PageControlProps) {
|
|
32
|
+
const writable = useWritable(room);
|
|
33
|
+
const { pageIndex, pageCount, ...actions } = usePageControl(room, manager);
|
|
34
|
+
|
|
35
|
+
const disabled = !writable;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={clsx(name, theme)}>
|
|
39
|
+
{/* <span className={clsx(`${name}-cut-line`, theme)} />{" "} */}
|
|
40
|
+
<Tippy
|
|
41
|
+
className="fastboard-tip"
|
|
42
|
+
content={i18n?.t("prevPage")}
|
|
43
|
+
theme={theme}
|
|
44
|
+
disabled={disabled}
|
|
45
|
+
placement="top"
|
|
46
|
+
duration={300}
|
|
47
|
+
offset={TopOffset}
|
|
48
|
+
>
|
|
49
|
+
<button
|
|
50
|
+
className={clsx(`${name}-btn`, "prev", theme)}
|
|
51
|
+
disabled={disabled || pageIndex === 0}
|
|
52
|
+
onClick={actions.prevPage}
|
|
53
|
+
>
|
|
54
|
+
<Icon
|
|
55
|
+
fallback={<ChevronLeft theme={theme} />}
|
|
56
|
+
src={disabled ? prevIconDisable : prevIcon}
|
|
57
|
+
alt="[prev]"
|
|
58
|
+
/>
|
|
59
|
+
</button>
|
|
60
|
+
</Tippy>
|
|
61
|
+
<span className={clsx(`${name}-page`, theme)}>
|
|
62
|
+
{pageCount === 0 ? "\u2026" : pageIndex + 1}
|
|
63
|
+
</span>
|
|
64
|
+
<span className={clsx(`${name}-slash`, theme)}>/</span>
|
|
65
|
+
<span className={clsx(`${name}-page-count`, theme)}>{pageCount}</span>
|
|
66
|
+
<Tippy
|
|
67
|
+
className="fastboard-tip"
|
|
68
|
+
content={i18n?.t("nextPage")}
|
|
69
|
+
theme={theme}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
placement="top"
|
|
72
|
+
duration={300}
|
|
73
|
+
offset={TopOffset}
|
|
74
|
+
>
|
|
75
|
+
<button
|
|
76
|
+
className={clsx(`${name}-btn`, "next", theme)}
|
|
77
|
+
disabled={disabled || pageIndex === pageCount - 1}
|
|
78
|
+
onClick={actions.nextPage}
|
|
79
|
+
>
|
|
80
|
+
<Icon
|
|
81
|
+
fallback={<ChevronRight theme={theme} />}
|
|
82
|
+
src={disabled ? nextIconDisable : nextIcon}
|
|
83
|
+
alt="[next]"
|
|
84
|
+
/>
|
|
85
|
+
</button>
|
|
86
|
+
</Tippy>
|
|
87
|
+
<Tippy
|
|
88
|
+
className="fastboard-tip"
|
|
89
|
+
content={i18n?.t("addPage")}
|
|
90
|
+
theme={theme}
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
placement="top"
|
|
93
|
+
duration={300}
|
|
94
|
+
offset={TopOffset}
|
|
95
|
+
>
|
|
96
|
+
<button
|
|
97
|
+
className={clsx(`${name}-btn`, "add", theme)}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
onClick={actions.addPage}
|
|
100
|
+
>
|
|
101
|
+
<Icon
|
|
102
|
+
fallback={<FilePlus theme={theme} />}
|
|
103
|
+
src={disabled ? addIconDisable : addIcon}
|
|
104
|
+
alt="[add]"
|
|
105
|
+
/>
|
|
106
|
+
</button>
|
|
107
|
+
</Tippy>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Room, RoomState } from "white-web-sdk";
|
|
2
|
+
import type { WindowManager } from "@netless/window-manager";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export function usePageControl(
|
|
6
|
+
room?: Room | null,
|
|
7
|
+
manager?: WindowManager | null
|
|
8
|
+
) {
|
|
9
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
10
|
+
const [pageCount, setPageCount] = useState(0);
|
|
11
|
+
|
|
12
|
+
const addPage = useCallback(async () => {
|
|
13
|
+
if (manager && room) {
|
|
14
|
+
await manager.switchMainViewToWriter();
|
|
15
|
+
const path = room.state.sceneState.contextPath;
|
|
16
|
+
room.putScenes(path, [{}], pageIndex + 1);
|
|
17
|
+
await manager.setMainViewSceneIndex(pageIndex + 1);
|
|
18
|
+
} else if (!manager && room) {
|
|
19
|
+
const path = room.state.sceneState.contextPath;
|
|
20
|
+
room.putScenes(path, [{}], pageIndex + 1);
|
|
21
|
+
room.setSceneIndex(pageIndex + 1);
|
|
22
|
+
}
|
|
23
|
+
}, [room, manager, pageIndex]);
|
|
24
|
+
|
|
25
|
+
const prevPage = useCallback(() => {
|
|
26
|
+
if (manager) {
|
|
27
|
+
manager.setMainViewSceneIndex(pageIndex - 1);
|
|
28
|
+
} else if (room) {
|
|
29
|
+
room.pptPreviousStep();
|
|
30
|
+
}
|
|
31
|
+
}, [room, manager, pageIndex]);
|
|
32
|
+
|
|
33
|
+
const nextPage = useCallback(() => {
|
|
34
|
+
if (manager) {
|
|
35
|
+
manager.setMainViewSceneIndex(pageIndex + 1);
|
|
36
|
+
} else if (room) {
|
|
37
|
+
room.pptNextStep();
|
|
38
|
+
}
|
|
39
|
+
}, [room, manager, pageIndex]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (room) {
|
|
43
|
+
setPageIndex(room.state.sceneState.index);
|
|
44
|
+
setPageCount(room.state.sceneState.scenes.length);
|
|
45
|
+
|
|
46
|
+
if (manager) {
|
|
47
|
+
manager.emitter.on("mainViewSceneIndexChange", setPageIndex);
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
manager.emitter.off("mainViewSceneIndexChange", setPageIndex);
|
|
51
|
+
};
|
|
52
|
+
} else {
|
|
53
|
+
const onRoomStateChanged = (modifyState: Partial<RoomState>) => {
|
|
54
|
+
if (modifyState.sceneState) {
|
|
55
|
+
setPageIndex(modifyState.sceneState.index);
|
|
56
|
+
setPageCount(modifyState.sceneState.scenes.length);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
room.callbacks.on("onRoomStateChanged", onRoomStateChanged);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
room.callbacks.off("onRoomStateChanged", onRoomStateChanged);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, [room, manager]);
|
|
68
|
+
|
|
69
|
+
return { pageIndex, pageCount, prevPage, nextPage, addPage };
|
|
70
|
+
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import type { Player } from "white-web-sdk";
|
|
2
1
|
import type { CommonProps, GenericIcon } from "../../types";
|
|
3
|
-
|
|
2
|
+
import Tippy from "@tippyjs/react";
|
|
4
3
|
import clsx from "clsx";
|
|
5
|
-
import React, { useEffect, useState } from "react";
|
|
6
4
|
import RcSlider from "rc-slider";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import { PlayerPhase, type Player } from "white-web-sdk";
|
|
7
|
+
|
|
9
8
|
import { Icon } from "../../icons";
|
|
10
9
|
import { themes, TopOffset } from "../../theme";
|
|
11
|
-
import { Icons } from "./icons";
|
|
12
|
-
import Tippy from "@tippyjs/react";
|
|
13
10
|
import { Button } from "./components/Button";
|
|
11
|
+
import { Icons } from "./icons";
|
|
12
|
+
import { usePlayerControl } from "./hooks";
|
|
14
13
|
|
|
15
14
|
export type PlayerControlProps = {
|
|
16
15
|
autoHide?: boolean;
|
|
@@ -28,7 +27,7 @@ export function PlayerControl({
|
|
|
28
27
|
...icons
|
|
29
28
|
}: PlayerControlProps) {
|
|
30
29
|
const [currentTime, setCurrentTime] = useState(0);
|
|
31
|
-
const player =
|
|
30
|
+
const player = usePlayerControl(player_);
|
|
32
31
|
|
|
33
32
|
useEffect(() => {
|
|
34
33
|
setCurrentTime(player.currentTime);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { DependencyList } from "react";
|
|
2
2
|
import type { Player } from "white-web-sdk";
|
|
3
3
|
|
|
4
|
-
import { useCallback, useEffect,
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
5
|
import { PlayerPhase } from "white-web-sdk";
|
|
6
|
+
import { useLastValue } from "../../internal/hooks";
|
|
6
7
|
|
|
7
8
|
const EMPTY_ARRAY: DependencyList = [];
|
|
8
9
|
|
|
@@ -12,15 +13,7 @@ function useForceUpdate() {
|
|
|
12
13
|
return useCallback(() => forceUpdate_({}), EMPTY_ARRAY);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function
|
|
16
|
-
const ref = useRef<T>(value);
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
ref.current = value;
|
|
19
|
-
}, [value]);
|
|
20
|
-
return ref.current;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function usePlayer(player?: Player | null) {
|
|
16
|
+
export function usePlayerControl(player?: Player | null) {
|
|
24
17
|
const togglePlay = useCallback(() => {
|
|
25
18
|
if (player) {
|
|
26
19
|
switch (player.phase) {
|
|
File without changes
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type { CommonProps, GenericIcon } from "
|
|
1
|
+
import type { CommonProps, GenericIcon } from "../../types";
|
|
2
2
|
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import React
|
|
4
|
+
import React from "react";
|
|
5
5
|
import Tippy from "@tippyjs/react";
|
|
6
6
|
|
|
7
|
-
import { Icon } from "
|
|
8
|
-
import { Undo } from "
|
|
9
|
-
import { Redo } from "
|
|
10
|
-
import { TopOffset } from "
|
|
7
|
+
import { Icon } from "../../icons";
|
|
8
|
+
import { Undo } from "../../icons/Undo";
|
|
9
|
+
import { Redo } from "../../icons/Redo";
|
|
10
|
+
import { TopOffset } from "../../theme";
|
|
11
|
+
import { useWritable } from "../hooks";
|
|
12
|
+
import { useRedoUndo } from "./hooks";
|
|
11
13
|
|
|
12
14
|
export const name = "fastboard-redo-undo";
|
|
13
15
|
|
|
@@ -15,6 +17,7 @@ export type RedoUndoProps = CommonProps & GenericIcon<"undo" | "redo">;
|
|
|
15
17
|
|
|
16
18
|
export function RedoUndo({
|
|
17
19
|
room,
|
|
20
|
+
manager,
|
|
18
21
|
theme = "light",
|
|
19
22
|
undoIcon,
|
|
20
23
|
undoIconDisable,
|
|
@@ -22,27 +25,8 @@ export function RedoUndo({
|
|
|
22
25
|
redoIconDisable,
|
|
23
26
|
i18n,
|
|
24
27
|
}: RedoUndoProps) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const [redoSteps, setRedoSteps] = useState(0);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (room) {
|
|
31
|
-
setWritable(room.isWritable);
|
|
32
|
-
room.isWritable && (room.disableSerialization = false);
|
|
33
|
-
|
|
34
|
-
const updateWritable = () => setWritable(room?.isWritable || false);
|
|
35
|
-
|
|
36
|
-
room.callbacks.on("onEnableWriteNowChanged", updateWritable);
|
|
37
|
-
room.callbacks.on("onCanUndoStepsUpdate", setUndoSteps);
|
|
38
|
-
room.callbacks.on("onCanRedoStepsUpdate", setRedoSteps);
|
|
39
|
-
return () => {
|
|
40
|
-
room.callbacks.off("onEnableWriteNowChanged", updateWritable);
|
|
41
|
-
room.callbacks.off("onCanUndoStepsUpdate", setUndoSteps);
|
|
42
|
-
room.callbacks.off("onCanRedoStepsUpdate", setRedoSteps);
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}, [room]);
|
|
28
|
+
const writable = useWritable(room);
|
|
29
|
+
const { redoSteps, undoSteps, redo, undo } = useRedoUndo(room, manager);
|
|
46
30
|
|
|
47
31
|
const disabled = !writable;
|
|
48
32
|
|
|
@@ -60,7 +44,7 @@ export function RedoUndo({
|
|
|
60
44
|
<button
|
|
61
45
|
className={clsx(`${name}-btn`, "undo", theme)}
|
|
62
46
|
disabled={disabled || undoSteps === 0}
|
|
63
|
-
onClick={
|
|
47
|
+
onClick={undo}
|
|
64
48
|
>
|
|
65
49
|
<Icon
|
|
66
50
|
fallback={<Undo theme={theme} />}
|
|
@@ -81,7 +65,7 @@ export function RedoUndo({
|
|
|
81
65
|
<button
|
|
82
66
|
className={clsx(`${name}-btn`, "redo", theme)}
|
|
83
67
|
disabled={disabled || redoSteps === 0}
|
|
84
|
-
onClick={
|
|
68
|
+
onClick={redo}
|
|
85
69
|
>
|
|
86
70
|
<Icon
|
|
87
71
|
fallback={<Redo theme={theme} />}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WindowManager, Room } from "@netless/window-manager";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
export function useRedoUndo(
|
|
5
|
+
room?: Room | null,
|
|
6
|
+
manager?: WindowManager | null
|
|
7
|
+
) {
|
|
8
|
+
const [undoSteps, setUndoSteps] = useState(0);
|
|
9
|
+
const [redoSteps, setRedoSteps] = useState(0);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (manager) {
|
|
13
|
+
manager.mainView.callbacks.on("onCanUndoStepsUpdate", setUndoSteps);
|
|
14
|
+
manager.mainView.callbacks.on("onCanRedoStepsUpdate", setRedoSteps);
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
manager.mainView.callbacks.off("onCanUndoStepsUpdate", setUndoSteps);
|
|
18
|
+
manager.mainView.callbacks.off("onCanRedoStepsUpdate", setRedoSteps);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (room) {
|
|
23
|
+
room.callbacks.on("onCanUndoStepsUpdate", setUndoSteps);
|
|
24
|
+
room.callbacks.on("onCanRedoStepsUpdate", setRedoSteps);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
room.callbacks.off("onCanUndoStepsUpdate", setUndoSteps);
|
|
28
|
+
room.callbacks.off("onCanRedoStepsUpdate", setRedoSteps);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}, [room, manager]);
|
|
32
|
+
|
|
33
|
+
const undo = useCallback(() => {
|
|
34
|
+
if (manager) {
|
|
35
|
+
manager.mainView.undo();
|
|
36
|
+
} else if (room) {
|
|
37
|
+
room.undo();
|
|
38
|
+
}
|
|
39
|
+
}, [manager, room]);
|
|
40
|
+
|
|
41
|
+
const redo = useCallback(() => {
|
|
42
|
+
if (manager) {
|
|
43
|
+
manager.mainView.redo();
|
|
44
|
+
} else if (room) {
|
|
45
|
+
room.redo();
|
|
46
|
+
}
|
|
47
|
+
}, [manager, room]);
|
|
48
|
+
|
|
49
|
+
return { redoSteps, undoSteps, redo, undo };
|
|
50
|
+
}
|