@netless/fastboard 0.0.7 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -19
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +678 -410
- 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 +11 -2
- package/src/WhiteboardApp.ts +91 -20
- 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 +3 -10
- 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 +20 -5
- package/src/internal/Instance.tsx +31 -7
- package/src/internal/helpers.ts +44 -0
- package/src/internal/hooks.ts +9 -0
- package/src/react.tsx +52 -0
- package/src/style.scss +9 -3
- package/src/components/PageControl.tsx +0 -181
- package/src/components/ZoomControl.tsx +0 -221
- package/src/hooks.ts +0 -53
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Room, WindowManager } from "@netless/window-manager";
|
|
2
|
+
import { BuiltinApps } from "@netless/window-manager";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export function useWritable(room?: Room | null) {
|
|
6
|
+
const [writable, setWritable] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (room) {
|
|
10
|
+
setWritable(room.isWritable);
|
|
11
|
+
room.isWritable && (room.disableSerialization = false);
|
|
12
|
+
|
|
13
|
+
const updateWritable = () => setWritable(room.isWritable);
|
|
14
|
+
room.callbacks.on("onEnableWriteNowChanged", updateWritable);
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
room.callbacks.off("onEnableWriteNowChanged", updateWritable);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}, [room]);
|
|
21
|
+
|
|
22
|
+
return writable;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type BoxState = "normal" | "minimized" | "maximized";
|
|
26
|
+
|
|
27
|
+
export function useBoxState(manager?: WindowManager | null) {
|
|
28
|
+
const [boxState, setBoxState] = useState<BoxState | undefined>();
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (manager) {
|
|
32
|
+
setBoxState(manager.boxState);
|
|
33
|
+
|
|
34
|
+
manager.emitter.on("boxStateChange", setBoxState);
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
manager.emitter.off("boxStateChange", setBoxState);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}, [manager]);
|
|
41
|
+
|
|
42
|
+
return boxState;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useFocusedApp(manager?: WindowManager | null) {
|
|
46
|
+
const [focused, setFocused] = useState<string | undefined>();
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (manager) {
|
|
50
|
+
setFocused(manager.focused);
|
|
51
|
+
|
|
52
|
+
manager.emitter.on("focusedChange", setFocused);
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
manager.emitter.off("focusedChange", setFocused);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}, [manager]);
|
|
59
|
+
|
|
60
|
+
return focused;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useMaximized(manager?: WindowManager | null) {
|
|
64
|
+
return useBoxState(manager) === "maximized";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useHideControls(manager?: WindowManager | null) {
|
|
68
|
+
const maximized = useMaximized(manager);
|
|
69
|
+
const focusedApp = useFocusedApp(manager);
|
|
70
|
+
|
|
71
|
+
if (maximized) {
|
|
72
|
+
if (Object.values(BuiltinApps).some(kind => focusedApp?.includes(kind))) {
|
|
73
|
+
return "toolbar-only";
|
|
74
|
+
} else {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,16 +6,31 @@ import "./behaviors/style";
|
|
|
6
6
|
import { WhiteboardApp, type WhiteboardAppConfig } from "./WhiteboardApp";
|
|
7
7
|
|
|
8
8
|
export { version } from "../package.json";
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
export {
|
|
10
|
+
PageControl,
|
|
11
|
+
usePageControl,
|
|
12
|
+
type PageControlProps,
|
|
13
|
+
} from "./components/PageControl";
|
|
14
|
+
export {
|
|
15
|
+
RedoUndo,
|
|
16
|
+
useRedoUndo,
|
|
17
|
+
type RedoUndoProps,
|
|
18
|
+
} from "./components/RedoUndo";
|
|
19
|
+
export { Toolbar, useToolbar, type ToolbarProps } from "./components/Toolbar";
|
|
20
|
+
export {
|
|
21
|
+
ZoomControl,
|
|
22
|
+
useZoomControl,
|
|
23
|
+
type ZoomControlProps,
|
|
24
|
+
} from "./components/ZoomControl";
|
|
13
25
|
export {
|
|
14
26
|
PlayerControl,
|
|
27
|
+
usePlayerControl,
|
|
15
28
|
type PlayerControlProps,
|
|
16
29
|
} from "./components/PlayerControl";
|
|
30
|
+
export {};
|
|
31
|
+
|
|
17
32
|
export * from "./WhiteboardApp";
|
|
18
|
-
export * from "./
|
|
33
|
+
export * from "./react";
|
|
19
34
|
|
|
20
35
|
export const register = WindowManager.register.bind(WindowManager);
|
|
21
36
|
|
|
@@ -6,6 +6,7 @@ import type { i18n } from "i18next";
|
|
|
6
6
|
|
|
7
7
|
import React, { createContext, useContext } from "react";
|
|
8
8
|
import ReactDOM from "react-dom";
|
|
9
|
+
import { BuiltinApps } from "@netless/window-manager";
|
|
9
10
|
|
|
10
11
|
import { Root } from "../components/Root";
|
|
11
12
|
import { mountWhiteboard } from "./mount-whiteboard";
|
|
@@ -18,6 +19,11 @@ export interface AcceptParams {
|
|
|
18
19
|
readonly i18n: i18n;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export interface InsertMediaParams {
|
|
23
|
+
title: string;
|
|
24
|
+
src: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
export interface InsertDocsStatic {
|
|
22
28
|
readonly fileType: "pdf" | "ppt";
|
|
23
29
|
readonly scenePath: string;
|
|
@@ -31,6 +37,7 @@ export interface InsertDocsDynamic {
|
|
|
31
37
|
readonly taskId: string;
|
|
32
38
|
readonly title?: string;
|
|
33
39
|
readonly url?: string;
|
|
40
|
+
readonly scenes?: SceneDefinition[];
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export type InsertDocsParams = InsertDocsStatic | InsertDocsDynamic;
|
|
@@ -110,16 +117,22 @@ export class Instance {
|
|
|
110
117
|
target: HTMLElement | null = null;
|
|
111
118
|
collector: HTMLElement | null = null;
|
|
112
119
|
|
|
113
|
-
bindElement(target: HTMLElement | null
|
|
114
|
-
if (this.target && target) {
|
|
120
|
+
bindElement(target: HTMLElement | null) {
|
|
121
|
+
if (this.target && this.target !== target) {
|
|
115
122
|
ReactDOM.unmountComponentAtNode(this.target);
|
|
116
123
|
}
|
|
117
124
|
this.target = target;
|
|
118
|
-
this.collector = collector;
|
|
119
125
|
this.forceUpdate();
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
|
|
128
|
+
bindCollector(collector: HTMLElement | null) {
|
|
129
|
+
this.collector = collector;
|
|
130
|
+
if (this.manager && collector) {
|
|
131
|
+
this.manager.bindCollectorContainer(collector);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
updateLayout(layout: Layout | undefined) {
|
|
123
136
|
this.config.layout = layout;
|
|
124
137
|
this.forceUpdate();
|
|
125
138
|
}
|
|
@@ -154,10 +167,9 @@ export class Instance {
|
|
|
154
167
|
if (!this.manager) {
|
|
155
168
|
throw new Error(`[WhiteboardApp] mounted, but not found window manager`);
|
|
156
169
|
}
|
|
170
|
+
this.manager.bindContainer(node);
|
|
157
171
|
if (this.collector) {
|
|
158
|
-
this.manager.
|
|
159
|
-
} else {
|
|
160
|
-
this.manager.bindContainer(node);
|
|
172
|
+
this.manager.bindCollectorContainer(this.collector);
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
|
|
@@ -198,6 +210,7 @@ export class Instance {
|
|
|
198
210
|
options: {
|
|
199
211
|
scenePath: params.scenePath,
|
|
200
212
|
title: params.title,
|
|
213
|
+
scenes: params.scenes,
|
|
201
214
|
},
|
|
202
215
|
attributes: {
|
|
203
216
|
taskId: params.taskId,
|
|
@@ -237,6 +250,17 @@ export class Instance {
|
|
|
237
250
|
});
|
|
238
251
|
}
|
|
239
252
|
|
|
253
|
+
insertMedia({ title, src }: InsertMediaParams) {
|
|
254
|
+
if (!this.manager) {
|
|
255
|
+
throw new Error(`[WhiteboardApp] cannot insert app before mounted`);
|
|
256
|
+
}
|
|
257
|
+
return this.manager.addApp({
|
|
258
|
+
kind: BuiltinApps.MediaPlayer,
|
|
259
|
+
options: { title },
|
|
260
|
+
attributes: { src },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
240
264
|
async changeLanguage(language: Language) {
|
|
241
265
|
try {
|
|
242
266
|
await this.i18n?.changeLanguage(language);
|
package/src/internal/helpers.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SceneDefinition } from "white-web-sdk";
|
|
2
|
+
|
|
1
3
|
export function noop() {
|
|
2
4
|
return;
|
|
3
5
|
}
|
|
@@ -40,3 +42,45 @@ export class Lock {
|
|
|
40
42
|
}
|
|
41
43
|
};
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
// Copy from https://github.com/crimx/side-effect-manager/blob/main/src/gen-uid.ts
|
|
47
|
+
const SOUP =
|
|
48
|
+
"!#%()*+,-./:;=?@[]^_`{|}~" +
|
|
49
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
50
|
+
const SOUP_LEN = SOUP.length;
|
|
51
|
+
const ID_LEN = 20;
|
|
52
|
+
const reusedIdCarrier = Array(ID_LEN);
|
|
53
|
+
|
|
54
|
+
export const genUID = (): string => {
|
|
55
|
+
for (let i = 0; i < ID_LEN; i++) {
|
|
56
|
+
reusedIdCarrier[i] = SOUP.charAt(Math.random() * SOUP_LEN);
|
|
57
|
+
}
|
|
58
|
+
return reusedIdCarrier.join("");
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function makeSlideParams(scenes: SceneDefinition[]) {
|
|
62
|
+
const scenesWithoutPPT: SceneDefinition[] = [];
|
|
63
|
+
let taskId = "";
|
|
64
|
+
let url = "";
|
|
65
|
+
|
|
66
|
+
// e.g. "ppt(x)://cdn/prefix/dynamicConvert/{taskId}/1.slide"
|
|
67
|
+
const pptSrcRE = /^pptx?(?<prefix>:\/\/\S+?dynamicConvert)\/(?<taskId>\w+)\//;
|
|
68
|
+
|
|
69
|
+
for (const { name, ppt } of scenes) {
|
|
70
|
+
// make sure scenesWithoutPPT.length === scenes.length
|
|
71
|
+
scenesWithoutPPT.push({ name });
|
|
72
|
+
|
|
73
|
+
if (!ppt || !ppt.src.startsWith("ppt")) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const match = pptSrcRE.exec(ppt.src);
|
|
77
|
+
if (!match || !match.groups) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
taskId = match.groups.taskId;
|
|
81
|
+
url = "https" + match.groups.prefix;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { scenesWithoutPPT, taskId, url };
|
|
86
|
+
}
|
package/src/react.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { WhiteboardApp } from "./index";
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef, useEffect, useRef } from "react";
|
|
4
|
+
import { useLastValue } from "./internal/hooks";
|
|
5
|
+
|
|
6
|
+
// https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd
|
|
7
|
+
function useCombinedRefs<T>(...refs: React.Ref<T>[]) {
|
|
8
|
+
const targetRef = useRef<T | null>(null);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
for (const ref of refs) {
|
|
12
|
+
if (!ref) continue;
|
|
13
|
+
|
|
14
|
+
if (typeof ref === "function") {
|
|
15
|
+
ref(targetRef.current);
|
|
16
|
+
} else {
|
|
17
|
+
(ref as typeof targetRef).current = targetRef.current;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}, [refs]);
|
|
21
|
+
|
|
22
|
+
return targetRef;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @example
|
|
27
|
+
* let app = await createWhiteboardApp(config)
|
|
28
|
+
* <Fastboard app={app} />
|
|
29
|
+
* await app.dispose()
|
|
30
|
+
*/
|
|
31
|
+
export const Fastboard = forwardRef<
|
|
32
|
+
HTMLDivElement,
|
|
33
|
+
{ app?: WhiteboardApp | null } & React.DetailedHTMLProps<
|
|
34
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
35
|
+
HTMLDivElement
|
|
36
|
+
>
|
|
37
|
+
>(({ app, ...restProps }, outerRef) => {
|
|
38
|
+
const innerRef = useRef<HTMLDivElement>(null);
|
|
39
|
+
const ref = useCombinedRefs(outerRef, innerRef);
|
|
40
|
+
const previous = useLastValue(app);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (previous && previous !== app) {
|
|
44
|
+
previous.bindElement(null);
|
|
45
|
+
}
|
|
46
|
+
if (app) {
|
|
47
|
+
app.bindElement(ref.current);
|
|
48
|
+
}
|
|
49
|
+
}, [app, previous, ref]);
|
|
50
|
+
|
|
51
|
+
return <div className="fastboard" {...restProps} ref={ref} />;
|
|
52
|
+
});
|
package/src/style.scss
CHANGED
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
@import "tippy.js/themes/light.css";
|
|
4
4
|
@import "rc-slider/assets/index.css";
|
|
5
5
|
@import "./components/Root.scss";
|
|
6
|
-
@import "./components/RedoUndo.scss";
|
|
7
|
-
@import "./components/PageControl.scss";
|
|
8
|
-
@import "./components/ZoomControl.scss";
|
|
6
|
+
@import "./components/RedoUndo/RedoUndo.scss";
|
|
7
|
+
@import "./components/PageControl/PageControl.scss";
|
|
8
|
+
@import "./components/ZoomControl/ZoomControl.scss";
|
|
9
9
|
@import "./components/Toolbar/Toolbar.scss";
|
|
10
10
|
@import "./components/PlayerControl/PlayerControl.scss";
|
|
11
11
|
|
|
12
|
+
.fastboard {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
position: relative;
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
.tippy-box.fastboard-tip {
|
|
13
19
|
color: #eee;
|
|
14
20
|
background-color: rgba($color: #000000, $alpha: 0.95);
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type { RoomState, ViewVisionMode } from "white-web-sdk";
|
|
2
|
-
import type { CommonProps, GenericIcon } from "../types";
|
|
3
|
-
|
|
4
|
-
import clsx from "clsx";
|
|
5
|
-
import React, { useCallback, useEffect, useState } from "react";
|
|
6
|
-
import Tippy from "@tippyjs/react";
|
|
7
|
-
|
|
8
|
-
import { TopOffset } from "../theme";
|
|
9
|
-
import { Icon } from "../icons";
|
|
10
|
-
import { FilePlus } from "../icons/FilePlus";
|
|
11
|
-
import { ChevronLeft } from "../icons/ChevronLeft";
|
|
12
|
-
import { ChevronRight } from "../icons/ChevronRight";
|
|
13
|
-
|
|
14
|
-
export const name = "fastboard-page-control";
|
|
15
|
-
|
|
16
|
-
export type PageControlProps = CommonProps &
|
|
17
|
-
GenericIcon<"add" | "prev" | "next">;
|
|
18
|
-
|
|
19
|
-
export function PageControl({
|
|
20
|
-
room,
|
|
21
|
-
manager,
|
|
22
|
-
theme = "light",
|
|
23
|
-
addIcon,
|
|
24
|
-
addIconDisable,
|
|
25
|
-
prevIcon,
|
|
26
|
-
prevIconDisable,
|
|
27
|
-
nextIcon,
|
|
28
|
-
nextIconDisable,
|
|
29
|
-
i18n,
|
|
30
|
-
}: PageControlProps) {
|
|
31
|
-
const [writable, setWritable] = useState(false);
|
|
32
|
-
const [pageIndex, setPageIndex] = useState(0);
|
|
33
|
-
const [pageCount, setPageCount] = useState(0);
|
|
34
|
-
|
|
35
|
-
const addPage = useCallback(async () => {
|
|
36
|
-
if (manager && room) {
|
|
37
|
-
await manager.switchMainViewToWriter();
|
|
38
|
-
const path = room.state.sceneState.contextPath;
|
|
39
|
-
room.putScenes(path, [{}], pageIndex + 1);
|
|
40
|
-
await manager.setMainViewSceneIndex(pageIndex + 1);
|
|
41
|
-
} else if (!manager && room) {
|
|
42
|
-
const path = room.state.sceneState.contextPath;
|
|
43
|
-
room.putScenes(path, [{}], pageIndex + 1);
|
|
44
|
-
room.setSceneIndex(pageIndex + 1);
|
|
45
|
-
}
|
|
46
|
-
}, [room, manager, pageIndex]);
|
|
47
|
-
|
|
48
|
-
const prevPage = useCallback(() => {
|
|
49
|
-
if (room?.isWritable) {
|
|
50
|
-
if (manager) {
|
|
51
|
-
manager.setMainViewSceneIndex(pageIndex - 1);
|
|
52
|
-
} else {
|
|
53
|
-
room.pptPreviousStep();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}, [room, manager, pageIndex]);
|
|
57
|
-
|
|
58
|
-
const nextPage = useCallback(() => {
|
|
59
|
-
if (room?.isWritable) {
|
|
60
|
-
if (manager) {
|
|
61
|
-
manager.setMainViewSceneIndex(pageIndex + 1);
|
|
62
|
-
} else {
|
|
63
|
-
room.pptNextStep();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}, [room, manager, pageIndex]);
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (room) {
|
|
70
|
-
setWritable(room.isWritable);
|
|
71
|
-
setPageIndex(room.state.sceneState.index);
|
|
72
|
-
setPageCount(room.state.sceneState.scenes.length);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const onRoomStateChanged = (modifyState: Partial<RoomState>) => {
|
|
76
|
-
if (modifyState.sceneState) {
|
|
77
|
-
setPageIndex(modifyState.sceneState.index);
|
|
78
|
-
setPageCount(modifyState.sceneState.scenes.length);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const onMainViewModeChanged = (mode: number) => {
|
|
83
|
-
if (room && mode === (0 as ViewVisionMode.Writable)) {
|
|
84
|
-
setPageIndex(room.state.sceneState.index);
|
|
85
|
-
setPageCount(room.state.sceneState.scenes.length);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const updateWritable = () => setWritable(room?.isWritable || false);
|
|
90
|
-
|
|
91
|
-
if (room) {
|
|
92
|
-
room.callbacks.on("onEnableWriteNowChanged", updateWritable);
|
|
93
|
-
room.callbacks.on("onRoomStateChanged", onRoomStateChanged);
|
|
94
|
-
manager?.callbacks.on("mainViewModeChange", onMainViewModeChanged);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return () => {
|
|
98
|
-
if (room) {
|
|
99
|
-
room.callbacks.off("onEnableWriteNowChanged", updateWritable);
|
|
100
|
-
room.callbacks.off("onRoomStateChanged", onRoomStateChanged);
|
|
101
|
-
manager?.callbacks.off("mainViewModeChange", onMainViewModeChanged);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
}, [room, manager]);
|
|
105
|
-
|
|
106
|
-
const disabled = !writable;
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div className={clsx(name, theme)}>
|
|
110
|
-
{/* <span className={clsx(`${name}-cut-line`, theme)} />{" "} */}
|
|
111
|
-
<Tippy
|
|
112
|
-
className="fastboard-tip"
|
|
113
|
-
content={i18n?.t("prevPage")}
|
|
114
|
-
theme={theme}
|
|
115
|
-
disabled={disabled}
|
|
116
|
-
placement="top"
|
|
117
|
-
duration={300}
|
|
118
|
-
offset={TopOffset}
|
|
119
|
-
>
|
|
120
|
-
<button
|
|
121
|
-
className={clsx(`${name}-btn`, "prev", theme)}
|
|
122
|
-
disabled={disabled || pageIndex === 0}
|
|
123
|
-
onClick={prevPage}
|
|
124
|
-
>
|
|
125
|
-
<Icon
|
|
126
|
-
fallback={<ChevronLeft theme={theme} />}
|
|
127
|
-
src={disabled ? prevIconDisable : prevIcon}
|
|
128
|
-
alt="[prev]"
|
|
129
|
-
/>
|
|
130
|
-
</button>
|
|
131
|
-
</Tippy>
|
|
132
|
-
<span className={clsx(`${name}-page`, theme)}>
|
|
133
|
-
{pageCount === 0 ? "\u2026" : pageIndex + 1}
|
|
134
|
-
</span>
|
|
135
|
-
<span className={clsx(`${name}-slash`, theme)}>/</span>
|
|
136
|
-
<span className={clsx(`${name}-page-count`, theme)}>{pageCount}</span>
|
|
137
|
-
<Tippy
|
|
138
|
-
className="fastboard-tip"
|
|
139
|
-
content={i18n?.t("nextPage")}
|
|
140
|
-
theme={theme}
|
|
141
|
-
disabled={disabled}
|
|
142
|
-
placement="top"
|
|
143
|
-
duration={300}
|
|
144
|
-
offset={TopOffset}
|
|
145
|
-
>
|
|
146
|
-
<button
|
|
147
|
-
className={clsx(`${name}-btn`, "next", theme)}
|
|
148
|
-
disabled={disabled || pageIndex === pageCount - 1}
|
|
149
|
-
onClick={nextPage}
|
|
150
|
-
>
|
|
151
|
-
<Icon
|
|
152
|
-
fallback={<ChevronRight theme={theme} />}
|
|
153
|
-
src={disabled ? nextIconDisable : nextIcon}
|
|
154
|
-
alt="[next]"
|
|
155
|
-
/>
|
|
156
|
-
</button>
|
|
157
|
-
</Tippy>
|
|
158
|
-
<Tippy
|
|
159
|
-
className="fastboard-tip"
|
|
160
|
-
content={i18n?.t("addPage")}
|
|
161
|
-
theme={theme}
|
|
162
|
-
disabled={disabled}
|
|
163
|
-
placement="top"
|
|
164
|
-
duration={300}
|
|
165
|
-
offset={TopOffset}
|
|
166
|
-
>
|
|
167
|
-
<button
|
|
168
|
-
className={clsx(`${name}-btn`, "add", theme)}
|
|
169
|
-
disabled={disabled}
|
|
170
|
-
onClick={addPage}
|
|
171
|
-
>
|
|
172
|
-
<Icon
|
|
173
|
-
fallback={<FilePlus theme={theme} />}
|
|
174
|
-
src={disabled ? addIconDisable : addIcon}
|
|
175
|
-
alt="[add]"
|
|
176
|
-
/>
|
|
177
|
-
</button>
|
|
178
|
-
</Tippy>
|
|
179
|
-
</div>
|
|
180
|
-
);
|
|
181
|
-
}
|