@netless/fastboard-react 0.2.6 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/fastboard-react",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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.3",
14
- "i18next": "^21.6.9",
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.6",
26
- "@netless/window-manager": "^0.4.0",
27
- "@types/react": "^17.0.38",
25
+ "@netless/fastboard-core": "0.2.7",
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.0",
29
+ "sass": "^1.49.8",
30
30
  "tippy.js": "^6.3.7",
31
- "tsup": "^5.11.11",
32
- "white-web-sdk": "^2.16.6"
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 className="fastboard-view" ref={useWhiteboard} />
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
+ }
@@ -206,6 +206,16 @@ $name: "fastboard-toolbar";
206
206
  }
207
207
  }
208
208
 
209
+ &-app-is-loading {
210
+ opacity: 0.8;
211
+ cursor: wait;
212
+ }
213
+
214
+ &-app-is-failed {
215
+ opacity: 0.5;
216
+ cursor: not-allowed;
217
+ }
218
+
209
219
  &-app-icon {
210
220
  padding-top: 4px;
211
221
  display: inline-flex;
@@ -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 title="GeoGebra" src={geogebraPNG} alt="[geogebra]" onClick={app?.insertGeoGebra.bind(app)} />
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 className="fastboard-toolbar-app-icon">
95
- <Button placement="top" content={title} onClick={onClick}>
96
- <img src={src} alt={alt} title={title} />
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,7 +2,7 @@ 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";
@@ -18,7 +18,7 @@ const ShapeTypes = new Set([...ApplianceShapes, ...Shapes]);
18
18
 
19
19
  export function ShapesButton() {
20
20
  const { t } = useTranslation();
21
- const { theme, memberState, lastShape } = useContext(ToolbarContext);
21
+ const { writable, theme, memberState, lastShape, setAppliance } = useContext(ToolbarContext);
22
22
 
23
23
  const appliance = memberState?.currentApplianceName;
24
24
  const shape = memberState?.shapeType;
@@ -29,6 +29,14 @@ export function ShapesButton() {
29
29
 
30
30
  const CurrentIcon = ShapesMap[lastShape];
31
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]);
39
+
32
40
  return (
33
41
  <span className="fastboard-toolbar-btn-interactive">
34
42
  <Tippy
@@ -41,7 +49,7 @@ export function ShapesButton() {
41
49
  arrow={false}
42
50
  interactive
43
51
  >
44
- <Button content={t("shape")} active={active}>
52
+ <Button content={t("shape")} active={active} disabled={!writable} onClick={onClick}>
45
53
  <CurrentIcon theme={theme} active={active} />
46
54
  <span className="fastboard-toolbar-triangle" />
47
55
  </Button>
@@ -1,4 +1,5 @@
1
1
  import type { ApplianceNames, Color, MemberState, ShapeType } from "white-web-sdk";
2
+ import type { AppsStatus } from "@netless/fastboard-core";
2
3
 
3
4
  import { useCallback, useState } from "react";
4
5
 
@@ -12,6 +13,7 @@ export interface ToolbarHook {
12
13
  readonly writable: boolean;
13
14
  readonly memberState: MemberState | undefined;
14
15
  readonly lastShape: UnifiedShape;
16
+ readonly appsStatus: AppsStatus;
15
17
  cleanCurrentScene(): void;
16
18
  setAppliance(appliance: ApplianceNames, shape?: ShapeType): void;
17
19
  setStrokeWidth(width: number): void;
@@ -22,10 +24,15 @@ export function useRoomState() {
22
24
  return useFastboardValue(useFastboardApp().memberState);
23
25
  }
24
26
 
27
+ export function useAppsStatus() {
28
+ return useFastboardValue(useFastboardApp().appsStatus);
29
+ }
30
+
25
31
  export function useToolbar(): ToolbarHook {
26
32
  const app = useFastboardApp();
27
33
  const writable = useWritable();
28
34
  const memberState = useRoomState();
35
+ const appsStatus = useAppsStatus();
29
36
  const [lastShape, setLastShape] = useState<UnifiedShape>("rectangle" as ApplianceNames.rectangle);
30
37
 
31
38
  const cleanCurrentScene = useCallback(() => {
@@ -62,6 +69,7 @@ export function useToolbar(): ToolbarHook {
62
69
  writable,
63
70
  memberState,
64
71
  lastShape,
72
+ appsStatus,
65
73
  cleanCurrentScene,
66
74
  setAppliance,
67
75
  setStrokeWidth,
@@ -73,6 +81,7 @@ export const EmptyToolbarHook: ToolbarHook = {
73
81
  writable: false,
74
82
  memberState: undefined,
75
83
  lastShape: "rectangle" as ApplianceNames.rectangle,
84
+ appsStatus: {},
76
85
  cleanCurrentScene: noop,
77
86
  setAppliance: noop,
78
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
+ };
@@ -5,14 +5,24 @@ import React from "react";
5
5
  import ReactDOM from "react-dom";
6
6
  import { Fastboard } from "../components/Fastboard";
7
7
 
8
+ export type MountProps = Omit<FastboardProps & DivProps, "ref">;
9
+
8
10
  /**
9
11
  * Mount fastboard app to some dom, returns the disposer, which will unmount the app.
10
12
  * @example
11
13
  * let app = await createFastboard({ ...config })
12
- * let disposer = mount(app, document.getElementById("whiteboard"))
13
- * disposer()
14
+ * const { update, destroy } = mount(app, document.getElementById("whiteboard"))
15
+ * update({ theme: 'dark' })
16
+ * destroy()
14
17
  */
15
- export function mount(app: FastboardApp, dom: HTMLElement, props: Omit<FastboardProps & DivProps, "ref">) {
18
+ export function mount(app: FastboardApp, dom: HTMLElement, props: MountProps) {
16
19
  ReactDOM.render(<Fastboard app={app} {...props} />, dom);
17
- return () => ReactDOM.unmountComponentAtNode(dom);
20
+ return {
21
+ update(props: MountProps) {
22
+ ReactDOM.render(<Fastboard app={app} {...props} />, dom);
23
+ },
24
+ destroy() {
25
+ ReactDOM.unmountComponentAtNode(dom);
26
+ },
27
+ };
18
28
  }