@netless/fastboard 0.0.10 → 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 +11 -1
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +527 -365
- 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 +3 -2
- 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 +1 -1
- 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 +19 -4
- package/src/style.scss +3 -3
- package/src/components/PageControl.tsx +0 -181
- package/src/components/ZoomControl.tsx +0 -221
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",
|
|
@@ -86,10 +86,11 @@
|
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@tippyjs/react": "^4.2.6",
|
|
88
88
|
"clsx": "^1.1.1",
|
|
89
|
+
"framer-motion": "^5.5.6",
|
|
89
90
|
"i18next": "^21.6.5",
|
|
90
91
|
"rc-slider": "^9.7.5",
|
|
91
92
|
"tippy.js": "^6.3.7",
|
|
92
93
|
"vue-demi": "^0.12.1"
|
|
93
94
|
},
|
|
94
|
-
"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
|
|
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"
|
|
95
96
|
}
|
|
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);
|
|
@@ -13,7 +13,7 @@ function useForceUpdate() {
|
|
|
13
13
|
return useCallback(() => forceUpdate_({}), EMPTY_ARRAY);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function
|
|
16
|
+
export function usePlayerControl(player?: Player | null) {
|
|
17
17
|
const togglePlay = useCallback(() => {
|
|
18
18
|
if (player) {
|
|
19
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
|
+
}
|
package/src/components/Root.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { Toolbar } from "./Toolbar";
|
|
|
5
5
|
import { RedoUndo } from "./RedoUndo";
|
|
6
6
|
import { ZoomControl } from "./ZoomControl";
|
|
7
7
|
import { PageControl } from "./PageControl";
|
|
8
|
+
import { useHideControls } from "./hooks";
|
|
8
9
|
|
|
9
10
|
export interface RootProps {
|
|
10
11
|
instance: Instance;
|
|
@@ -21,12 +22,8 @@ export function Root({ instance: app }: RootProps) {
|
|
|
21
22
|
[app, mux]
|
|
22
23
|
);
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
RedoUndo: redo_undo = true,
|
|
27
|
-
ZoomControl: zoom_control = true,
|
|
28
|
-
PageControl: page_control = true,
|
|
29
|
-
} = app.config.layout || {};
|
|
25
|
+
const hideControls = useHideControls(app.manager);
|
|
26
|
+
const showControls = !hideControls;
|
|
30
27
|
|
|
31
28
|
const props = {
|
|
32
29
|
room: app.room,
|
|
@@ -34,6 +31,13 @@ export function Root({ instance: app }: RootProps) {
|
|
|
34
31
|
i18n: app.i18n,
|
|
35
32
|
};
|
|
36
33
|
|
|
34
|
+
const {
|
|
35
|
+
Toolbar: toolbar = showControls || hideControls === "toolbar-only",
|
|
36
|
+
RedoUndo: redo_undo = showControls,
|
|
37
|
+
ZoomControl: zoom_control = showControls,
|
|
38
|
+
PageControl: page_control = showControls,
|
|
39
|
+
} = app.config.layout || {};
|
|
40
|
+
|
|
37
41
|
return (
|
|
38
42
|
<Instance.Context.Provider value={app}>
|
|
39
43
|
<div className="fastboard-root">
|
|
@@ -14,8 +14,9 @@ import { AppsButton } from "./components/AppsButton";
|
|
|
14
14
|
import { PencilButton } from "./components/PencilButton";
|
|
15
15
|
import { TextButton } from "./components/TextButton";
|
|
16
16
|
import { ShapesButton } from "./components/ShapesButton";
|
|
17
|
+
import clsx from "clsx";
|
|
17
18
|
|
|
18
|
-
export
|
|
19
|
+
export const Content = React.memo(() => {
|
|
19
20
|
const app = useInstance();
|
|
20
21
|
const ref = useRef<HTMLDivElement>(null);
|
|
21
22
|
const [scrollTop, setScrollTop] = useState(0);
|
|
@@ -65,7 +66,7 @@ export function Content() {
|
|
|
65
66
|
)}
|
|
66
67
|
<div
|
|
67
68
|
ref={ref}
|
|
68
|
-
className={`${name}-section`}
|
|
69
|
+
className={clsx(`${name}-section`)}
|
|
69
70
|
style={{
|
|
70
71
|
height: `${sectionHeight}px`,
|
|
71
72
|
overflow: needScroll ? "hidden" : "visible",
|
|
@@ -90,4 +91,4 @@ export function Content() {
|
|
|
90
91
|
)}
|
|
91
92
|
</>
|
|
92
93
|
);
|
|
93
|
-
}
|
|
94
|
+
});
|
|
@@ -40,10 +40,23 @@ $name: "fastboard-toolbar";
|
|
|
40
40
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
&.expanded {
|
|
44
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
&.dark {
|
|
44
48
|
color: #ddd;
|
|
45
49
|
background-color: rgba($color: #333, $alpha: 0.85);
|
|
46
|
-
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&.expanded:hover {
|
|
53
|
+
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.25);
|
|
54
|
+
transform: translate3d(0, 0, 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.collapsed {
|
|
58
|
+
padding: 0;
|
|
59
|
+
background-color: transparent;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
&-tooltip {
|
|
@@ -132,6 +145,11 @@ $name: "fastboard-toolbar";
|
|
|
132
145
|
flex-flow: column nowrap;
|
|
133
146
|
gap: 4px;
|
|
134
147
|
scroll-behavior: smooth;
|
|
148
|
+
|
|
149
|
+
&.collapsed {
|
|
150
|
+
transform: translateX(-100%);
|
|
151
|
+
transition: 1s transform;
|
|
152
|
+
}
|
|
135
153
|
}
|
|
136
154
|
|
|
137
155
|
&-panel {
|
|
@@ -244,4 +262,20 @@ $name: "fastboard-toolbar";
|
|
|
244
262
|
outline-offset: 2px;
|
|
245
263
|
}
|
|
246
264
|
}
|
|
265
|
+
|
|
266
|
+
&-mask-btn {
|
|
267
|
+
width: 17px;
|
|
268
|
+
height: 62px;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
&.dark {
|
|
271
|
+
filter: invert(0.8);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
&-expand-btn {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
position: absolute;
|
|
279
|
+
left: 0;
|
|
280
|
+
}
|
|
247
281
|
}
|