2022-04-05 22:49:13 +08:00
|
|
|
import * as React from "react";
|
2022-04-26 21:55:05 +08:00
|
|
|
import _ from "lodash";
|
2022-04-05 22:49:13 +08:00
|
|
|
import Cursors from "./cursors/container";
|
|
|
|
import { TldrawApp, Tldraw } from "@tldraw/tldraw";
|
2022-04-26 21:55:05 +08:00
|
|
|
import {
|
|
|
|
ColorStyle,
|
|
|
|
DashStyle,
|
|
|
|
SizeStyle,
|
|
|
|
TDDocument,
|
|
|
|
TDShapeType,
|
|
|
|
} from "@tldraw/tldraw";
|
2022-04-11 00:31:12 +08:00
|
|
|
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,
|
|
|
|
removeShape,
|
|
|
|
initDefaultPages,
|
|
|
|
meetingId,
|
|
|
|
persistShape,
|
|
|
|
persistAsset,
|
|
|
|
shapes,
|
|
|
|
assets,
|
|
|
|
currentUser,
|
|
|
|
publishCursorUpdate,
|
2022-04-26 21:55:05 +08:00
|
|
|
curPres,
|
|
|
|
curSlide,
|
|
|
|
changeCurrentSlide,
|
2022-04-05 22:49:13 +08:00
|
|
|
} = props;
|
2022-04-26 21:55:05 +08:00
|
|
|
console.log('curPres : ', curPres)
|
|
|
|
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,
|
|
|
|
id: `WB-${meetingId}`,
|
|
|
|
pages,
|
|
|
|
pageStates,
|
|
|
|
bindings: {},
|
|
|
|
assets,
|
|
|
|
});
|
|
|
|
const [doc, setDoc] = React.useState(rDocument.current);
|
2022-04-26 21:55:05 +08:00
|
|
|
const [curPage, setCurPage] = React.useState({ id: "1" });
|
2022-04-11 00:31:12 +08:00
|
|
|
const [ass, setAss] = React.useState(assets);
|
2022-04-26 21:55:05 +08:00
|
|
|
const [command, setCommand] = React.useState("");
|
|
|
|
const [selectedIds, setSelectedIds] = React.useState([]);
|
2022-04-11 00:31:12 +08:00
|
|
|
const [tldrawAPI, setTLDrawAPI] = React.useState(null);
|
|
|
|
const prevShapes = usePrevious(shapes);
|
2022-04-26 21:55:05 +08:00
|
|
|
const prevPage = usePrevious(curPage);
|
2022-04-11 00:31:12 +08:00
|
|
|
|
2022-04-05 22:49:13 +08:00
|
|
|
const handleChange = React.useCallback((state) => {
|
|
|
|
rDocument.current = state.document;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
React.useMemo(() => {
|
|
|
|
const currentDoc = rDocument.current;
|
|
|
|
const propShapes = Object.entries(shapes || {})?.map(([k, v]) => v.id);
|
2022-04-11 00:31:12 +08:00
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
if (!curPage && tldrawAPI) {
|
|
|
|
tldrawAPI.getPage();
|
2022-04-05 22:49:13 +08:00
|
|
|
}
|
2022-04-11 00:31:12 +08:00
|
|
|
|
2022-04-05 22:49:13 +08:00
|
|
|
const next = { ...currentDoc };
|
|
|
|
|
|
|
|
next.assets = { ...assets };
|
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
const pShapes = Object.entries(shapes || {})?.map(([k, v]) => v.id);
|
|
|
|
shapes?.forEach((s) => {
|
|
|
|
try {
|
|
|
|
Object.keys(next.pages[s.parentId].shapes).map((k) => {
|
|
|
|
if (!pShapes.includes(k) && s.parentId === tldrawAPI?.getPage()?.id) {
|
|
|
|
delete next.pages[s.parentId].shapes[k];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
next.pages[s.parentId] = {
|
|
|
|
...next.pages[s.parentId],
|
|
|
|
shapes: {
|
|
|
|
...next.pages[s.parentId].shapes,
|
|
|
|
[s.id]: { ...s },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (curPres?.pages.length) {
|
|
|
|
curPres.pages.map((p, i) => {
|
|
|
|
next.assets[`slide-background-asset-${i}`] = {
|
|
|
|
id: `slide-background-asset-${i}`,
|
|
|
|
size: [2560 / 3.5, 1440 / 3.5],
|
|
|
|
src: curPres?.pages[i]?.svgUri,
|
|
|
|
type: "image",
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
next.pages[i + 1]["shapes"]["slide-background-shape"] = {
|
|
|
|
assetId: `slide-background-asset-${i}`,
|
|
|
|
childIndex: 1,
|
|
|
|
id: "slide-background-shape",
|
|
|
|
name: "Image",
|
|
|
|
type: TDShapeType.Image,
|
|
|
|
parentId: `${i + 1}`,
|
|
|
|
childIndex: 1,
|
|
|
|
point: [50, 60],
|
2022-05-03 22:50:47 +08:00
|
|
|
isLocked: true,
|
2022-04-26 21:55:05 +08:00
|
|
|
size: [2560 / 3.5, 1440 / 3.5],
|
|
|
|
style: {
|
|
|
|
dash: DashStyle.Draw,
|
|
|
|
size: SizeStyle.Medium,
|
|
|
|
color: ColorStyle.Blue,
|
2022-04-05 22:49:13 +08:00
|
|
|
},
|
|
|
|
};
|
2022-04-26 21:55:05 +08:00
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
}
|
|
|
|
// setDoc(next);
|
|
|
|
});
|
|
|
|
}
|
2022-04-05 22:49:13 +08:00
|
|
|
|
|
|
|
rDocument.current = next;
|
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
const pageID = tldrawAPI?.getPage()?.id;
|
|
|
|
if (next.pageStates[pageID]?.selectedIds.length > 0) {
|
2022-04-05 22:49:13 +08:00
|
|
|
// if a selected id is not in the list of shapes remove it from list
|
2022-04-26 21:55:05 +08:00
|
|
|
next.pageStates[pageID]?.selectedIds.map((k) => {
|
|
|
|
if (!next.pages[pageID].shapes[k]) {
|
|
|
|
next.pageStates[pageID].selectedIds =
|
|
|
|
next.pageStates[pageID].selectedIds.filter(
|
|
|
|
(id) => id !== k
|
|
|
|
);
|
2022-04-05 22:49:13 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setDoc(next);
|
2022-04-26 21:55:05 +08:00
|
|
|
//
|
|
|
|
if (
|
|
|
|
tldrawAPI &&
|
|
|
|
!_.isEqual(shapes, prevShapes) &&
|
|
|
|
!_.isEqual(assets, ass)
|
|
|
|
) {
|
2022-04-11 00:31:12 +08:00
|
|
|
setAss(assets);
|
2022-04-26 21:55:05 +08:00
|
|
|
tldrawAPI?.replacePageContent(next?.pages[pageID]?.shapes, {}, assets);
|
2022-04-11 00:31:12 +08:00
|
|
|
}
|
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
if (tldrawAPI && !_.isEqual(shapes, prevShapes) && !_.isEqual(assets, ass)) {
|
|
|
|
tldrawAPI?.replacePageContent(next?.pages[pageID]?.shapes, {}, assets);
|
|
|
|
}
|
|
|
|
|
|
|
|
// setDoc(next);
|
|
|
|
|
|
|
|
}, [assets, shapes, curPres, tldrawAPI]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
isPresenter && curPage && changeCurrentSlide(curPage?.id);
|
|
|
|
}, [curPage]);
|
2022-04-11 00:31:12 +08:00
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
React.useEffect(() => {
|
|
|
|
tldrawAPI &&
|
|
|
|
!isPresenter &&
|
|
|
|
curSlide?.activeSlide &&
|
|
|
|
tldrawAPI.changePage(curSlide?.activeSlide);
|
|
|
|
}, [curSlide]);
|
2022-04-05 22:49:13 +08:00
|
|
|
|
|
|
|
return (
|
2022-04-26 21:55:05 +08:00
|
|
|
<>
|
|
|
|
<Cursors
|
|
|
|
tldrawAPI={tldrawAPI}
|
|
|
|
currentUser={currentUser}
|
|
|
|
publishCursorUpdate={publishCursorUpdate}
|
|
|
|
>
|
|
|
|
<Tldraw
|
|
|
|
document={doc}
|
|
|
|
disableAssets={false}
|
|
|
|
onChangePage={(app, s, b, a) => {
|
|
|
|
setCurPage(app.getPage());
|
|
|
|
}}
|
|
|
|
onMount={(app) => {
|
|
|
|
setTLDrawAPI(app);
|
|
|
|
}}
|
|
|
|
onChange={handleChange}
|
|
|
|
onPersist={(e) => {
|
|
|
|
///////////// handle assets /////////////////////////
|
|
|
|
e?.assets?.forEach((a) => {
|
|
|
|
persistAsset(a);
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
showPages={false || isPresenter}
|
|
|
|
showUI={true || isPresenter}
|
|
|
|
showMenu={false}
|
|
|
|
showMultiplayerMenu={false}
|
|
|
|
// readOnly={!isPresenter}
|
2022-05-03 22:47:45 +08:00
|
|
|
|
|
|
|
onUndo={s => {
|
|
|
|
s?.selectedIds?.map(id => {
|
|
|
|
persistShape(s.getShape(id));
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
|
|
|
|
onRedo={s => {
|
|
|
|
s?.selectedIds?.map(id => {
|
|
|
|
persistShape(s.getShape(id));
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
|
|
|
|
onChangePage={(app, s, b, a) => {
|
|
|
|
if (curPage?.id !== app.getPage()?.id) setCurPage(app.getPage());
|
|
|
|
}}
|
|
|
|
onCommand={(e, s, g) => {
|
|
|
|
if (s.includes("session:complete:DrawSession")) {
|
|
|
|
Object.entries(rDocument?.current?.pages[e.getPage()?.id]?.shapes)
|
|
|
|
.filter(([k, s]) => s?.type === 'draw')
|
|
|
|
.forEach(([k, s]) => {
|
|
|
|
if (!e.prevShapes[k] || !k.includes('slide-background')) {
|
|
|
|
persistShape(s);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s.includes("style")
|
|
|
|
|| s?.includes("session:complete:ArrowSession")) {
|
|
|
|
e.selectedIds.forEach(id => {
|
|
|
|
persistShape(e.getShape(id));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s?.includes("session:complete:TransformSingleSession")
|
|
|
|
|| s?.includes("session:complete:TranslateSession")
|
|
|
|
|| s?.includes("updated_shapes")
|
|
|
|
|| s?.includes("session:complete:RotateSession")) {
|
|
|
|
e.selectedIds.forEach(id => {
|
|
|
|
persistShape(e.getShape(id));
|
|
|
|
//checks to find any bindings assosiated with the selected shapes.
|
|
|
|
//If any, they need to be updated as well.
|
|
|
|
const pageBindings = rDocument?.current?.pages[e.getPage()?.id]?.bindings;
|
|
|
|
const boundShapes = [];
|
|
|
|
if (pageBindings) {
|
|
|
|
Object.entries(pageBindings).map(([k,b]) => {
|
|
|
|
if (b.toId.includes(id)) {
|
|
|
|
boundShapes.push(rDocument?.current?.pages[e.getPage()?.id]?.shapes[b.fromId])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
//persist shape(s) that was updated by the client and any shapes bound to it.
|
|
|
|
boundShapes.forEach(bs => persistShape(bs))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s?.includes("session:complete:EraseSession") || s?.includes("delete")) {
|
|
|
|
shapes.forEach(s => {
|
|
|
|
const ids = e.shapes.map(ss => ss.id);
|
|
|
|
if (!ids.includes(s.id)) removeShape(s.id);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
|
2022-04-26 21:55:05 +08:00
|
|
|
/>
|
|
|
|
</Cursors>
|
|
|
|
</>
|
2022-04-05 22:49:13 +08:00
|
|
|
);
|
|
|
|
}
|