bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx

287 lines
9.9 KiB
React
Raw Normal View History

2022-04-05 22:49:13 +08:00
import * as React from "react";
import _ from "lodash";
2022-04-05 22:49:13 +08:00
import Cursors from "./cursors/container";
import { TldrawApp, Tldraw } from "@tldraw/tldraw";
2022-05-13 23:15:44 +08:00
import logger from '/imports/startup/client/logger';
import {
ColorStyle,
DashStyle,
SizeStyle,
TDDocument,
TDShapeType,
} from "@tldraw/tldraw";
import { Renderer } from "@tldraw/core";
function usePrevious(value) {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
2022-04-05 22:49:13 +08:00
const findRemoved = (A, B) => {
return A.filter((a) => {
return !B.includes(a);
});
};
export default function Whiteboard(props) {
const {
isPresenter,
removeShapes,
2022-04-05 22:49:13 +08:00
initDefaultPages,
meetingId,
persistShape,
persistAsset,
shapes,
assets,
currentUser,
curPres,
curSlide,
changeCurrentSlide,
2022-05-07 00:37:43 +08:00
whiteboardId,
2022-05-12 05:58:16 +08:00
podId,
zoomSlide,
skipToSlide,
2022-05-12 05:58:16 +08:00
slidePosition,
2022-05-16 10:35:17 +08:00
curPageId,
svgUri,
2022-04-05 22:49:13 +08:00
} = props;
2022-05-16 10:35:17 +08:00
const { pages, pageStates } = initDefaultPages(curPres?.pages.length || 1);
2022-04-05 22:49:13 +08:00
const rDocument = React.useRef({
name: "test",
version: TldrawApp.version,
2022-05-16 10:35:17 +08:00
id: whiteboardId,
2022-04-05 22:49:13 +08:00
pages,
pageStates,
bindings: {},
assets,
});
//const [doc, setDoc] = React.useState(rDocument.current);
const [curPage, setCurPage] = React.useState({ id: "1" });
2022-05-11 08:22:30 +08:00
const [_assets, setAssets] = React.useState(assets);
const [command, setCommand] = React.useState("");
2022-05-16 10:35:17 +08:00
const [wbAccess, setWBAccess] = React.useState(props?.hasMultiUserAccess(props.whiteboardId, props.currentUser.userId));
const [selectedIds, setSelectedIds] = React.useState([]);
const [tldrawAPI, setTLDrawAPI] = React.useState(null);
const prevShapes = usePrevious(shapes);
const prevPage = usePrevious(curPage);
2022-05-12 05:58:16 +08:00
const prevSlidePosition = usePrevious(slidePosition);
const prevPageId = usePrevious(curPageId);
const doc = React.useMemo(() => {
2022-04-05 22:49:13 +08:00
const currentDoc = rDocument.current;
let next = { ...currentDoc };
let pageBindings = null;
let history = null;
2022-05-19 08:52:45 +08:00
let stack = null;
let changed = false;
if (next.pageStates[curPageId] && !_.isEqual(prevShapes, shapes)) {
// mergeDocument loses bindings and history, save it
pageBindings = tldrawAPI?.getPage(curPageId)?.bindings;
history = tldrawAPI?.history
2022-05-19 08:52:45 +08:00
stack = tldrawAPI?.stack
next.pages[curPageId].shapes = shapes;
changed = true;
}
2022-04-05 22:49:13 +08:00
if (next.pages[curPageId] && !next.pages[curPageId].shapes["slide-background-shape"]) {
next.assets[`slide-background-asset-${curPageId}`] = {
id: `slide-background-asset-${curPageId}`,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
src: svgUri,
type: "image",
};
next.pages[curPageId].shapes["slide-background-shape"] = {
assetId: `slide-background-asset-${curPageId}`,
childIndex: 1,
id: "slide-background-shape",
name: "Image",
type: TDShapeType.Image,
parentId: `${curPageId}`,
point: [0, 0],
isLocked: true,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Blue,
},
};
changed = true;
2022-04-05 22:49:13 +08:00
}
if (changed) {
tldrawAPI?.mergeDocument(next);
if (tldrawAPI && history) tldrawAPI.history = history;
2022-05-19 08:52:45 +08:00
if (tldrawAPI && stack) tldrawAPI.stack = stack;
if (pageBindings && Object.keys(pageBindings).length !== 0) {
currentDoc.pages[curPageId].bindings = pageBindings;
}
}
return currentDoc;
}, [assets, shapes, tldrawAPI, curPageId, slidePosition]);
// change tldraw page when presentation page changes
React.useEffect(() => {
const previousPageZoom = tldrawAPI?.getPageState()?.camera?.zoom;
tldrawAPI &&
curPageId &&
tldrawAPI.changePage(curPageId)
//change zoom of the new page to follow the previous one
previousPageZoom &&
tldrawAPI.zoomTo(previousPageZoom)
}, [curPageId]);
2022-04-05 22:49:13 +08:00
// change tldraw camera when slidePosition changes
React.useEffect(() => {
tldrawAPI &&
!isPresenter &&
curPageId &&
slidePosition &&
tldrawAPI?.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom);
}, [curPageId, slidePosition]);
2022-05-16 10:35:17 +08:00
const hasWBAccess = props?.hasMultiUserAccess(props.whiteboardId, props.currentUser.userId);
2022-04-05 22:49:13 +08:00
return (
<>
<Cursors
tldrawAPI={tldrawAPI}
currentUser={currentUser}
2022-05-07 00:37:43 +08:00
whiteboardId={whiteboardId}
>
<Tldraw
document={doc}
disableAssets={false}
onMount={(app) => {
setTLDrawAPI(app);
2022-05-16 10:35:17 +08:00
props.setTldrawAPI(app);
curPageId && app.changePage(curPageId);
curPageId && app.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom)
}}
//onChange={handleChange}
onPersist={(e) => {
///////////// handle assets /////////////////////////
e?.assets?.forEach((a) => {
//persistAsset(a);
});
}}
2022-05-16 10:35:17 +08:00
showPages={false}
showZoom={false}
showUI={isPresenter || hasWBAccess}
showMenu={false}
showMultiplayerMenu={false}
2022-05-16 10:35:17 +08:00
readOnly={!isPresenter && !hasWBAccess}
onUndo={(e, s) => {
e?.selectedIds?.map(id => {
persistShape(e.getShape(id), whiteboardId);
})
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
removeShapes(shapesIdsToRemove, whiteboardId)
}}
onRedo={(e, s) => {
e?.selectedIds?.map(id => {
2022-05-19 08:52:45 +08:00
persistShape(e.getShape(id), whiteboardId);
});
2022-05-19 08:52:45 +08:00
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
removeShapes(shapesIdsToRemove, whiteboardId)
let shapeIdsToReAdd = findRemoved(Object.keys(pageShapes), Object.keys(shapes))
shapeIdsToReAdd.forEach(id => {
persistShape(pageShapes[id], whiteboardId);
})
}}
onChangePage={(app, s, b, a) => {
if (app.getPage()?.id !== curPageId) {
skipToSlide(Number.parseInt(app.getPage()?.id), podId)
}
}}
onCommand={(e, s, g) => {
if (s.includes("session:complete:DrawSession")) {
Object.entries(e.state.document.pages[e.getPage()?.id]?.shapes)
.filter(([k, s]) => s?.type === 'draw')
.forEach(([k, s]) => {
if (!e.prevShapes[k] && !k.includes('slide-background')) {
2022-05-07 00:37:43 +08:00
persistShape(s, whiteboardId);
}
});
}
if (s.includes("style")
|| s?.includes("session:complete:ArrowSession")) {
e.selectedIds.forEach(id => {
2022-05-07 00:37:43 +08:00
persistShape(e.getShape(id), whiteboardId);
});
}
2022-05-17 03:29:21 +08:00
if (s.includes('move_to_page')) {
const movedShapes = e.selectedIds.map(id => {
return e.getShape(id);
});
//remove shapes on origin page
removeShapes(e.selectedIds, whiteboardId);
//persist shapes for destination page
const newWhiteboardId = curPres.pages.find(page => page.num === Number.parseInt(e.getPage()?.id)).id;
2022-05-17 03:29:21 +08:00
movedShapes.forEach(s => {
persistShape(s, newWhiteboardId);
2022-05-17 03:29:21 +08:00
});
}
if (s?.includes("session:complete:TransformSingleSession")
|| s?.includes("session:complete:TranslateSession")
|| s?.includes("updated_shapes")
|| s?.includes("session:complete:RotateSession")
|| s?.includes("session:complete:HandleSession")) {
e.selectedIds.forEach(id => {
2022-05-07 00:37:43 +08:00
persistShape(e.getShape(id), whiteboardId);
//checks to find any bindings assosiated with the selected shapes.
//If any, they need to be updated as well.
const pageBindings = e.state.document.pages[e.getPage()?.id]?.bindings;
const boundShapes = [];
if (pageBindings) {
Object.entries(pageBindings).map(([k,b]) => {
2022-05-07 00:37:43 +08:00
if (b.toId.includes(id), whiteboardId) {
boundShapes.push(e.state.document.pages[e.getPage()?.id]?.shapes[b.fromId])
}
})
}
//persist shape(s) that was updated by the client and any shapes bound to it.
2022-05-11 08:22:30 +08:00
boundShapes.forEach(bs => persistShape(bs, whiteboardId))
});
}
if (s?.includes("session:complete:EraseSession") || s?.includes("delete")) {
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
2022-05-07 00:37:43 +08:00
removeShapes(shapesIdsToRemove, whiteboardId)
}
}}
2022-05-12 05:58:16 +08:00
onPatch={(s, reason) => {
if (reason && isPresenter && (reason.includes("zoomed") || reason.includes("panned"))) {
const pageID = tldrawAPI?.getPage()?.id;
const camera = s.document.pageStates[pageID]?.camera
zoomSlide(parseInt(pageID), podId, camera.zoom, camera.point[0], camera.point[1]);
}
}}
/>
</Cursors>
</>
2022-04-05 22:49:13 +08:00
);
}