@netless/fastboard-react 0.2.5 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +349 -288
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +290 -229
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -14
- package/src/components/Fastboard.scss +1 -1
- package/src/components/Fastboard.tsx +9 -1
- package/src/components/PageControl/PageControl.scss +15 -11
- package/src/components/PageControl/PageControl.tsx +15 -17
- package/src/components/PlayerControl/PlayerControl.scss +12 -12
- package/src/components/RedoUndo/RedoUndo.scss +10 -10
- package/src/components/Toolbar/Toolbar.scss +23 -12
- package/src/components/Toolbar/components/AppsButton.tsx +29 -5
- package/src/components/Toolbar/components/ShapesButton.tsx +12 -5
- package/src/components/Toolbar/hooks.ts +22 -1
- package/src/components/Toolbar/icons/Laser.tsx +21 -0
- package/src/components/ZoomControl/ZoomControl.scss +15 -11
- package/src/components/ZoomControl/ZoomControl.tsx +4 -2
- package/src/components/ZoomControl/hooks.ts +2 -4
- package/src/icons/Left.tsx +15 -0
- package/src/icons/Minus.tsx +2 -2
- package/src/icons/Plus.tsx +2 -2
- package/src/icons/Redo.tsx +6 -5
- package/src/icons/Reset.tsx +4 -6
- package/src/icons/Right.tsx +15 -0
- package/src/icons/Undo.tsx +6 -5
- package/src/icons/WhiteboardAdd.tsx +26 -0
- package/src/vanilla/index.tsx +14 -4
- package/src/icons/ChevronLeft.tsx +0 -21
- package/src/icons/ChevronRight.tsx +0 -21
- package/src/icons/FilePlus.tsx +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/fastboard-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "A UI kit built on top of @netless/fastboard.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@tippyjs/react": "^4.2.6",
|
|
12
12
|
"clsx": "^1.1.1",
|
|
13
|
-
"framer-motion": "^6.2.
|
|
14
|
-
"i18next": "^21.6.
|
|
13
|
+
"framer-motion": "^6.2.8",
|
|
14
|
+
"i18next": "^21.6.12",
|
|
15
15
|
"rc-slider": "^9.7.5"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
@@ -22,19 +22,14 @@
|
|
|
22
22
|
"white-web-sdk": ">=2.16.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@netless/fastboard-core": "0.2.
|
|
26
|
-
"@netless/window-manager": "^0.4.
|
|
27
|
-
"@types/react": "^17.0.
|
|
25
|
+
"@netless/fastboard-core": "0.2.8",
|
|
26
|
+
"@netless/window-manager": "^0.4.7",
|
|
27
|
+
"@types/react": "^17.0.39",
|
|
28
28
|
"@types/react-dom": "^17.0.11",
|
|
29
|
-
"sass": "^1.49.
|
|
29
|
+
"sass": "^1.49.8",
|
|
30
30
|
"tippy.js": "^6.3.7",
|
|
31
|
-
"tsup": "^5.11.
|
|
32
|
-
"white-web-sdk": "^2.16.
|
|
33
|
-
},
|
|
34
|
-
"publishConfig": {
|
|
35
|
-
"main": "dist/index.js",
|
|
36
|
-
"module": "dist/index.mjs",
|
|
37
|
-
"types": "src/index.ts"
|
|
31
|
+
"tsup": "^5.11.13",
|
|
32
|
+
"white-web-sdk": "^2.16.10"
|
|
38
33
|
},
|
|
39
34
|
"scripts": {
|
|
40
35
|
"build": "tsup",
|
|
@@ -77,7 +77,11 @@ function FastboardInternal({
|
|
|
77
77
|
<ThemeContext.Provider value={theme}>
|
|
78
78
|
<I18nContext.Provider value={i18n}>
|
|
79
79
|
<div {...restProps} className="fastboard-root" ref={forwardedRef}>
|
|
80
|
-
<div
|
|
80
|
+
<div
|
|
81
|
+
className="fastboard-view"
|
|
82
|
+
ref={useWhiteboard}
|
|
83
|
+
onPointerDownCapture={focusThisElementImmediate}
|
|
84
|
+
/>
|
|
81
85
|
{children ? (
|
|
82
86
|
children
|
|
83
87
|
) : (
|
|
@@ -105,3 +109,7 @@ function FastboardInternal({
|
|
|
105
109
|
</ThemeContext.Provider>
|
|
106
110
|
);
|
|
107
111
|
}
|
|
112
|
+
|
|
113
|
+
function focusThisElementImmediate(ev: React.PointerEvent<HTMLDivElement>) {
|
|
114
|
+
ev.currentTarget.focus();
|
|
115
|
+
}
|
|
@@ -6,19 +6,19 @@ $name: "fastboard-page-control";
|
|
|
6
6
|
gap: 4px;
|
|
7
7
|
padding: 4px;
|
|
8
8
|
border-radius: 4px;
|
|
9
|
-
backdrop-filter: blur(
|
|
10
|
-
-webkit-backdrop-filter: blur(
|
|
9
|
+
backdrop-filter: blur(5px);
|
|
10
|
+
-webkit-backdrop-filter: blur(5px);
|
|
11
11
|
|
|
12
12
|
&.light {
|
|
13
13
|
color: #333;
|
|
14
|
-
background-color: rgba(
|
|
15
|
-
border: 1px solid
|
|
14
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
15
|
+
border: 1px solid #e5e8f0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
&.dark {
|
|
19
19
|
color: #ddd;
|
|
20
|
-
background-color:
|
|
21
|
-
border: 1px solid
|
|
20
|
+
background-color: #14181e;
|
|
21
|
+
border: 1px solid #383b42;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -37,8 +37,8 @@ $name: "fastboard-page-control";
|
|
|
37
37
|
|
|
38
38
|
svg,
|
|
39
39
|
img {
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
&:disabled {
|
|
@@ -47,11 +47,11 @@ $name: "fastboard-page-control";
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
&.light:not(:disabled):hover {
|
|
50
|
-
background-color:
|
|
50
|
+
background-color: #ebf2ff;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
&.dark:not(:disabled):hover {
|
|
54
|
-
background-color:
|
|
54
|
+
background-color: #383b42;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -72,9 +72,13 @@ $name: "fastboard-page-control";
|
|
|
72
72
|
opacity: 0.6;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
.#{$name}-text {
|
|
76
|
+
line-height: 24px;
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
.#{$name}-page,
|
|
76
80
|
.#{$name}-slash,
|
|
77
81
|
.#{$name}-page-count {
|
|
78
|
-
font-size:
|
|
82
|
+
font-size: 14px;
|
|
79
83
|
font-variant-numeric: tabular-nums;
|
|
80
84
|
}
|
|
@@ -6,9 +6,9 @@ import React from "react";
|
|
|
6
6
|
|
|
7
7
|
import { useTranslation } from "../../i18n";
|
|
8
8
|
import { Icon } from "../../icons";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { Left } from "../../icons/Left";
|
|
10
|
+
import { Right } from "../../icons/Right";
|
|
11
|
+
import { WhiteboardAdd } from "../../icons/WhiteboardAdd";
|
|
12
12
|
import { TopOffset } from "../../theme";
|
|
13
13
|
import { useTheme, useWritable } from "../hooks";
|
|
14
14
|
import { usePageControl } from "./hooks";
|
|
@@ -52,16 +52,14 @@ export function PageControl({
|
|
|
52
52
|
disabled={disabled || pageIndex === 0}
|
|
53
53
|
onClick={actions.prevPage}
|
|
54
54
|
>
|
|
55
|
-
<Icon
|
|
56
|
-
fallback={<ChevronLeft theme={theme} />}
|
|
57
|
-
src={disabled ? prevIconDisable : prevIcon}
|
|
58
|
-
alt="[prev]"
|
|
59
|
-
/>
|
|
55
|
+
<Icon fallback={<Left theme={theme} />} src={disabled ? prevIconDisable : prevIcon} alt="[prev]" />
|
|
60
56
|
</button>
|
|
61
57
|
</Tippy>
|
|
62
|
-
<span className={clsx(`${name}-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
<span className={clsx(`${name}-text`, theme)}>
|
|
59
|
+
<span className={clsx(`${name}-page`, theme)}>{pageCount === 0 ? "\u2026" : pageIndex + 1}</span>
|
|
60
|
+
<span className={clsx(`${name}-slash`, theme)}>/</span>
|
|
61
|
+
<span className={clsx(`${name}-page-count`, theme)}>{pageCount}</span>
|
|
62
|
+
</span>
|
|
65
63
|
<Tippy
|
|
66
64
|
className="fastboard-tip"
|
|
67
65
|
content={t("nextPage")}
|
|
@@ -77,11 +75,7 @@ export function PageControl({
|
|
|
77
75
|
disabled={disabled || pageIndex === pageCount - 1}
|
|
78
76
|
onClick={actions.nextPage}
|
|
79
77
|
>
|
|
80
|
-
<Icon
|
|
81
|
-
fallback={<ChevronRight theme={theme} />}
|
|
82
|
-
src={disabled ? nextIconDisable : nextIcon}
|
|
83
|
-
alt="[next]"
|
|
84
|
-
/>
|
|
78
|
+
<Icon fallback={<Right theme={theme} />} src={disabled ? nextIconDisable : nextIcon} alt="[next]" />
|
|
85
79
|
</button>
|
|
86
80
|
</Tippy>
|
|
87
81
|
<Tippy
|
|
@@ -95,7 +89,11 @@ export function PageControl({
|
|
|
95
89
|
offset={TopOffset}
|
|
96
90
|
>
|
|
97
91
|
<button className={clsx(`${name}-btn`, "add", theme)} disabled={disabled} onClick={actions.addPage}>
|
|
98
|
-
<Icon
|
|
92
|
+
<Icon
|
|
93
|
+
fallback={<WhiteboardAdd theme={theme} />}
|
|
94
|
+
src={disabled ? addIconDisable : addIcon}
|
|
95
|
+
alt="[add]"
|
|
96
|
+
/>
|
|
99
97
|
</button>
|
|
100
98
|
</Tippy>
|
|
101
99
|
</div>
|
|
@@ -7,8 +7,8 @@ $name: "fastboard-player-control";
|
|
|
7
7
|
gap: 4px;
|
|
8
8
|
padding: 4px;
|
|
9
9
|
border-radius: 4px;
|
|
10
|
-
backdrop-filter: blur(
|
|
11
|
-
-webkit-backdrop-filter: blur(
|
|
10
|
+
backdrop-filter: blur(5px);
|
|
11
|
+
-webkit-backdrop-filter: blur(5px);
|
|
12
12
|
|
|
13
13
|
&.auto-hide {
|
|
14
14
|
opacity: 0;
|
|
@@ -45,14 +45,14 @@ $name: "fastboard-player-control";
|
|
|
45
45
|
|
|
46
46
|
&.light {
|
|
47
47
|
color: #333;
|
|
48
|
-
background-color: rgba(
|
|
49
|
-
border: 1px solid
|
|
48
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
49
|
+
border: 1px solid #e5e8f0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
&.dark {
|
|
53
53
|
color: #ddd;
|
|
54
|
-
background-color:
|
|
55
|
-
border: 1px solid
|
|
54
|
+
background-color: #14181e;
|
|
55
|
+
border: 1px solid #383b42;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -74,8 +74,8 @@ $name: "fastboard-player-control";
|
|
|
74
74
|
|
|
75
75
|
svg,
|
|
76
76
|
img {
|
|
77
|
-
width:
|
|
78
|
-
height:
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 100%;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
&:disabled {
|
|
@@ -84,11 +84,11 @@ $name: "fastboard-player-control";
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
&.light:not(:disabled):hover {
|
|
87
|
-
background-color:
|
|
87
|
+
background-color: #ebf2ff;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
&.dark:not(:disabled):hover {
|
|
91
|
-
background-color:
|
|
91
|
+
background-color: #383b42;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
&.loading {
|
|
@@ -113,7 +113,7 @@ $name: "fastboard-player-control";
|
|
|
113
113
|
width: initial;
|
|
114
114
|
height: initial;
|
|
115
115
|
user-select: none;
|
|
116
|
-
font-size:
|
|
116
|
+
font-size: 14px;
|
|
117
117
|
padding: 4px;
|
|
118
118
|
justify-content: flex-end;
|
|
119
119
|
|
|
@@ -140,6 +140,6 @@ $name: "fastboard-player-control";
|
|
|
140
140
|
.#{$name}-slash,
|
|
141
141
|
.#{$name}-total,
|
|
142
142
|
.#{$name}-speed-text {
|
|
143
|
-
font-size:
|
|
143
|
+
font-size: 14px;
|
|
144
144
|
font-variant-numeric: tabular-nums;
|
|
145
145
|
}
|
|
@@ -6,19 +6,19 @@ $name: "fastboard-redo-undo";
|
|
|
6
6
|
gap: 4px;
|
|
7
7
|
padding: 4px;
|
|
8
8
|
border-radius: 4px;
|
|
9
|
-
backdrop-filter: blur(
|
|
10
|
-
-webkit-backdrop-filter: blur(
|
|
9
|
+
backdrop-filter: blur(5px);
|
|
10
|
+
-webkit-backdrop-filter: blur(5px);
|
|
11
11
|
|
|
12
12
|
&.light {
|
|
13
13
|
color: #333;
|
|
14
|
-
background-color: rgba(
|
|
15
|
-
border: 1px solid
|
|
14
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
15
|
+
border: 1px solid #e5e8f0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
&.dark {
|
|
19
19
|
color: #ddd;
|
|
20
|
-
background-color:
|
|
21
|
-
border: 1px solid
|
|
20
|
+
background-color: #14181e;
|
|
21
|
+
border: 1px solid #383b42;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -37,8 +37,8 @@ $name: "fastboard-redo-undo";
|
|
|
37
37
|
|
|
38
38
|
svg,
|
|
39
39
|
img {
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
&:disabled {
|
|
@@ -47,10 +47,10 @@ $name: "fastboard-redo-undo";
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
&.light:not(:disabled):hover {
|
|
50
|
-
background-color:
|
|
50
|
+
background-color: #ebf2ff;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
&.dark:not(:disabled):hover {
|
|
54
|
-
background-color:
|
|
54
|
+
background-color: #383b42;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -9,8 +9,8 @@ $name: "fastboard-toolbar";
|
|
|
9
9
|
gap: 4px;
|
|
10
10
|
position: absolute;
|
|
11
11
|
z-index: 100;
|
|
12
|
-
backdrop-filter: blur(
|
|
13
|
-
-webkit-backdrop-filter: blur(
|
|
12
|
+
backdrop-filter: blur(5px);
|
|
13
|
+
-webkit-backdrop-filter: blur(5px);
|
|
14
14
|
|
|
15
15
|
.rc-slider {
|
|
16
16
|
padding: 6px 0;
|
|
@@ -36,17 +36,18 @@ $name: "fastboard-toolbar";
|
|
|
36
36
|
|
|
37
37
|
&.light {
|
|
38
38
|
color: #333;
|
|
39
|
-
background-color: rgba(
|
|
40
|
-
border: 1px solid
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&.expanded {
|
|
44
|
-
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
39
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
40
|
+
border: 1px solid #e5e8f0;
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
&.dark {
|
|
48
44
|
color: #ddd;
|
|
49
|
-
background-color:
|
|
45
|
+
background-color: #14181e;
|
|
46
|
+
border: 1px solid #383b42;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.expanded {
|
|
50
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
&.expanded:hover {
|
|
@@ -108,11 +109,11 @@ $name: "fastboard-toolbar";
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
&.light:not(:disabled):hover {
|
|
111
|
-
background-color:
|
|
112
|
+
background-color: #ebf2ff;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
&.dark:not(:disabled):hover {
|
|
115
|
-
background-color:
|
|
116
|
+
background-color: #383b42;
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -206,6 +207,16 @@ $name: "fastboard-toolbar";
|
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
&-app-is-loading {
|
|
211
|
+
opacity: 0.8;
|
|
212
|
+
cursor: wait;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
&-app-is-failed {
|
|
216
|
+
opacity: 0.5;
|
|
217
|
+
cursor: not-allowed;
|
|
218
|
+
}
|
|
219
|
+
|
|
209
220
|
&-app-icon {
|
|
210
221
|
padding-top: 4px;
|
|
211
222
|
display: inline-flex;
|
|
@@ -218,7 +229,7 @@ $name: "fastboard-toolbar";
|
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
&-text {
|
|
221
|
-
font-size:
|
|
232
|
+
font-size: 14px;
|
|
222
233
|
color: #5d5d5d;
|
|
223
234
|
overflow: hidden;
|
|
224
235
|
white-space: nowrap;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
1
2
|
import Tippy from "@tippyjs/react";
|
|
2
3
|
import React, { useContext } from "react";
|
|
3
4
|
|
|
@@ -62,6 +63,7 @@ function renderAppsButtonContent(content?: React.ReactNode) {
|
|
|
62
63
|
|
|
63
64
|
function DefaultApps() {
|
|
64
65
|
const app = useFastboardApp();
|
|
66
|
+
const { appsStatus } = useContext(ToolbarContext);
|
|
65
67
|
|
|
66
68
|
return (
|
|
67
69
|
<>
|
|
@@ -69,13 +71,21 @@ function DefaultApps() {
|
|
|
69
71
|
title="Code Editor"
|
|
70
72
|
src={vscodePNG}
|
|
71
73
|
alt="[code editor]"
|
|
74
|
+
appStatus={appsStatus["Monaco"]}
|
|
72
75
|
onClick={app?.insertCodeEditor.bind(app)}
|
|
73
76
|
/>
|
|
74
|
-
<AppIcon
|
|
77
|
+
<AppIcon
|
|
78
|
+
title="GeoGebra"
|
|
79
|
+
src={geogebraPNG}
|
|
80
|
+
alt="[geogebra]"
|
|
81
|
+
appStatus={appsStatus["GeoGebra"]}
|
|
82
|
+
onClick={app?.insertGeoGebra.bind(app)}
|
|
83
|
+
/>
|
|
75
84
|
<AppIcon
|
|
76
85
|
title="Countdown"
|
|
77
86
|
src={countdownPNG}
|
|
78
87
|
alt="[countdown]"
|
|
88
|
+
appStatus={appsStatus["Countdown"]}
|
|
79
89
|
onClick={app?.insertCountdown.bind(app)}
|
|
80
90
|
/>
|
|
81
91
|
</>
|
|
@@ -86,14 +96,28 @@ interface AppIconProps {
|
|
|
86
96
|
title: string;
|
|
87
97
|
src: string;
|
|
88
98
|
alt: string;
|
|
99
|
+
appStatus?: {
|
|
100
|
+
status: "idle" | "loading" | "failed";
|
|
101
|
+
reason?: string | undefined;
|
|
102
|
+
};
|
|
89
103
|
onClick?: () => void;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
function AppIcon({ title, src, alt, onClick }: AppIconProps) {
|
|
106
|
+
function AppIcon({ title, src, alt, appStatus, onClick }: AppIconProps) {
|
|
107
|
+
const { status = "idle", reason } = appStatus || {};
|
|
108
|
+
const loading = status === "loading";
|
|
109
|
+
const failed = status === "failed";
|
|
110
|
+
const unifiedTitle = loading ? "loading" : failed ? reason : title;
|
|
111
|
+
|
|
93
112
|
return (
|
|
94
|
-
<span
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
<span
|
|
114
|
+
className={clsx("fastboard-toolbar-app-icon", {
|
|
115
|
+
"fastboard-toolbar-app-is-loading": loading,
|
|
116
|
+
"fastboard-toolbar-app-is-failed": failed,
|
|
117
|
+
})}
|
|
118
|
+
>
|
|
119
|
+
<Button disabled={failed} placement="top" content={unifiedTitle} onClick={onClick}>
|
|
120
|
+
<img src={src} alt={alt} title={unifiedTitle} />
|
|
97
121
|
</Button>
|
|
98
122
|
<span className="fastboard-toolbar-app-icon-text">{title}</span>
|
|
99
123
|
</span>
|
|
@@ -2,13 +2,12 @@ import type { ShapeType } from "white-web-sdk";
|
|
|
2
2
|
import type { IconProps } from "../../../typings";
|
|
3
3
|
|
|
4
4
|
import Tippy from "@tippyjs/react";
|
|
5
|
-
import React, { useContext } from "react";
|
|
5
|
+
import React, { useCallback, useContext } from "react";
|
|
6
6
|
import { ApplianceNames } from "white-web-sdk";
|
|
7
7
|
|
|
8
8
|
import { useTranslation } from "../../../i18n";
|
|
9
9
|
import { RightOffset } from "../../../theme";
|
|
10
10
|
import { ApplianceShapes, Shapes, ShapesMap } from "../const";
|
|
11
|
-
import { Icons } from "../icons";
|
|
12
11
|
import { ToolbarContext } from "../Toolbar";
|
|
13
12
|
import { Button } from "./Button";
|
|
14
13
|
import { ColorBox } from "./ColorBox";
|
|
@@ -19,7 +18,7 @@ const ShapeTypes = new Set([...ApplianceShapes, ...Shapes]);
|
|
|
19
18
|
|
|
20
19
|
export function ShapesButton() {
|
|
21
20
|
const { t } = useTranslation();
|
|
22
|
-
const { theme, memberState } = useContext(ToolbarContext);
|
|
21
|
+
const { writable, theme, memberState, lastShape, setAppliance } = useContext(ToolbarContext);
|
|
23
22
|
|
|
24
23
|
const appliance = memberState?.currentApplianceName;
|
|
25
24
|
const shape = memberState?.shapeType;
|
|
@@ -28,7 +27,15 @@ export function ShapesButton() {
|
|
|
28
27
|
|
|
29
28
|
const active = ShapeTypes.has(key);
|
|
30
29
|
|
|
31
|
-
const CurrentIcon = ShapesMap[
|
|
30
|
+
const CurrentIcon = ShapesMap[lastShape];
|
|
31
|
+
|
|
32
|
+
const onClick = useCallback(() => {
|
|
33
|
+
if ((ApplianceShapes as readonly ApplianceNames[]).includes(lastShape as ApplianceNames)) {
|
|
34
|
+
setAppliance(lastShape as ApplianceNames);
|
|
35
|
+
} else if (Shapes.includes(lastShape as ShapeType)) {
|
|
36
|
+
setAppliance(ApplianceNames.shape, lastShape as ShapeType);
|
|
37
|
+
}
|
|
38
|
+
}, [lastShape, setAppliance]);
|
|
32
39
|
|
|
33
40
|
return (
|
|
34
41
|
<span className="fastboard-toolbar-btn-interactive">
|
|
@@ -42,7 +49,7 @@ export function ShapesButton() {
|
|
|
42
49
|
arrow={false}
|
|
43
50
|
interactive
|
|
44
51
|
>
|
|
45
|
-
<Button content={t("shape")} active={active}>
|
|
52
|
+
<Button content={t("shape")} active={active} disabled={!writable} onClick={onClick}>
|
|
46
53
|
<CurrentIcon theme={theme} active={active} />
|
|
47
54
|
<span className="fastboard-toolbar-triangle" />
|
|
48
55
|
</Button>
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { ApplianceNames, Color, MemberState, ShapeType } from "white-web-sdk";
|
|
2
|
+
import type { AppsStatus } from "@netless/fastboard-core";
|
|
2
3
|
|
|
3
|
-
import { useCallback } from "react";
|
|
4
|
+
import { useCallback, useState } from "react";
|
|
4
5
|
|
|
5
6
|
import { noop } from "../../internal";
|
|
6
7
|
import { useFastboardApp, useFastboardValue, useWritable } from "../hooks";
|
|
8
|
+
import { ShapesMap } from "./const";
|
|
9
|
+
|
|
10
|
+
export type UnifiedShape = keyof typeof ShapesMap;
|
|
7
11
|
|
|
8
12
|
export interface ToolbarHook {
|
|
9
13
|
readonly writable: boolean;
|
|
10
14
|
readonly memberState: MemberState | undefined;
|
|
15
|
+
readonly lastShape: UnifiedShape;
|
|
16
|
+
readonly appsStatus: AppsStatus;
|
|
11
17
|
cleanCurrentScene(): void;
|
|
12
18
|
setAppliance(appliance: ApplianceNames, shape?: ShapeType): void;
|
|
13
19
|
setStrokeWidth(width: number): void;
|
|
@@ -18,10 +24,16 @@ export function useRoomState() {
|
|
|
18
24
|
return useFastboardValue(useFastboardApp().memberState);
|
|
19
25
|
}
|
|
20
26
|
|
|
27
|
+
export function useAppsStatus() {
|
|
28
|
+
return useFastboardValue(useFastboardApp().appsStatus);
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
export function useToolbar(): ToolbarHook {
|
|
22
32
|
const app = useFastboardApp();
|
|
23
33
|
const writable = useWritable();
|
|
24
34
|
const memberState = useRoomState();
|
|
35
|
+
const appsStatus = useAppsStatus();
|
|
36
|
+
const [lastShape, setLastShape] = useState<UnifiedShape>("rectangle" as ApplianceNames.rectangle);
|
|
25
37
|
|
|
26
38
|
const cleanCurrentScene = useCallback(() => {
|
|
27
39
|
app.cleanCurrentScene();
|
|
@@ -30,6 +42,11 @@ export function useToolbar(): ToolbarHook {
|
|
|
30
42
|
const setAppliance = useCallback(
|
|
31
43
|
(appliance: ApplianceNames, shape?: ShapeType) => {
|
|
32
44
|
app.setAppliance(appliance, shape);
|
|
45
|
+
if (shape) {
|
|
46
|
+
setLastShape(shape);
|
|
47
|
+
} else if (appliance in ShapesMap) {
|
|
48
|
+
setLastShape(appliance as UnifiedShape);
|
|
49
|
+
}
|
|
33
50
|
},
|
|
34
51
|
[app]
|
|
35
52
|
);
|
|
@@ -51,6 +68,8 @@ export function useToolbar(): ToolbarHook {
|
|
|
51
68
|
return {
|
|
52
69
|
writable,
|
|
53
70
|
memberState,
|
|
71
|
+
lastShape,
|
|
72
|
+
appsStatus,
|
|
54
73
|
cleanCurrentScene,
|
|
55
74
|
setAppliance,
|
|
56
75
|
setStrokeWidth,
|
|
@@ -61,6 +80,8 @@ export function useToolbar(): ToolbarHook {
|
|
|
61
80
|
export const EmptyToolbarHook: ToolbarHook = {
|
|
62
81
|
writable: false,
|
|
63
82
|
memberState: undefined,
|
|
83
|
+
lastShape: "rectangle" as ApplianceNames.rectangle,
|
|
84
|
+
appsStatus: {},
|
|
64
85
|
cleanCurrentScene: noop,
|
|
65
86
|
setAppliance: noop,
|
|
66
87
|
setStrokeWidth: noop,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IconProps } from "../../../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { getStroke } from "../../../theme";
|
|
5
|
+
|
|
6
|
+
export const Laser = (props: IconProps) => {
|
|
7
|
+
const stroke = getStroke(props);
|
|
8
|
+
return (
|
|
9
|
+
<svg viewBox="0 0 24 24">
|
|
10
|
+
<g fill="none" fill-rule="evenodd">
|
|
11
|
+
<circle cx="12" cy="12" r="2" fill={stroke} />
|
|
12
|
+
<path
|
|
13
|
+
stroke={stroke}
|
|
14
|
+
stroke-linecap="square"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
d="M12 4v2m0 12v2m8-8h-2M6 12H4m13.657 5.657-1.414-1.414M7.757 7.757 6.343 6.343m0 11.314 1.414-1.414m8.486-8.486 1.414-1.414"
|
|
17
|
+
/>
|
|
18
|
+
</g>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -7,19 +7,19 @@ $name: "fastboard-zoom-control";
|
|
|
7
7
|
gap: 4px;
|
|
8
8
|
padding: 4px;
|
|
9
9
|
border-radius: 4px;
|
|
10
|
-
backdrop-filter: blur(
|
|
11
|
-
-webkit-backdrop-filter: blur(
|
|
10
|
+
backdrop-filter: blur(5px);
|
|
11
|
+
-webkit-backdrop-filter: blur(5px);
|
|
12
12
|
|
|
13
13
|
&.light {
|
|
14
14
|
color: #333;
|
|
15
|
-
background-color: rgba(
|
|
16
|
-
border: 1px solid
|
|
15
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
16
|
+
border: 1px solid #e5e8f0;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
&.dark {
|
|
20
20
|
color: #ddd;
|
|
21
|
-
background-color:
|
|
22
|
-
border: 1px solid
|
|
21
|
+
background-color: #14181e;
|
|
22
|
+
border: 1px solid #383b42;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -38,8 +38,8 @@ $name: "fastboard-zoom-control";
|
|
|
38
38
|
|
|
39
39
|
svg,
|
|
40
40
|
img {
|
|
41
|
-
width:
|
|
42
|
-
height:
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
&:disabled {
|
|
@@ -48,11 +48,11 @@ $name: "fastboard-zoom-control";
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
&.light:not(:disabled):hover {
|
|
51
|
-
background-color:
|
|
51
|
+
background-color: #ebf2ff;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
&.dark:not(:disabled):hover {
|
|
55
|
-
background-color:
|
|
55
|
+
background-color: #383b42;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -69,12 +69,16 @@ $name: "fastboard-zoom-control";
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
.#{$name}-text {
|
|
73
|
+
line-height: 24px;
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
.#{$name}-percent {
|
|
73
77
|
opacity: 0.6;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
.#{$name}-scale,
|
|
77
81
|
.#{$name}-percent {
|
|
78
|
-
font-size:
|
|
82
|
+
font-size: 14px;
|
|
79
83
|
font-variant-numeric: tabular-nums;
|
|
80
84
|
}
|