@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.
Files changed (49) hide show
  1. package/README.md +21 -19
  2. package/dist/index.cjs.js +4 -4
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.es.js +678 -410
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/svelte.cjs.js +1 -1
  7. package/dist/svelte.cjs.js.map +1 -1
  8. package/dist/svelte.es.js +1 -0
  9. package/dist/svelte.es.js.map +1 -1
  10. package/dist/vue.cjs.js +1 -1
  11. package/dist/vue.cjs.js.map +1 -1
  12. package/dist/vue.es.js +1 -0
  13. package/dist/vue.es.js.map +1 -1
  14. package/package.json +11 -2
  15. package/src/WhiteboardApp.ts +91 -20
  16. package/src/components/{PageControl.scss → PageControl/PageControl.scss} +0 -0
  17. package/src/components/PageControl/PageControl.tsx +110 -0
  18. package/src/components/PageControl/hooks.ts +70 -0
  19. package/src/components/PageControl/index.ts +2 -0
  20. package/src/components/PlayerControl/PlayerControl.tsx +7 -8
  21. package/src/components/PlayerControl/hooks.ts +3 -10
  22. package/src/components/PlayerControl/index.ts +1 -0
  23. package/src/components/{RedoUndo.scss → RedoUndo/RedoUndo.scss} +0 -0
  24. package/src/components/{RedoUndo.tsx → RedoUndo/RedoUndo.tsx} +13 -29
  25. package/src/components/RedoUndo/hooks.ts +50 -0
  26. package/src/components/RedoUndo/index.ts +2 -0
  27. package/src/components/Root.tsx +10 -6
  28. package/src/components/Toolbar/Content.tsx +4 -3
  29. package/src/components/Toolbar/Toolbar.scss +35 -1
  30. package/src/components/Toolbar/Toolbar.tsx +78 -28
  31. package/src/components/Toolbar/components/Mask.tsx +44 -0
  32. package/src/components/Toolbar/components/assets/collapsed.png +0 -0
  33. package/src/components/Toolbar/components/assets/expanded.png +0 -0
  34. package/src/components/Toolbar/hooks.ts +28 -29
  35. package/src/components/Toolbar/index.ts +1 -0
  36. package/src/components/{ZoomControl.scss → ZoomControl/ZoomControl.scss} +0 -0
  37. package/src/components/ZoomControl/ZoomControl.tsx +109 -0
  38. package/src/components/ZoomControl/hooks.ts +111 -0
  39. package/src/components/ZoomControl/index.ts +2 -0
  40. package/src/components/hooks.ts +80 -0
  41. package/src/index.ts +20 -5
  42. package/src/internal/Instance.tsx +31 -7
  43. package/src/internal/helpers.ts +44 -0
  44. package/src/internal/hooks.ts +9 -0
  45. package/src/react.tsx +52 -0
  46. package/src/style.scss +9 -3
  47. package/src/components/PageControl.tsx +0 -181
  48. package/src/components/ZoomControl.tsx +0 -221
  49. package/src/hooks.ts +0 -53
@@ -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,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;
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
@@ -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":"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"}
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
@@ -5,6 +5,7 @@ import "white-web-sdk";
5
5
  import "i18next";
6
6
  import "react";
7
7
  import "react-dom";
8
+ import "framer-motion";
8
9
  import "clsx";
9
10
  import "@tippyjs/react";
10
11
  import "rc-slider";
@@ -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":";;;;;;;;;;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;;"}
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"),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;
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
@@ -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":"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"}
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
@@ -5,6 +5,7 @@ import "white-web-sdk";
5
5
  import "i18next";
6
6
  import "react";
7
7
  import "react-dom";
8
+ import "framer-motion";
8
9
  import "clsx";
9
10
  import "@tippyjs/react";
10
11
  import "rc-slider";
@@ -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":";;;;;;;;;;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;;"}
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.7",
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 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"
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
  }
@@ -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 { WhiteboardAppConfig, InsertDocsParams };
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._target;
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._collector;
47
+ return this._instance.collector;
41
48
  }
42
49
 
43
- public bindElement(
44
- target?: HTMLElement | null,
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 insertDocs(params: InsertDocsParams) {
53
- return this._instance.insertDocs(params);
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() {
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./hooks";
2
+ export { name, PageControl, type PageControlProps } from "./PageControl";
@@ -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 { PlayerPhase } from "white-web-sdk";
8
- import { usePlayer } from "./hooks";
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 = usePlayer(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, useRef, useState } from "react";
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 useLastValue<T>(value: T) {
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) {
@@ -1 +1,2 @@
1
+ export * from "./hooks";
1
2
  export { PlayerControl, name, type PlayerControlProps } from "./PlayerControl";
@@ -1,13 +1,15 @@
1
- import type { CommonProps, GenericIcon } from "../types";
1
+ import type { CommonProps, GenericIcon } from "../../types";
2
2
 
3
3
  import clsx from "clsx";
4
- import React, { useCallback, useEffect, useState } from "react";
4
+ import React from "react";
5
5
  import Tippy from "@tippyjs/react";
6
6
 
7
- import { Icon } from "../icons";
8
- import { Undo } from "../icons/Undo";
9
- import { Redo } from "../icons/Redo";
10
- import { TopOffset } from "../theme";
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 [writable, setWritable] = useState(false);
26
- const [undoSteps, setUndoSteps] = useState(0);
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={useCallback(() => room && room.undo(), [room])}
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={useCallback(() => room && room.redo(), [room])}
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
+ }