@netless/fastboard 0.0.1 → 0.0.5
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 +8 -10
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +478 -168
- package/dist/index.es.js.map +1 -1
- package/dist/svelte.cjs.js +2 -0
- package/dist/svelte.cjs.js.map +1 -0
- package/dist/svelte.es.js +31 -0
- package/dist/svelte.es.js.map +1 -0
- package/dist/vue.cjs.js +2 -0
- package/dist/vue.cjs.js.map +1 -0
- package/dist/vue.es.js +42 -0
- package/dist/vue.es.js.map +1 -0
- package/package.json +25 -41
- package/src/WhiteboardApp.ts +39 -3
- package/src/behaviors/style.ts +1 -1
- package/src/components/PlayerControl/PlayerControl.scss +145 -0
- package/src/components/PlayerControl/PlayerControl.tsx +158 -0
- package/src/components/PlayerControl/components/Button.tsx +55 -0
- package/src/components/PlayerControl/hooks.ts +95 -0
- package/src/components/PlayerControl/icons/Loading.tsx +13 -0
- package/src/components/PlayerControl/icons/Pause.tsx +13 -0
- package/src/components/PlayerControl/icons/Play.tsx +13 -0
- package/src/components/PlayerControl/icons/index.ts +10 -0
- package/src/components/PlayerControl/index.ts +1 -0
- package/src/components/Root.tsx +1 -2
- package/src/components/Toolbar/Content.tsx +28 -15
- package/src/components/Toolbar/components/ColorBox.tsx +3 -3
- package/src/components/Toolbar/components/UpDownButtons.tsx +7 -10
- package/src/components/Toolbar/hooks.ts +1 -1
- package/src/components/ZoomControl.tsx +1 -1
- package/src/hooks.ts +18 -60
- package/src/i18n/en.json +2 -1
- package/src/i18n/zh-CN.json +2 -1
- package/src/index.ts +9 -2
- package/src/internal/Instance.tsx +40 -19
- package/src/internal/helpers.ts +15 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/mount-whiteboard.ts +13 -5
- package/src/style.scss +1 -0
- package/src/svelte.ts +45 -0
- package/src/vue.ts +74 -0
- package/src/helpers/index.ts +0 -18
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var a=require("svelte/store"),n=require("./index.cjs.js");require("@netless/window-manager");require("white-web-sdk");require("i18next");require("react");require("react-dom");require("clsx");require("@tippyjs/react");require("rc-slider");function o(t,i){return a.readable(null,s=>{let e=null;n.createWhiteboardApp(i).then(r=>{s(e=r)});const u=t.subscribe(r=>{r&&e&&e.bindElement(r)});return()=>{u(),e&&e.dispose()}})}exports.useFastboard=o;
|
|
2
|
+
//# sourceMappingURL=svelte.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.cjs.js","sources":["../src/svelte.ts"],"sourcesContent":["import { readable, type Readable } from \"svelte/store\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\n/**\n * @example\n * ```svelte\n * <script>\n * let ref = writable(null)\n * let app = useFastboard(ref, { sdkConfig, joinRoom })\n * if (app) {\n * app.insertDocs({...})\n * }\n * </script>\n * <div style=\"width: 100%; height: 100%\" bind:this={$ref} />\n * ```\n */\nexport function useFastboard(\n ref: Readable<HTMLElement | null>,\n config: FastBoardConfig\n): Readable<WhiteboardApp | null> {\n return readable<WhiteboardApp | null>(null, set => {\n let app_: WhiteboardApp | null = null;\n\n createWhiteboardApp(config).then(app => {\n set((app_ = app));\n });\n\n const dispose = ref.subscribe(div => {\n if (div && app_) {\n app_.bindElement(div);\n }\n });\n\n return () => {\n dispose();\n if (app_) {\n app_.dispose();\n }\n };\n });\n}\n"],"names":["readable"],"mappings":"kWAqBE,EACA,EACgC,OACzBA,YAA+B,KAAM,GAAO,IAC7C,GAA6B,2BAEb,GAAQ,KAAK,GAAO,GACjC,EAAO,UAGR,GAAU,EAAI,UAAU,GAAO,CAC/B,GAAO,KACJ,YAAY,WAId,IAAM,KAEP,KACG"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readable } from "svelte/store";
|
|
2
|
+
import { createWhiteboardApp } from "./index.es.js";
|
|
3
|
+
import "@netless/window-manager";
|
|
4
|
+
import "white-web-sdk";
|
|
5
|
+
import "i18next";
|
|
6
|
+
import "react";
|
|
7
|
+
import "react-dom";
|
|
8
|
+
import "clsx";
|
|
9
|
+
import "@tippyjs/react";
|
|
10
|
+
import "rc-slider";
|
|
11
|
+
function useFastboard(ref, config) {
|
|
12
|
+
return readable(null, (set) => {
|
|
13
|
+
let app_ = null;
|
|
14
|
+
createWhiteboardApp(config).then((app) => {
|
|
15
|
+
set(app_ = app);
|
|
16
|
+
});
|
|
17
|
+
const dispose = ref.subscribe((div) => {
|
|
18
|
+
if (div && app_) {
|
|
19
|
+
app_.bindElement(div);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return () => {
|
|
23
|
+
dispose();
|
|
24
|
+
if (app_) {
|
|
25
|
+
app_.dispose();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export { useFastboard };
|
|
31
|
+
//# sourceMappingURL=svelte.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte.es.js","sources":["../src/svelte.ts"],"sourcesContent":["import { readable, type Readable } from \"svelte/store\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\n/**\n * @example\n * ```svelte\n * <script>\n * let ref = writable(null)\n * let app = useFastboard(ref, { sdkConfig, joinRoom })\n * if (app) {\n * app.insertDocs({...})\n * }\n * </script>\n * <div style=\"width: 100%; height: 100%\" bind:this={$ref} />\n * ```\n */\nexport function useFastboard(\n ref: Readable<HTMLElement | null>,\n config: FastBoardConfig\n): Readable<WhiteboardApp | null> {\n return readable<WhiteboardApp | null>(null, set => {\n let app_: WhiteboardApp | null = null;\n\n createWhiteboardApp(config).then(app => {\n set((app_ = app));\n });\n\n const dispose = ref.subscribe(div => {\n if (div && app_) {\n app_.bindElement(div);\n }\n });\n\n return () => {\n dispose();\n if (app_) {\n app_.dispose();\n }\n };\n });\n}\n"],"names":[],"mappings":";;;;;;;;;;sBAqBE,KACA,QACgC;SACzB,SAA+B,MAAM,SAAO;QAC7C,OAA6B;wBAEb,QAAQ,KAAK,SAAO;UACjC,OAAO;AAAA;UAGR,UAAU,IAAI,UAAU,SAAO;UAC/B,OAAO,MAAM;aACV,YAAY;AAAA;AAAA;WAId,MAAM;;UAEP,MAAM;aACH;AAAA;AAAA;AAAA;AAAA;;"}
|
package/dist/vue.cjs.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports[Symbol.toStringTag]="Module";var t=require("vue-demi"),s=require("./index.cjs.js");require("@netless/window-manager");require("white-web-sdk");require("i18next");require("react");require("react-dom");require("clsx");require("@tippyjs/react");require("rc-slider");function n(u){var e;const r=t.unref(u);return(e=r==null?void 0:r.$el)!=null?e:r}function o(u){return t.getCurrentScope()?(t.onScopeDispose(u),!0):!1}function l(u,r){const e=t.shallowRef(null),i=t.computed(()=>n(u));return s.createWhiteboardApp(r).then(a=>{e.value=a}),t.watchEffect(()=>{i.value&&e.value&&e.value.bindElement(i.value)}),o(()=>{e.value&&e.value.dispose()}),e}exports.unrefElement=n;exports.useFastboard=l;
|
|
2
|
+
//# sourceMappingURL=vue.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.cjs.js","sources":["../src/vue.ts"],"sourcesContent":["import type { ComponentPublicInstance, Ref } from \"vue-demi\";\nimport {\n computed,\n getCurrentScope,\n onScopeDispose,\n shallowRef,\n unref,\n watchEffect,\n} from \"vue-demi\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\nexport type MaybeRef<T> = T | Ref<T>;\nexport type VueInstance = ComponentPublicInstance;\nexport type MaybeElementRef = MaybeRef<\n HTMLElement | SVGElement | VueInstance | undefined | null\n>;\n\n/**\n * Get the dom element of a ref of element or Vue component instance\n */\nexport function unrefElement(elRef: MaybeElementRef) {\n const plain = unref(elRef);\n return (plain as VueInstance)?.$el ?? plain;\n}\n\nfunction tryOnScopeDispose(fn: () => void) {\n if (getCurrentScope()) {\n onScopeDispose(fn);\n return true;\n }\n return false;\n}\n\n/**\n * @example\n * ```vue\n * <script setup>\n * const el = ref(null)\n * const app = useFastboard(el, { sdkConfig, joinRoom })\n * if (app.value) {\n * app.value.insertDocs({...})\n * }\n * </script>\n * <template>\n * <div style=\"width: 100%; height: 100%\" ref=\"el\"></div>\n * </template>\n * ```\n */\nexport function useFastboard(el: MaybeElementRef, config: FastBoardConfig) {\n const app = shallowRef<WhiteboardApp | null>(null);\n const target = computed(() => unrefElement(el));\n\n createWhiteboardApp(config).then(app_ => {\n app.value = app_;\n });\n\n watchEffect(() => {\n if (target.value && app.value) {\n app.value.bindElement(target.value);\n }\n });\n\n tryOnScopeDispose(() => {\n if (app.value) {\n app.value.dispose();\n }\n });\n\n return app;\n}\n"],"names":["unref","getCurrentScope","shallowRef","computed"],"mappings":"8VAwB6B,EAAwB,YAC7C,GAAQA,QAAM,SACZ,oBAAuB,MAAvB,OAA8B,EAGxC,WAA2B,EAAgB,OACrCC,uCACa,GACR,IAEF,cAkBoB,EAAqB,EAAyB,MACnE,GAAMC,aAAiC,MACvC,EAASC,WAAS,IAAM,EAAa,iCAEvB,GAAQ,KAAK,GAAQ,GACnC,MAAQ,kBAGF,IAAM,CACZ,EAAO,OAAS,EAAI,SAClB,MAAM,YAAY,EAAO,WAIf,IAAM,CAClB,EAAI,SACF,MAAM,YAIP"}
|
package/dist/vue.es.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { unref, shallowRef, computed, watchEffect, getCurrentScope, onScopeDispose } from "vue-demi";
|
|
2
|
+
import { createWhiteboardApp } from "./index.es.js";
|
|
3
|
+
import "@netless/window-manager";
|
|
4
|
+
import "white-web-sdk";
|
|
5
|
+
import "i18next";
|
|
6
|
+
import "react";
|
|
7
|
+
import "react-dom";
|
|
8
|
+
import "clsx";
|
|
9
|
+
import "@tippyjs/react";
|
|
10
|
+
import "rc-slider";
|
|
11
|
+
function unrefElement(elRef) {
|
|
12
|
+
var _a;
|
|
13
|
+
const plain = unref(elRef);
|
|
14
|
+
return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
|
|
15
|
+
}
|
|
16
|
+
function tryOnScopeDispose(fn) {
|
|
17
|
+
if (getCurrentScope()) {
|
|
18
|
+
onScopeDispose(fn);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function useFastboard(el, config) {
|
|
24
|
+
const app = shallowRef(null);
|
|
25
|
+
const target = computed(() => unrefElement(el));
|
|
26
|
+
createWhiteboardApp(config).then((app_) => {
|
|
27
|
+
app.value = app_;
|
|
28
|
+
});
|
|
29
|
+
watchEffect(() => {
|
|
30
|
+
if (target.value && app.value) {
|
|
31
|
+
app.value.bindElement(target.value);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
tryOnScopeDispose(() => {
|
|
35
|
+
if (app.value) {
|
|
36
|
+
app.value.dispose();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return app;
|
|
40
|
+
}
|
|
41
|
+
export { unrefElement, useFastboard };
|
|
42
|
+
//# sourceMappingURL=vue.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.es.js","sources":["../src/vue.ts"],"sourcesContent":["import type { ComponentPublicInstance, Ref } from \"vue-demi\";\nimport {\n computed,\n getCurrentScope,\n onScopeDispose,\n shallowRef,\n unref,\n watchEffect,\n} from \"vue-demi\";\nimport { createWhiteboardApp } from \"./index\";\n\nimport type { WhiteboardApp, WhiteboardAppConfig } from \"./WhiteboardApp\";\n\nexport type FastBoardConfig = WhiteboardAppConfig;\n\nexport type MaybeRef<T> = T | Ref<T>;\nexport type VueInstance = ComponentPublicInstance;\nexport type MaybeElementRef = MaybeRef<\n HTMLElement | SVGElement | VueInstance | undefined | null\n>;\n\n/**\n * Get the dom element of a ref of element or Vue component instance\n */\nexport function unrefElement(elRef: MaybeElementRef) {\n const plain = unref(elRef);\n return (plain as VueInstance)?.$el ?? plain;\n}\n\nfunction tryOnScopeDispose(fn: () => void) {\n if (getCurrentScope()) {\n onScopeDispose(fn);\n return true;\n }\n return false;\n}\n\n/**\n * @example\n * ```vue\n * <script setup>\n * const el = ref(null)\n * const app = useFastboard(el, { sdkConfig, joinRoom })\n * if (app.value) {\n * app.value.insertDocs({...})\n * }\n * </script>\n * <template>\n * <div style=\"width: 100%; height: 100%\" ref=\"el\"></div>\n * </template>\n * ```\n */\nexport function useFastboard(el: MaybeElementRef, config: FastBoardConfig) {\n const app = shallowRef<WhiteboardApp | null>(null);\n const target = computed(() => unrefElement(el));\n\n createWhiteboardApp(config).then(app_ => {\n app.value = app_;\n });\n\n watchEffect(() => {\n if (target.value && app.value) {\n app.value.bindElement(target.value);\n }\n });\n\n tryOnScopeDispose(() => {\n if (app.value) {\n app.value.dispose();\n }\n });\n\n return app;\n}\n"],"names":[],"mappings":";;;;;;;;;;sBAwB6B,OAAwB;;QAC7C,QAAQ,MAAM;SACZ,qCAAuB,QAAvB,YAA8B;AAAA;AAGxC,2BAA2B,IAAgB;MACrC,mBAAmB;mBACN;WACR;AAAA;SAEF;AAAA;sBAkBoB,IAAqB,QAAyB;QACnE,MAAM,WAAiC;QACvC,SAAS,SAAS,MAAM,aAAa;sBAEvB,QAAQ,KAAK,UAAQ;QACnC,QAAQ;AAAA;cAGF,MAAM;QACZ,OAAO,SAAS,IAAI,OAAO;UACzB,MAAM,YAAY,OAAO;AAAA;AAAA;oBAIf,MAAM;QAClB,IAAI,OAAO;UACT,MAAM;AAAA;AAAA;SAIP;AAAA;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/fastboard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "An open sourced sdk based on white-web-sdk.",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"default": "./dist/index.es.js",
|
|
15
15
|
"types": "./src/index.ts"
|
|
16
16
|
},
|
|
17
|
+
"./vue": {
|
|
18
|
+
"node": {
|
|
19
|
+
"import": "./dist/vue.es.js",
|
|
20
|
+
"require": "./dist/vue.cjs.js"
|
|
21
|
+
},
|
|
22
|
+
"default": "./dist/vue.es.js",
|
|
23
|
+
"types": "./src/vue.ts"
|
|
24
|
+
},
|
|
17
25
|
"./package.json": "./package.json"
|
|
18
26
|
},
|
|
19
27
|
"files": [
|
|
@@ -37,8 +45,11 @@
|
|
|
37
45
|
"@netless/app-monaco": "*",
|
|
38
46
|
"@netless/app-slide": "*",
|
|
39
47
|
"@netless/window-manager": "*",
|
|
48
|
+
"@vue/composition-api": "*",
|
|
40
49
|
"react": "*",
|
|
41
50
|
"react-dom": "*",
|
|
51
|
+
"svelte": "*",
|
|
52
|
+
"vue": "*",
|
|
42
53
|
"white-web-sdk": "*"
|
|
43
54
|
},
|
|
44
55
|
"peerDependenciesMeta": {
|
|
@@ -53,51 +64,24 @@
|
|
|
53
64
|
},
|
|
54
65
|
"@netless/app-geogebra": {
|
|
55
66
|
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@vue/composition-api": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"svelte": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"vue": {
|
|
75
|
+
"optional": true
|
|
56
76
|
}
|
|
57
77
|
},
|
|
58
78
|
"dependencies": {
|
|
59
79
|
"@tippyjs/react": "^4.2.6",
|
|
60
80
|
"clsx": "^1.1.1",
|
|
61
|
-
"i18next": "^21.6.
|
|
81
|
+
"i18next": "^21.6.5",
|
|
62
82
|
"rc-slider": "^9.7.5",
|
|
63
|
-
"tippy.js": "^6.3.7"
|
|
64
|
-
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@hyrious/esbuild-dev": "^0.7.1",
|
|
67
|
-
"@hyrious/rimraf": "^0.1.0",
|
|
68
|
-
"@netless/app-countdown": "^0.0.1",
|
|
69
|
-
"@netless/app-geogebra": "^0.0.2",
|
|
70
|
-
"@netless/app-monaco": "^0.1.12",
|
|
71
|
-
"@netless/app-slide": "^0.0.52",
|
|
72
|
-
"@netless/window-manager": "^0.3.16",
|
|
73
|
-
"@types/node": "^17.0.4",
|
|
74
|
-
"@types/react": "^17.0.38",
|
|
75
|
-
"@types/react-dom": "^17.0.11",
|
|
76
|
-
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
|
77
|
-
"@typescript-eslint/parser": "^5.8.0",
|
|
78
|
-
"@vitejs/plugin-react": "^1.1.3",
|
|
79
|
-
"esbuild": "^0.14.8",
|
|
80
|
-
"eslint": "^8.5.0",
|
|
81
|
-
"eslint-define-config": "^1.2.0",
|
|
82
|
-
"eslint-plugin-react-hooks": "^4.3.0",
|
|
83
|
-
"prettier": "2.5.1",
|
|
84
|
-
"react": "^17.0.2",
|
|
85
|
-
"react-dom": "^17.0.2",
|
|
86
|
-
"rollup": "^2.61.1",
|
|
87
|
-
"rollup-plugin-visualizer": "^5.5.2",
|
|
88
|
-
"sass": "^1.45.1",
|
|
89
|
-
"type-fest": "^2.8.0",
|
|
90
|
-
"typescript": "^4.5.4",
|
|
91
|
-
"vite": "^2.7.6",
|
|
92
|
-
"white-web-sdk": "^2.15.13"
|
|
93
|
-
},
|
|
94
|
-
"scripts": {
|
|
95
|
-
"preinstall": "npx only-allow pnpm",
|
|
96
|
-
"format": "prettier --write .",
|
|
97
|
-
"lint": "eslint --ext .ts,.tsx .",
|
|
98
|
-
"dev": "vite dev --host",
|
|
99
|
-
"build": "vite build",
|
|
100
|
-
"build:dev": "vite build --mode development"
|
|
83
|
+
"tippy.js": "^6.3.7",
|
|
84
|
+
"vue-demi": "^0.12.1"
|
|
101
85
|
},
|
|
102
|
-
"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
|
|
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"
|
|
103
87
|
}
|
package/src/WhiteboardApp.ts
CHANGED
|
@@ -10,18 +10,54 @@ export type { WhiteboardAppConfig, InsertDocsParams };
|
|
|
10
10
|
export class WhiteboardApp {
|
|
11
11
|
private readonly _instance: Instance;
|
|
12
12
|
|
|
13
|
+
private _target: HTMLElement | null = null;
|
|
14
|
+
public get target(): HTMLElement | null {
|
|
15
|
+
return this._target;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
constructor(readonly config: WhiteboardAppConfig) {
|
|
14
19
|
this._instance = new Instance(config);
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
get room() {
|
|
23
|
+
return this._instance.room;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get manager() {
|
|
27
|
+
return this._instance.manager;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get sdk() {
|
|
31
|
+
return this._instance.sdk;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get i18n() {
|
|
35
|
+
return this._instance.i18n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public bindElement(target?: HTMLElement | null) {
|
|
39
|
+
this._target = target || null;
|
|
40
|
+
this._instance.target = target || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
public insertDocs(params: InsertDocsParams) {
|
|
18
44
|
return this._instance.insertDocs(params);
|
|
19
45
|
}
|
|
20
46
|
|
|
47
|
+
public insertCodeEditor() {
|
|
48
|
+
return this._instance.insertCodeEditor();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public insertGeoGebra() {
|
|
52
|
+
return this._instance.insertGeoGebra();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public insertCountdown() {
|
|
56
|
+
return this._instance.insertCountdown();
|
|
57
|
+
}
|
|
58
|
+
|
|
21
59
|
public changeLanguage(language: Language) {
|
|
22
|
-
return this._instance.changeLanguage(language)
|
|
23
|
-
this._instance.forceUpdate();
|
|
24
|
-
});
|
|
60
|
+
return this._instance.changeLanguage(language);
|
|
25
61
|
}
|
|
26
62
|
|
|
27
63
|
public dispose() {
|
package/src/behaviors/style.ts
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
$name: "fastboard-player-control";
|
|
2
|
+
|
|
3
|
+
.#{$name} {
|
|
4
|
+
width: 100%;
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: 4px;
|
|
8
|
+
padding: 4px;
|
|
9
|
+
border-radius: 4px;
|
|
10
|
+
backdrop-filter: blur(2px);
|
|
11
|
+
-webkit-backdrop-filter: blur(2px);
|
|
12
|
+
|
|
13
|
+
&.auto-hide {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
transition: opacity 0.2s;
|
|
16
|
+
|
|
17
|
+
&:hover {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rc-slider-disabled {
|
|
23
|
+
background: transparent;
|
|
24
|
+
opacity: 0.5;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.rc-slider-rail,
|
|
28
|
+
.rc-slider-track {
|
|
29
|
+
height: 2px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.tippy-content {
|
|
33
|
+
padding: 8px;
|
|
34
|
+
}
|
|
35
|
+
.tippy-box {
|
|
36
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
37
|
+
background-color: rgba($color: #333, $alpha: 0.95);
|
|
38
|
+
backdrop-filter: blur(2px);
|
|
39
|
+
-webkit-backdrop-filter: blur(2px);
|
|
40
|
+
}
|
|
41
|
+
.tippy-box[data-theme~="light"] {
|
|
42
|
+
background-color: rgba($color: #fff, $alpha: 0.95);
|
|
43
|
+
box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.25);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&.light {
|
|
47
|
+
color: #333;
|
|
48
|
+
background-color: rgba($color: #fff, $alpha: 0.85);
|
|
49
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&.dark {
|
|
53
|
+
color: #ddd;
|
|
54
|
+
background-color: rgba($color: #333, $alpha: 0.85);
|
|
55
|
+
border: 1px solid rgba(0, 0, 0, 0.45);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.#{$name}-btn {
|
|
60
|
+
appearance: none;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
margin: 0;
|
|
63
|
+
border: 0;
|
|
64
|
+
padding: 0;
|
|
65
|
+
min-width: 24px;
|
|
66
|
+
height: 24px;
|
|
67
|
+
background-color: transparent;
|
|
68
|
+
border-radius: 4px;
|
|
69
|
+
font-size: 24px;
|
|
70
|
+
line-height: 1;
|
|
71
|
+
display: inline-flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
|
|
75
|
+
svg,
|
|
76
|
+
img {
|
|
77
|
+
width: 1em;
|
|
78
|
+
height: 1em;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:disabled {
|
|
82
|
+
opacity: 0.5;
|
|
83
|
+
cursor: not-allowed;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&.light:not(:disabled):hover {
|
|
87
|
+
background-color: rgba(51, 129, 255, 0.1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&.dark:not(:disabled):hover {
|
|
91
|
+
background-color: rgba(51, 129, 255, 0.25);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&.loading {
|
|
95
|
+
animation: fastboard-player-control-rotate 0.5s linear infinite;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes fastboard-player-control-rotate {
|
|
99
|
+
100% {
|
|
100
|
+
transform: rotate(360deg);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.#{$name}-panel {
|
|
106
|
+
padding: 0;
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-flow: column nowrap;
|
|
109
|
+
align-items: stretch;
|
|
110
|
+
gap: 4px;
|
|
111
|
+
|
|
112
|
+
.#{$name}-btn {
|
|
113
|
+
width: initial;
|
|
114
|
+
height: initial;
|
|
115
|
+
user-select: none;
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
padding: 4px;
|
|
118
|
+
justify-content: flex-end;
|
|
119
|
+
|
|
120
|
+
&.active {
|
|
121
|
+
color: #3381ff;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.#{$name}-slider {
|
|
127
|
+
width: 100%;
|
|
128
|
+
padding: 0 7px;
|
|
129
|
+
|
|
130
|
+
&.loading {
|
|
131
|
+
cursor: not-allowed;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.#{$name}-slash {
|
|
136
|
+
opacity: 0.6;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.#{$name}-current,
|
|
140
|
+
.#{$name}-slash,
|
|
141
|
+
.#{$name}-total,
|
|
142
|
+
.#{$name}-speed-text {
|
|
143
|
+
font-size: 12px;
|
|
144
|
+
font-variant-numeric: tabular-nums;
|
|
145
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { Player } from "white-web-sdk";
|
|
2
|
+
import type { CommonProps, GenericIcon } from "../../types";
|
|
3
|
+
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import React, { useEffect, useState } from "react";
|
|
6
|
+
import RcSlider from "rc-slider";
|
|
7
|
+
import { PlayerPhase } from "white-web-sdk";
|
|
8
|
+
import { usePlayer } from "./hooks";
|
|
9
|
+
import { Icon } from "../../icons";
|
|
10
|
+
import { themes, TopOffset } from "../../theme";
|
|
11
|
+
import { Icons } from "./icons";
|
|
12
|
+
import Tippy from "@tippyjs/react";
|
|
13
|
+
import { Button } from "./components/Button";
|
|
14
|
+
|
|
15
|
+
export type PlayerControlProps = {
|
|
16
|
+
autoHide?: boolean;
|
|
17
|
+
player?: Player;
|
|
18
|
+
} & Omit<CommonProps, "room"> &
|
|
19
|
+
GenericIcon<"play" | "pause" | "loading">;
|
|
20
|
+
|
|
21
|
+
export const name = "fastboard-player-control";
|
|
22
|
+
|
|
23
|
+
export function PlayerControl({
|
|
24
|
+
autoHide = false,
|
|
25
|
+
player: player_,
|
|
26
|
+
theme = "light",
|
|
27
|
+
i18n,
|
|
28
|
+
...icons
|
|
29
|
+
}: PlayerControlProps) {
|
|
30
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
31
|
+
const player = usePlayer(player_);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setCurrentTime(player.currentTime);
|
|
35
|
+
}, [player.currentTime]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (player.currentTime !== currentTime) {
|
|
39
|
+
player.seekToProgressTime(currentTime);
|
|
40
|
+
}
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, [currentTime]);
|
|
43
|
+
|
|
44
|
+
const isLoading =
|
|
45
|
+
player.phase === PlayerPhase.WaitingFirstFrame ||
|
|
46
|
+
player.phase === PlayerPhase.Buffering;
|
|
47
|
+
const isPlaying = player.phase === PlayerPhase.Playing;
|
|
48
|
+
|
|
49
|
+
const { activeColor } = themes[theme];
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={clsx(name, theme, { "auto-hide": autoHide })}>
|
|
53
|
+
<button
|
|
54
|
+
className={clsx(
|
|
55
|
+
`${name}-btn`,
|
|
56
|
+
isLoading ? "loading" : isPlaying ? "pause" : "play",
|
|
57
|
+
theme
|
|
58
|
+
)}
|
|
59
|
+
disabled={isLoading}
|
|
60
|
+
onClick={player.togglePlay}
|
|
61
|
+
>
|
|
62
|
+
<Icon
|
|
63
|
+
fallback={
|
|
64
|
+
isLoading ? (
|
|
65
|
+
<Icons.Loading theme={theme} />
|
|
66
|
+
) : isPlaying ? (
|
|
67
|
+
<Icons.Pause theme={theme} />
|
|
68
|
+
) : (
|
|
69
|
+
<Icons.Play theme={theme} />
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
src={
|
|
73
|
+
isLoading
|
|
74
|
+
? icons.loadingIcon
|
|
75
|
+
: isPlaying
|
|
76
|
+
? icons.pauseIcon
|
|
77
|
+
: icons.playIcon
|
|
78
|
+
}
|
|
79
|
+
alt={isLoading ? "[loading]" : isPlaying ? "[pause]" : "[play]"}
|
|
80
|
+
/>
|
|
81
|
+
</button>
|
|
82
|
+
<span className={clsx(`${name}-slider`, { loading: isLoading }, theme)}>
|
|
83
|
+
<RcSlider
|
|
84
|
+
disabled={isLoading}
|
|
85
|
+
trackStyle={{ background: activeColor }}
|
|
86
|
+
handleStyle={{ border: `1px solid ${activeColor}` }}
|
|
87
|
+
value={currentTime}
|
|
88
|
+
onChange={setCurrentTime}
|
|
89
|
+
min={0}
|
|
90
|
+
max={player.totalTime}
|
|
91
|
+
step={100}
|
|
92
|
+
/>
|
|
93
|
+
</span>
|
|
94
|
+
<span className={clsx(`${name}-current`, theme)}>
|
|
95
|
+
{renderTime(player.currentTime)}
|
|
96
|
+
</span>
|
|
97
|
+
<span className={clsx(`${name}-slash`, theme)}>/</span>
|
|
98
|
+
<span className={clsx(`${name}-total`, theme)}>
|
|
99
|
+
{renderTime(player.totalTime)}
|
|
100
|
+
</span>
|
|
101
|
+
<span className={`${name}-btn-interactive`}>
|
|
102
|
+
<Tippy
|
|
103
|
+
className="fastboard-tip"
|
|
104
|
+
content={renderSpeeds(player)}
|
|
105
|
+
theme={theme}
|
|
106
|
+
placement="top-end"
|
|
107
|
+
trigger="click"
|
|
108
|
+
offset={TopOffset}
|
|
109
|
+
arrow={false}
|
|
110
|
+
interactive
|
|
111
|
+
>
|
|
112
|
+
<Button content={i18n?.t("speed")} theme={theme} disabled={isLoading}>
|
|
113
|
+
<span className={clsx(`${name}-speed-text`, theme)}>
|
|
114
|
+
{player.speed}x
|
|
115
|
+
</span>
|
|
116
|
+
</Button>
|
|
117
|
+
</Tippy>
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderTime(ms: number) {
|
|
124
|
+
let seconds = ms / 1000;
|
|
125
|
+
const minutes = Math.floor(seconds / 60);
|
|
126
|
+
seconds = Math.floor(seconds) % 60;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
`${String(minutes).padStart(2, "0")}` +
|
|
130
|
+
`:${String(seconds).padStart(2, "0")}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const Speeds = [2.0, 1.5, 1.25, 1.0, 0.75, 0.5];
|
|
135
|
+
|
|
136
|
+
function renderSpeeds({
|
|
137
|
+
speed: current,
|
|
138
|
+
setSpeed,
|
|
139
|
+
}: {
|
|
140
|
+
speed: number;
|
|
141
|
+
setSpeed: (speed: number) => void;
|
|
142
|
+
}) {
|
|
143
|
+
return (
|
|
144
|
+
<div className={clsx(`${name}-panel`, "speed")}>
|
|
145
|
+
{Speeds.map(speed => (
|
|
146
|
+
<button
|
|
147
|
+
className={clsx(`${name}-btn`, "speed", {
|
|
148
|
+
active: speed === current,
|
|
149
|
+
})}
|
|
150
|
+
key={speed}
|
|
151
|
+
onClick={() => setSpeed(speed)}
|
|
152
|
+
>
|
|
153
|
+
{speed}x
|
|
154
|
+
</button>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Placement } from "tippy.js";
|
|
2
|
+
import type { Theme } from "../../../types";
|
|
3
|
+
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import React, { forwardRef, type PropsWithChildren } from "react";
|
|
6
|
+
import Tippy from "@tippyjs/react";
|
|
7
|
+
|
|
8
|
+
import { TopOffset } from "../../../theme";
|
|
9
|
+
|
|
10
|
+
type ButtonProps = PropsWithChildren<{
|
|
11
|
+
theme: Theme;
|
|
12
|
+
content: React.ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
active?: boolean;
|
|
15
|
+
onClick?: () => void;
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
placement?: Placement;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
21
|
+
(props, ref) => {
|
|
22
|
+
const {
|
|
23
|
+
theme,
|
|
24
|
+
content,
|
|
25
|
+
disabled,
|
|
26
|
+
active,
|
|
27
|
+
onClick,
|
|
28
|
+
interactive,
|
|
29
|
+
placement = "top",
|
|
30
|
+
children,
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Tippy
|
|
35
|
+
className="fastboard-tip"
|
|
36
|
+
content={content}
|
|
37
|
+
interactive={interactive}
|
|
38
|
+
theme={theme}
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
placement={placement}
|
|
41
|
+
offset={TopOffset}
|
|
42
|
+
duration={300}
|
|
43
|
+
>
|
|
44
|
+
<button
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={clsx("fastboard-player-control-btn", theme, { active })}
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
disabled={disabled}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</button>
|
|
52
|
+
</Tippy>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
);
|