mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
refactor: rewrite collabWrapper to remove TDZs and simplify (#2834)
This commit is contained in:
parent
03f6d9c783
commit
0a0be839b9
16 changed files with 312 additions and 264 deletions
|
@ -6,10 +6,11 @@ import { APP_NAME, ENV, EVENT } from "../../constants";
|
|||
import { ImportedDataState } from "../../data/types";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import {
|
||||
getElementMap,
|
||||
getSceneVersion,
|
||||
getSyncableElements,
|
||||
} from "../../packages/excalidraw/index";
|
||||
import { AppState, Collaborator, Gesture } from "../../types";
|
||||
import { Collaborator, Gesture } from "../../types";
|
||||
import { resolvablePromise, withBatchedUpdates } from "../../utils";
|
||||
import {
|
||||
INITIAL_SCENE_UPDATE_TIMEOUT,
|
||||
|
@ -31,6 +32,7 @@ import {
|
|||
} from "../data/localStorage";
|
||||
import Portal from "./Portal";
|
||||
import RoomDialog from "./RoomDialog";
|
||||
import { createInverseContext } from "../../createInverseContext";
|
||||
|
||||
interface CollabState {
|
||||
isCollaborating: boolean;
|
||||
|
@ -56,17 +58,21 @@ type ReconciledElements = readonly ExcalidrawElement[] & {
|
|||
};
|
||||
|
||||
interface Props {
|
||||
children: (collab: CollabAPI) => React.ReactNode;
|
||||
// NOTE not type-safe because the refObject may in fact not be initialized
|
||||
// with ExcalidrawImperativeAPI yet
|
||||
excalidrawRef: React.MutableRefObject<ExcalidrawImperativeAPI>;
|
||||
excalidrawAPI: ExcalidrawImperativeAPI;
|
||||
}
|
||||
|
||||
const {
|
||||
Context: CollabContext,
|
||||
Consumer: CollabContextConsumer,
|
||||
Provider: CollabContextProvider,
|
||||
} = createInverseContext<{ api: CollabAPI | null }>({ api: null });
|
||||
|
||||
export { CollabContext, CollabContextConsumer };
|
||||
|
||||
class CollabWrapper extends PureComponent<Props, CollabState> {
|
||||
portal: Portal;
|
||||
excalidrawAPI: Props["excalidrawAPI"];
|
||||
private socketInitializationTimer?: NodeJS.Timeout;
|
||||
private excalidrawRef: Props["excalidrawRef"];
|
||||
excalidrawAppState?: AppState;
|
||||
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||
private collaborators = new Map<string, Collaborator>();
|
||||
|
||||
|
@ -80,7 +86,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
activeRoomLink: "",
|
||||
};
|
||||
this.portal = new Portal(this);
|
||||
this.excalidrawRef = props.excalidrawRef;
|
||||
this.excalidrawAPI = props.excalidrawAPI;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -142,7 +148,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
|
||||
saveCollabRoomToFirebase = async (
|
||||
syncableElements: ExcalidrawElement[] = getSyncableElements(
|
||||
this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||
),
|
||||
) => {
|
||||
try {
|
||||
|
@ -154,13 +160,13 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
|
||||
openPortal = async () => {
|
||||
window.history.pushState({}, APP_NAME, await generateCollaborationLink());
|
||||
const elements = this.excalidrawRef.current!.getSceneElements();
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
// remove deleted elements from elements array & history to ensure we don't
|
||||
// expose potentially sensitive user data in case user manually deletes
|
||||
// existing elements (or clears scene), which would otherwise be persisted
|
||||
// to database even if deleted before creating the room.
|
||||
this.excalidrawRef.current!.history.clear();
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
this.excalidrawAPI.history.clear();
|
||||
this.excalidrawAPI.updateScene({
|
||||
elements,
|
||||
commitToHistory: true,
|
||||
});
|
||||
|
@ -175,7 +181,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
|
||||
private destroySocketClient = () => {
|
||||
this.collaborators = new Map();
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
this.excalidrawAPI.updateScene({
|
||||
collaborators: this.collaborators,
|
||||
});
|
||||
this.setState({
|
||||
|
@ -265,7 +271,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
user.selectedElementIds = selectedElementIds;
|
||||
user.username = username;
|
||||
collaborators.set(socketId, user);
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
this.excalidrawAPI.updateScene({
|
||||
collaborators,
|
||||
});
|
||||
break;
|
||||
|
@ -300,7 +306,55 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
private reconcileElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
): ReconciledElements => {
|
||||
const newElements = this.portal.reconcileElements(elements);
|
||||
const currentElements = this.getSceneElementsIncludingDeleted();
|
||||
// create a map of ids so we don't have to iterate
|
||||
// over the array more than once.
|
||||
const localElementMap = getElementMap(currentElements);
|
||||
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
|
||||
// Reconcile
|
||||
const newElements: readonly ExcalidrawElement[] = elements
|
||||
.reduce((elements, element) => {
|
||||
// if the remote element references one that's currently
|
||||
// edited on local, skip it (it'll be added in the next step)
|
||||
if (
|
||||
element.id === appState.editingElement?.id ||
|
||||
element.id === appState.resizingElement?.id ||
|
||||
element.id === appState.draggingElement?.id
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version > element.version
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
delete localElementMap[element.id];
|
||||
} else if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version === element.version &&
|
||||
localElementMap[element.id].versionNonce !== element.versionNonce
|
||||
) {
|
||||
// resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
||||
if (localElementMap[element.id].versionNonce < element.versionNonce) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
} else {
|
||||
// it should be highly unlikely that the two versionNonces are the same. if we are
|
||||
// really worried about this, we can replace the versionNonce with the socket id.
|
||||
elements.push(element);
|
||||
}
|
||||
delete localElementMap[element.id];
|
||||
} else {
|
||||
elements.push(element);
|
||||
delete localElementMap[element.id];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [] as Mutable<typeof elements>)
|
||||
// add local elements that weren't deleted or on remote
|
||||
.concat(...Object.values(localElementMap));
|
||||
|
||||
// Avoid broadcasting to the rest of the collaborators the scene
|
||||
// we just received!
|
||||
|
@ -319,10 +373,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
}: { init?: boolean; initFromSnapshot?: boolean } = {},
|
||||
) => {
|
||||
if (init || initFromSnapshot) {
|
||||
this.excalidrawRef.current!.setScrollToCenter(elements);
|
||||
this.excalidrawAPI.setScrollToCenter(elements);
|
||||
}
|
||||
|
||||
this.excalidrawRef.current!.updateScene({
|
||||
this.excalidrawAPI.updateScene({
|
||||
elements,
|
||||
commitToHistory: !!init,
|
||||
});
|
||||
|
@ -331,7 +385,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
// when we receive any messages from another peer. This UX can be pretty rough -- if you
|
||||
// undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
|
||||
// right now we think this is the right tradeoff.
|
||||
this.excalidrawRef.current!.history.clear();
|
||||
this.excalidrawAPI.history.clear();
|
||||
};
|
||||
|
||||
setCollaborators(sockets: string[]) {
|
||||
|
@ -347,7 +401,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
}
|
||||
}
|
||||
this.collaborators = collaborators;
|
||||
this.excalidrawRef.current!.updateScene({ collaborators });
|
||||
this.excalidrawAPI.updateScene({ collaborators });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -360,7 +414,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
};
|
||||
|
||||
public getSceneElementsIncludingDeleted = () => {
|
||||
return this.excalidrawRef.current!.getSceneElementsIncludingDeleted();
|
||||
return this.excalidrawAPI.getSceneElementsIncludingDeleted();
|
||||
};
|
||||
|
||||
onPointerUpdate = (payload: {
|
||||
|
@ -373,11 +427,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
this.portal.broadcastMouseLocation(payload);
|
||||
};
|
||||
|
||||
broadcastElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
state: AppState,
|
||||
) => {
|
||||
this.excalidrawAppState = state;
|
||||
broadcastElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
if (
|
||||
getSceneVersion(elements) >
|
||||
this.getLastBroadcastedOrReceivedSceneVersion()
|
||||
|
@ -396,7 +446,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
this.portal.broadcastScene(
|
||||
SCENE.UPDATE,
|
||||
getSyncableElements(
|
||||
this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
@ -425,8 +475,23 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
});
|
||||
};
|
||||
|
||||
/** PRIVATE. Use `this.getContextValue()` instead. */
|
||||
private contextValue: CollabAPI | null = null;
|
||||
|
||||
/** Getter of context value. Returned object is stable. */
|
||||
getContextValue = (): CollabAPI => {
|
||||
this.contextValue = this.contextValue || ({} as CollabAPI);
|
||||
|
||||
this.contextValue.isCollaborating = this.state.isCollaborating;
|
||||
this.contextValue.username = this.state.username;
|
||||
this.contextValue.onPointerUpdate = this.onPointerUpdate;
|
||||
this.contextValue.initializeSocketClient = this.initializeSocketClient;
|
||||
this.contextValue.onCollabButtonClick = this.onCollabButtonClick;
|
||||
this.contextValue.broadcastElements = this.broadcastElements;
|
||||
return this.contextValue;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { modalIsShown, username, errorMessage, activeRoomLink } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -450,14 +515,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|||
onClose={() => this.setState({ errorMessage: "" })}
|
||||
/>
|
||||
)}
|
||||
{children({
|
||||
isCollaborating: this.state.isCollaborating,
|
||||
username: this.state.username,
|
||||
onPointerUpdate: this.onPointerUpdate,
|
||||
initializeSocketClient: this.initializeSocketClient,
|
||||
onCollabButtonClick: this.onCollabButtonClick,
|
||||
broadcastElements: this.broadcastElements,
|
||||
})}
|
||||
<CollabContextProvider
|
||||
value={{
|
||||
api: this.getContextValue(),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,23 +6,20 @@ import {
|
|||
|
||||
import CollabWrapper from "./CollabWrapper";
|
||||
|
||||
import {
|
||||
getElementMap,
|
||||
getSyncableElements,
|
||||
} from "../../packages/excalidraw/index";
|
||||
import { getSyncableElements } from "../../packages/excalidraw/index";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { BROADCAST, SCENE } from "../app_constants";
|
||||
|
||||
class Portal {
|
||||
app: CollabWrapper;
|
||||
collab: CollabWrapper;
|
||||
socket: SocketIOClient.Socket | null = null;
|
||||
socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
|
||||
roomId: string | null = null;
|
||||
roomKey: string | null = null;
|
||||
broadcastedElementVersions: Map<string, number> = new Map();
|
||||
|
||||
constructor(app: CollabWrapper) {
|
||||
this.app = app;
|
||||
constructor(collab: CollabWrapper) {
|
||||
this.collab = collab;
|
||||
}
|
||||
|
||||
open(socket: SocketIOClient.Socket, id: string, key: string) {
|
||||
|
@ -30,7 +27,7 @@ class Portal {
|
|||
this.roomId = id;
|
||||
this.roomKey = key;
|
||||
|
||||
// Initialize socket listeners (moving from App)
|
||||
// Initialize socket listeners
|
||||
this.socket.on("init-room", () => {
|
||||
if (this.socket) {
|
||||
this.socket.emit("join-room", this.roomId);
|
||||
|
@ -39,12 +36,12 @@ class Portal {
|
|||
this.socket.on("new-user", async (_socketId: string) => {
|
||||
this.broadcastScene(
|
||||
SCENE.INIT,
|
||||
getSyncableElements(this.app.getSceneElementsIncludingDeleted()),
|
||||
getSyncableElements(this.collab.getSceneElementsIncludingDeleted()),
|
||||
/* syncAll */ true,
|
||||
);
|
||||
});
|
||||
this.socket.on("room-user-change", (clients: string[]) => {
|
||||
this.app.setCollaborators(clients);
|
||||
this.collab.setCollaborators(clients);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -125,10 +122,10 @@ class Portal {
|
|||
data as SocketUpdateData,
|
||||
);
|
||||
|
||||
if (syncAll && this.app.state.isCollaborating) {
|
||||
if (syncAll && this.collab.state.isCollaborating) {
|
||||
await Promise.all([
|
||||
broadcastPromise,
|
||||
this.app.saveCollabRoomToFirebase(syncableElements),
|
||||
this.collab.saveCollabRoomToFirebase(syncableElements),
|
||||
]);
|
||||
} else {
|
||||
await broadcastPromise;
|
||||
|
@ -146,9 +143,9 @@ class Portal {
|
|||
socketId: this.socket.id,
|
||||
pointer: payload.pointer,
|
||||
button: payload.button || "up",
|
||||
selectedElementIds:
|
||||
this.app.excalidrawAppState?.selectedElementIds || {},
|
||||
username: this.app.state.username,
|
||||
selectedElementIds: this.collab.excalidrawAPI.getAppState()
|
||||
.selectedElementIds,
|
||||
username: this.collab.state.username,
|
||||
},
|
||||
};
|
||||
return this._broadcastSocketData(
|
||||
|
@ -157,62 +154,6 @@ class Portal {
|
|||
);
|
||||
}
|
||||
};
|
||||
|
||||
reconcileElements = (
|
||||
sceneElements: readonly ExcalidrawElement[],
|
||||
): readonly ExcalidrawElement[] => {
|
||||
const currentElements = this.app.getSceneElementsIncludingDeleted();
|
||||
// create a map of ids so we don't have to iterate
|
||||
// over the array more than once.
|
||||
const localElementMap = getElementMap(currentElements);
|
||||
|
||||
// Reconcile
|
||||
return (
|
||||
sceneElements
|
||||
.reduce((elements, element) => {
|
||||
// if the remote element references one that's currently
|
||||
// edited on local, skip it (it'll be added in the next step)
|
||||
if (
|
||||
element.id === this.app.excalidrawAppState?.editingElement?.id ||
|
||||
element.id === this.app.excalidrawAppState?.resizingElement?.id ||
|
||||
element.id === this.app.excalidrawAppState?.draggingElement?.id
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version > element.version
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
delete localElementMap[element.id];
|
||||
} else if (
|
||||
localElementMap.hasOwnProperty(element.id) &&
|
||||
localElementMap[element.id].version === element.version &&
|
||||
localElementMap[element.id].versionNonce !== element.versionNonce
|
||||
) {
|
||||
// resolve conflicting edits deterministically by taking the one with the lowest versionNonce
|
||||
if (
|
||||
localElementMap[element.id].versionNonce < element.versionNonce
|
||||
) {
|
||||
elements.push(localElementMap[element.id]);
|
||||
} else {
|
||||
// it should be highly unlikely that the two versionNonces are the same. if we are
|
||||
// really worried about this, we can replace the versionNonce with the socket id.
|
||||
elements.push(element);
|
||||
}
|
||||
delete localElementMap[element.id];
|
||||
} else {
|
||||
elements.push(element);
|
||||
delete localElementMap[element.id];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [] as Mutable<typeof sceneElements>)
|
||||
// add local elements that weren't deleted or on remote
|
||||
.concat(...Object.values(localElementMap))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Portal;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
|
@ -17,12 +18,13 @@ import {
|
|||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||
import { Language, t } from "../i18n";
|
||||
import Excalidraw, {
|
||||
defaultLang,
|
||||
languages,
|
||||
} from "../packages/excalidraw/index";
|
||||
import { AppState, ExcalidrawAPIRefValue } from "../types";
|
||||
import { AppState } from "../types";
|
||||
import {
|
||||
debounce,
|
||||
getVersion,
|
||||
|
@ -30,7 +32,11 @@ import {
|
|||
resolvablePromise,
|
||||
} from "../utils";
|
||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
|
||||
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
|
||||
import CollabWrapper, {
|
||||
CollabAPI,
|
||||
CollabContext,
|
||||
CollabContextConsumer,
|
||||
} from "./collab/CollabWrapper";
|
||||
import { LanguageList } from "./components/LanguageList";
|
||||
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
|
||||
import { loadFromFirebase } from "./data/firebase";
|
||||
|
@ -49,15 +55,6 @@ languageDetector.init({
|
|||
checkWhitelist: false,
|
||||
});
|
||||
|
||||
const excalidrawRef: React.MutableRefObject<
|
||||
MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
|
||||
> = {
|
||||
current: {
|
||||
readyPromise: resolvablePromise(),
|
||||
ready: false,
|
||||
},
|
||||
};
|
||||
|
||||
const saveDebounced = debounce(
|
||||
(elements: readonly ExcalidrawElement[], state: AppState) => {
|
||||
saveToLocalStorage(elements, state);
|
||||
|
@ -191,7 +188,7 @@ const initializeScene = async (opts: {
|
|||
return null;
|
||||
};
|
||||
|
||||
function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
||||
function ExcalidrawWrapper() {
|
||||
// dimensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -226,35 +223,40 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||
initialStatePromiseRef.current.promise = resolvablePromise<ImportedDataState | null>();
|
||||
}
|
||||
|
||||
const { collab } = props;
|
||||
|
||||
useEffect(() => {
|
||||
// Delayed so that the app has a time to load the latest SW
|
||||
setTimeout(() => {
|
||||
trackEvent("load", "version", getVersion());
|
||||
}, VERSION_TIMEOUT);
|
||||
}, []);
|
||||
|
||||
excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
|
||||
initializeScene({
|
||||
resetScene: excalidrawApi.resetScene,
|
||||
initializeSocketClient: collab.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
initialStatePromiseRef.current.promise.resolve(scene);
|
||||
});
|
||||
const [
|
||||
excalidrawAPI,
|
||||
excalidrawRefCallback,
|
||||
] = useCallbackRefState<ExcalidrawImperativeAPI>();
|
||||
|
||||
const collabAPI = useContext(CollabContext)?.api;
|
||||
|
||||
useEffect(() => {
|
||||
if (!collabAPI || !excalidrawAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
initializeScene({
|
||||
resetScene: excalidrawAPI.resetScene,
|
||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
initialStatePromiseRef.current.promise.resolve(scene);
|
||||
});
|
||||
|
||||
const onHashChange = (_: HashChangeEvent) => {
|
||||
const api = excalidrawRef.current!;
|
||||
if (!api.ready) {
|
||||
return;
|
||||
}
|
||||
if (window.location.hash.length > 1) {
|
||||
initializeScene({
|
||||
resetScene: api.resetScene,
|
||||
initializeSocketClient: collab.initializeSocketClient,
|
||||
resetScene: excalidrawAPI.resetScene,
|
||||
initializeSocketClient: collabAPI.initializeSocketClient,
|
||||
}).then((scene) => {
|
||||
if (scene) {
|
||||
api.updateScene(scene);
|
||||
excalidrawAPI.updateScene(scene);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -273,7 +275,7 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||
window.removeEventListener(EVENT.BLUR, onBlur, false);
|
||||
clearTimeout(titleTimeout);
|
||||
};
|
||||
}, [collab.initializeSocketClient]);
|
||||
}, [collabAPI, excalidrawAPI]);
|
||||
|
||||
useEffect(() => {
|
||||
languageDetector.cacheUserLanguage(langCode);
|
||||
|
@ -284,8 +286,8 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||
appState: AppState,
|
||||
) => {
|
||||
saveDebounced(elements, appState);
|
||||
if (collab.isCollaborating) {
|
||||
collab.broadcastElements(elements, appState);
|
||||
if (collabAPI?.isCollaborating) {
|
||||
collabAPI.broadcastElements(elements);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -343,19 +345,20 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||
return (
|
||||
<>
|
||||
<Excalidraw
|
||||
ref={excalidrawRef}
|
||||
ref={excalidrawRefCallback}
|
||||
onChange={onChange}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
initialData={initialStatePromiseRef.current.promise}
|
||||
user={{ name: collab.username }}
|
||||
onCollabButtonClick={collab.onCollabButtonClick}
|
||||
isCollaborating={collab.isCollaborating}
|
||||
onPointerUpdate={collab.onPointerUpdate}
|
||||
user={{ name: collabAPI?.username }}
|
||||
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
||||
isCollaborating={collabAPI?.isCollaborating}
|
||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||
onExportToBackend={onExportToBackend}
|
||||
renderFooter={renderFooter}
|
||||
langCode={langCode}
|
||||
/>
|
||||
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
||||
{errorMessage && (
|
||||
<ErrorDialog
|
||||
message={errorMessage}
|
||||
|
@ -369,13 +372,9 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
|
|||
export default function ExcalidrawApp() {
|
||||
return (
|
||||
<TopErrorBoundary>
|
||||
<CollabWrapper
|
||||
excalidrawRef={
|
||||
excalidrawRef as React.MutableRefObject<ExcalidrawImperativeAPI>
|
||||
}
|
||||
>
|
||||
{(collab) => <ExcalidrawWrapper collab={collab} />}
|
||||
</CollabWrapper>
|
||||
<CollabContextConsumer>
|
||||
<ExcalidrawWrapper />
|
||||
</CollabContextConsumer>
|
||||
</TopErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue