|
@@ -8,10 +8,12 @@ import {
|
|
ExcalidrawElement,
|
|
ExcalidrawElement,
|
|
InitializedExcalidrawImageElement,
|
|
InitializedExcalidrawImageElement,
|
|
} from "../../element/types";
|
|
} from "../../element/types";
|
|
-import { getSceneVersion } from "../../packages/excalidraw/index";
|
|
|
|
|
|
+import {
|
|
|
|
+ getSceneVersion,
|
|
|
|
+ restoreElements,
|
|
|
|
+} from "../../packages/excalidraw/index";
|
|
import { Collaborator, Gesture } from "../../types";
|
|
import { Collaborator, Gesture } from "../../types";
|
|
import {
|
|
import {
|
|
- getFrame,
|
|
|
|
preventUnload,
|
|
preventUnload,
|
|
resolvablePromise,
|
|
resolvablePromise,
|
|
withBatchedUpdates,
|
|
withBatchedUpdates,
|
|
@@ -47,11 +49,9 @@ import {
|
|
} from "../data/localStorage";
|
|
} from "../data/localStorage";
|
|
import Portal from "./Portal";
|
|
import Portal from "./Portal";
|
|
import RoomDialog from "./RoomDialog";
|
|
import RoomDialog from "./RoomDialog";
|
|
-import { createInverseContext } from "../../createInverseContext";
|
|
|
|
import { t } from "../../i18n";
|
|
import { t } from "../../i18n";
|
|
import { UserIdleState } from "../../types";
|
|
import { UserIdleState } from "../../types";
|
|
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
|
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants";
|
|
-import { trackEvent } from "../../analytics";
|
|
|
|
import {
|
|
import {
|
|
encodeFilesForUpload,
|
|
encodeFilesForUpload,
|
|
FileManager,
|
|
FileManager,
|
|
@@ -70,52 +70,45 @@ import {
|
|
import { decryptData } from "../../data/encryption";
|
|
import { decryptData } from "../../data/encryption";
|
|
import { resetBrowserStateVersions } from "../data/tabSync";
|
|
import { resetBrowserStateVersions } from "../data/tabSync";
|
|
import { LocalData } from "../data/LocalData";
|
|
import { LocalData } from "../data/LocalData";
|
|
|
|
+import { atom, useAtom } from "jotai";
|
|
|
|
+import { jotaiStore } from "../../jotai";
|
|
|
|
+
|
|
|
|
+export const collabAPIAtom = atom<CollabAPI | null>(null);
|
|
|
|
+export const collabDialogShownAtom = atom(false);
|
|
|
|
+export const isCollaboratingAtom = atom(false);
|
|
|
|
|
|
interface CollabState {
|
|
interface CollabState {
|
|
- modalIsShown: boolean;
|
|
|
|
errorMessage: string;
|
|
errorMessage: string;
|
|
username: string;
|
|
username: string;
|
|
- userState: UserIdleState;
|
|
|
|
activeRoomLink: string;
|
|
activeRoomLink: string;
|
|
}
|
|
}
|
|
|
|
|
|
-type CollabInstance = InstanceType<typeof CollabWrapper>;
|
|
|
|
|
|
+type CollabInstance = InstanceType<typeof Collab>;
|
|
|
|
|
|
export interface CollabAPI {
|
|
export interface CollabAPI {
|
|
/** function so that we can access the latest value from stale callbacks */
|
|
/** function so that we can access the latest value from stale callbacks */
|
|
isCollaborating: () => boolean;
|
|
isCollaborating: () => boolean;
|
|
- username: CollabState["username"];
|
|
|
|
- userState: CollabState["userState"];
|
|
|
|
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
|
onPointerUpdate: CollabInstance["onPointerUpdate"];
|
|
- initializeSocketClient: CollabInstance["initializeSocketClient"];
|
|
|
|
- onCollabButtonClick: CollabInstance["onCollabButtonClick"];
|
|
|
|
|
|
+ startCollaboration: CollabInstance["startCollaboration"];
|
|
|
|
+ stopCollaboration: CollabInstance["stopCollaboration"];
|
|
syncElements: CollabInstance["syncElements"];
|
|
syncElements: CollabInstance["syncElements"];
|
|
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
|
|
fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
|
|
setUsername: (username: string) => void;
|
|
setUsername: (username: string) => void;
|
|
}
|
|
}
|
|
|
|
|
|
-interface Props {
|
|
|
|
|
|
+interface PublicProps {
|
|
excalidrawAPI: ExcalidrawImperativeAPI;
|
|
excalidrawAPI: ExcalidrawImperativeAPI;
|
|
- onRoomClose?: () => void;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-const {
|
|
|
|
- Context: CollabContext,
|
|
|
|
- Consumer: CollabContextConsumer,
|
|
|
|
- Provider: CollabContextProvider,
|
|
|
|
-} = createInverseContext<{ api: CollabAPI | null }>({ api: null });
|
|
|
|
-
|
|
|
|
-export { CollabContext, CollabContextConsumer };
|
|
|
|
|
|
+type Props = PublicProps & { modalIsShown: boolean };
|
|
|
|
|
|
-class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
|
+class Collab extends PureComponent<Props, CollabState> {
|
|
portal: Portal;
|
|
portal: Portal;
|
|
fileManager: FileManager;
|
|
fileManager: FileManager;
|
|
excalidrawAPI: Props["excalidrawAPI"];
|
|
excalidrawAPI: Props["excalidrawAPI"];
|
|
activeIntervalId: number | null;
|
|
activeIntervalId: number | null;
|
|
idleTimeoutId: number | null;
|
|
idleTimeoutId: number | null;
|
|
|
|
|
|
- // marked as private to ensure we don't change it outside this class
|
|
|
|
- private _isCollaborating: boolean = false;
|
|
|
|
private socketInitializationTimer?: number;
|
|
private socketInitializationTimer?: number;
|
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
|
private collaborators = new Map<string, Collaborator>();
|
|
private collaborators = new Map<string, Collaborator>();
|
|
@@ -123,10 +116,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
constructor(props: Props) {
|
|
constructor(props: Props) {
|
|
super(props);
|
|
super(props);
|
|
this.state = {
|
|
this.state = {
|
|
- modalIsShown: false,
|
|
|
|
errorMessage: "",
|
|
errorMessage: "",
|
|
username: importUsernameFromLocalStorage() || "",
|
|
username: importUsernameFromLocalStorage() || "",
|
|
- userState: UserIdleState.ACTIVE,
|
|
|
|
activeRoomLink: "",
|
|
activeRoomLink: "",
|
|
};
|
|
};
|
|
this.portal = new Portal(this);
|
|
this.portal = new Portal(this);
|
|
@@ -164,6 +155,18 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
|
window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
|
|
window.addEventListener(EVENT.UNLOAD, this.onUnload);
|
|
window.addEventListener(EVENT.UNLOAD, this.onUnload);
|
|
|
|
|
|
|
|
+ const collabAPI: CollabAPI = {
|
|
|
|
+ isCollaborating: this.isCollaborating,
|
|
|
|
+ onPointerUpdate: this.onPointerUpdate,
|
|
|
|
+ startCollaboration: this.startCollaboration,
|
|
|
|
+ syncElements: this.syncElements,
|
|
|
|
+ fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
|
|
|
|
+ stopCollaboration: this.stopCollaboration,
|
|
|
|
+ setUsername: this.setUsername,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ jotaiStore.set(collabAPIAtom, collabAPI);
|
|
|
|
+
|
|
if (
|
|
if (
|
|
process.env.NODE_ENV === ENV.TEST ||
|
|
process.env.NODE_ENV === ENV.TEST ||
|
|
process.env.NODE_ENV === ENV.DEVELOPMENT
|
|
process.env.NODE_ENV === ENV.DEVELOPMENT
|
|
@@ -196,7 +199,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- isCollaborating = () => this._isCollaborating;
|
|
|
|
|
|
+ isCollaborating = () => jotaiStore.get(isCollaboratingAtom)!;
|
|
|
|
+
|
|
|
|
+ private setIsCollaborating = (isCollaborating: boolean) => {
|
|
|
|
+ jotaiStore.set(isCollaboratingAtom, isCollaborating);
|
|
|
|
+ };
|
|
|
|
|
|
private onUnload = () => {
|
|
private onUnload = () => {
|
|
this.destroySocketClient({ isUnload: true });
|
|
this.destroySocketClient({ isUnload: true });
|
|
@@ -208,7 +215,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
);
|
|
);
|
|
|
|
|
|
if (
|
|
if (
|
|
- this._isCollaborating &&
|
|
|
|
|
|
+ this.isCollaborating() &&
|
|
(this.fileManager.shouldPreventUnload(syncableElements) ||
|
|
(this.fileManager.shouldPreventUnload(syncableElements) ||
|
|
!isSavedToFirebase(this.portal, syncableElements))
|
|
!isSavedToFirebase(this.portal, syncableElements))
|
|
) {
|
|
) {
|
|
@@ -252,12 +259,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
- openPortal = async () => {
|
|
|
|
- trackEvent("share", "room creation", `ui (${getFrame()})`);
|
|
|
|
- return this.initializeSocketClient(null);
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- closePortal = () => {
|
|
|
|
|
|
+ stopCollaboration = (keepRemoteState = true) => {
|
|
this.queueBroadcastAllElements.cancel();
|
|
this.queueBroadcastAllElements.cancel();
|
|
this.queueSaveToFirebase.cancel();
|
|
this.queueSaveToFirebase.cancel();
|
|
this.loadImageFiles.cancel();
|
|
this.loadImageFiles.cancel();
|
|
@@ -267,16 +269,26 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
|
this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
|
),
|
|
),
|
|
);
|
|
);
|
|
- if (window.confirm(t("alerts.collabStopOverridePrompt"))) {
|
|
|
|
|
|
+
|
|
|
|
+ if (this.portal.socket && this.fallbackInitializationHandler) {
|
|
|
|
+ this.portal.socket.off(
|
|
|
|
+ "connect_error",
|
|
|
|
+ this.fallbackInitializationHandler,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!keepRemoteState) {
|
|
|
|
+ LocalData.fileStorage.reset();
|
|
|
|
+ this.destroySocketClient();
|
|
|
|
+ } else if (window.confirm(t("alerts.collabStopOverridePrompt"))) {
|
|
// hack to ensure that we prefer we disregard any new browser state
|
|
// hack to ensure that we prefer we disregard any new browser state
|
|
// that could have been saved in other tabs while we were collaborating
|
|
// that could have been saved in other tabs while we were collaborating
|
|
resetBrowserStateVersions();
|
|
resetBrowserStateVersions();
|
|
|
|
|
|
window.history.pushState({}, APP_NAME, window.location.origin);
|
|
window.history.pushState({}, APP_NAME, window.location.origin);
|
|
this.destroySocketClient();
|
|
this.destroySocketClient();
|
|
- trackEvent("share", "room closed");
|
|
|
|
|
|
|
|
- this.props.onRoomClose?.();
|
|
|
|
|
|
+ LocalData.fileStorage.reset();
|
|
|
|
|
|
const elements = this.excalidrawAPI
|
|
const elements = this.excalidrawAPI
|
|
.getSceneElementsIncludingDeleted()
|
|
.getSceneElementsIncludingDeleted()
|
|
@@ -295,20 +307,20 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
};
|
|
};
|
|
|
|
|
|
private destroySocketClient = (opts?: { isUnload: boolean }) => {
|
|
private destroySocketClient = (opts?: { isUnload: boolean }) => {
|
|
|
|
+ this.lastBroadcastedOrReceivedSceneVersion = -1;
|
|
|
|
+ this.portal.close();
|
|
|
|
+ this.fileManager.reset();
|
|
if (!opts?.isUnload) {
|
|
if (!opts?.isUnload) {
|
|
|
|
+ this.setIsCollaborating(false);
|
|
|
|
+ this.setState({
|
|
|
|
+ activeRoomLink: "",
|
|
|
|
+ });
|
|
this.collaborators = new Map();
|
|
this.collaborators = new Map();
|
|
this.excalidrawAPI.updateScene({
|
|
this.excalidrawAPI.updateScene({
|
|
collaborators: this.collaborators,
|
|
collaborators: this.collaborators,
|
|
});
|
|
});
|
|
- this.setState({
|
|
|
|
- activeRoomLink: "",
|
|
|
|
- });
|
|
|
|
- this._isCollaborating = false;
|
|
|
|
LocalData.resumeSave("collaboration");
|
|
LocalData.resumeSave("collaboration");
|
|
}
|
|
}
|
|
- this.lastBroadcastedOrReceivedSceneVersion = -1;
|
|
|
|
- this.portal.close();
|
|
|
|
- this.fileManager.reset();
|
|
|
|
};
|
|
};
|
|
|
|
|
|
private fetchImageFilesFromFirebase = async (scene: {
|
|
private fetchImageFilesFromFirebase = async (scene: {
|
|
@@ -349,7 +361,9 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
- private initializeSocketClient = async (
|
|
|
|
|
|
+ private fallbackInitializationHandler: null | (() => any) = null;
|
|
|
|
+
|
|
|
|
+ startCollaboration = async (
|
|
existingRoomLinkData: null | { roomId: string; roomKey: string },
|
|
existingRoomLinkData: null | { roomId: string; roomKey: string },
|
|
): Promise<ImportedDataState | null> => {
|
|
): Promise<ImportedDataState | null> => {
|
|
if (this.portal.socket) {
|
|
if (this.portal.socket) {
|
|
@@ -372,13 +386,23 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
|
const scenePromise = resolvablePromise<ImportedDataState | null>();
|
|
const scenePromise = resolvablePromise<ImportedDataState | null>();
|
|
|
|
|
|
- this._isCollaborating = true;
|
|
|
|
|
|
+ this.setIsCollaborating(true);
|
|
LocalData.pauseSave("collaboration");
|
|
LocalData.pauseSave("collaboration");
|
|
|
|
|
|
const { default: socketIOClient } = await import(
|
|
const { default: socketIOClient } = await import(
|
|
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
|
/* webpackChunkName: "socketIoClient" */ "socket.io-client"
|
|
);
|
|
);
|
|
|
|
|
|
|
|
+ const fallbackInitializationHandler = () => {
|
|
|
|
+ this.initializeRoom({
|
|
|
|
+ roomLinkData: existingRoomLinkData,
|
|
|
|
+ fetchScene: true,
|
|
|
|
+ }).then((scene) => {
|
|
|
|
+ scenePromise.resolve(scene);
|
|
|
|
+ });
|
|
|
|
+ };
|
|
|
|
+ this.fallbackInitializationHandler = fallbackInitializationHandler;
|
|
|
|
+
|
|
try {
|
|
try {
|
|
const socketServerData = await getCollabServer();
|
|
const socketServerData = await getCollabServer();
|
|
|
|
|
|
@@ -391,6 +415,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
roomId,
|
|
roomId,
|
|
roomKey,
|
|
roomKey,
|
|
);
|
|
);
|
|
|
|
+
|
|
|
|
+ this.portal.socket.once("connect_error", fallbackInitializationHandler);
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
console.error(error);
|
|
this.setState({ errorMessage: error.message });
|
|
this.setState({ errorMessage: error.message });
|
|
@@ -419,13 +445,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
|
// fallback in case you're not alone in the room but still don't receive
|
|
// fallback in case you're not alone in the room but still don't receive
|
|
// initial SCENE_INIT message
|
|
// initial SCENE_INIT message
|
|
- this.socketInitializationTimer = window.setTimeout(() => {
|
|
|
|
- this.initializeRoom({
|
|
|
|
- roomLinkData: existingRoomLinkData,
|
|
|
|
- fetchScene: true,
|
|
|
|
- });
|
|
|
|
- scenePromise.resolve(null);
|
|
|
|
- }, INITIAL_SCENE_UPDATE_TIMEOUT);
|
|
|
|
|
|
+ this.socketInitializationTimer = window.setTimeout(
|
|
|
|
+ fallbackInitializationHandler,
|
|
|
|
+ INITIAL_SCENE_UPDATE_TIMEOUT,
|
|
|
|
+ );
|
|
|
|
|
|
// All socket listeners are moving to Portal
|
|
// All socket listeners are moving to Portal
|
|
this.portal.socket.on(
|
|
this.portal.socket.on(
|
|
@@ -530,6 +553,12 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
}
|
|
}
|
|
| { fetchScene: false; roomLinkData?: null }) => {
|
|
| { fetchScene: false; roomLinkData?: null }) => {
|
|
clearTimeout(this.socketInitializationTimer!);
|
|
clearTimeout(this.socketInitializationTimer!);
|
|
|
|
+ if (this.portal.socket && this.fallbackInitializationHandler) {
|
|
|
|
+ this.portal.socket.off(
|
|
|
|
+ "connect_error",
|
|
|
|
+ this.fallbackInitializationHandler,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
if (fetchScene && roomLinkData && this.portal.socket) {
|
|
if (fetchScene && roomLinkData && this.portal.socket) {
|
|
this.excalidrawAPI.resetScene();
|
|
this.excalidrawAPI.resetScene();
|
|
|
|
|
|
@@ -567,6 +596,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
const localElements = this.getSceneElementsIncludingDeleted();
|
|
const localElements = this.getSceneElementsIncludingDeleted();
|
|
const appState = this.excalidrawAPI.getAppState();
|
|
const appState = this.excalidrawAPI.getAppState();
|
|
|
|
|
|
|
|
+ remoteElements = restoreElements(remoteElements, null);
|
|
|
|
+
|
|
const reconciledElements = _reconcileElements(
|
|
const reconciledElements = _reconcileElements(
|
|
localElements,
|
|
localElements,
|
|
remoteElements,
|
|
remoteElements,
|
|
@@ -672,19 +703,17 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
};
|
|
};
|
|
|
|
|
|
setCollaborators(sockets: string[]) {
|
|
setCollaborators(sockets: string[]) {
|
|
- this.setState((state) => {
|
|
|
|
- const collaborators: InstanceType<typeof CollabWrapper>["collaborators"] =
|
|
|
|
- new Map();
|
|
|
|
- for (const socketId of sockets) {
|
|
|
|
- if (this.collaborators.has(socketId)) {
|
|
|
|
- collaborators.set(socketId, this.collaborators.get(socketId)!);
|
|
|
|
- } else {
|
|
|
|
- collaborators.set(socketId, {});
|
|
|
|
- }
|
|
|
|
|
|
+ const collaborators: InstanceType<typeof Collab>["collaborators"] =
|
|
|
|
+ new Map();
|
|
|
|
+ for (const socketId of sockets) {
|
|
|
|
+ if (this.collaborators.has(socketId)) {
|
|
|
|
+ collaborators.set(socketId, this.collaborators.get(socketId)!);
|
|
|
|
+ } else {
|
|
|
|
+ collaborators.set(socketId, {});
|
|
}
|
|
}
|
|
- this.collaborators = collaborators;
|
|
|
|
- this.excalidrawAPI.updateScene({ collaborators });
|
|
|
|
- });
|
|
|
|
|
|
+ }
|
|
|
|
+ this.collaborators = collaborators;
|
|
|
|
+ this.excalidrawAPI.updateScene({ collaborators });
|
|
}
|
|
}
|
|
|
|
|
|
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
|
|
public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
|
|
@@ -713,7 +742,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
);
|
|
);
|
|
|
|
|
|
onIdleStateChange = (userState: UserIdleState) => {
|
|
onIdleStateChange = (userState: UserIdleState) => {
|
|
- this.setState({ userState });
|
|
|
|
this.portal.broadcastIdleChange(userState);
|
|
this.portal.broadcastIdleChange(userState);
|
|
};
|
|
};
|
|
|
|
|
|
@@ -747,18 +775,22 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
this.setLastBroadcastedOrReceivedSceneVersion(newVersion);
|
|
this.setLastBroadcastedOrReceivedSceneVersion(newVersion);
|
|
}, SYNC_FULL_SCENE_INTERVAL_MS);
|
|
}, SYNC_FULL_SCENE_INTERVAL_MS);
|
|
|
|
|
|
- queueSaveToFirebase = throttle(() => {
|
|
|
|
- if (this.portal.socketInitialized) {
|
|
|
|
- this.saveCollabRoomToFirebase(
|
|
|
|
- getSyncableElements(
|
|
|
|
- this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }, SYNC_FULL_SCENE_INTERVAL_MS);
|
|
|
|
|
|
+ queueSaveToFirebase = throttle(
|
|
|
|
+ () => {
|
|
|
|
+ if (this.portal.socketInitialized) {
|
|
|
|
+ this.saveCollabRoomToFirebase(
|
|
|
|
+ getSyncableElements(
|
|
|
|
+ this.excalidrawAPI.getSceneElementsIncludingDeleted(),
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ SYNC_FULL_SCENE_INTERVAL_MS,
|
|
|
|
+ { leading: false },
|
|
|
|
+ );
|
|
|
|
|
|
handleClose = () => {
|
|
handleClose = () => {
|
|
- this.setState({ modalIsShown: false });
|
|
|
|
|
|
+ jotaiStore.set(collabDialogShownAtom, false);
|
|
};
|
|
};
|
|
|
|
|
|
setUsername = (username: string) => {
|
|
setUsername = (username: string) => {
|
|
@@ -770,35 +802,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
saveUsernameToLocalStorage(username);
|
|
saveUsernameToLocalStorage(username);
|
|
};
|
|
};
|
|
|
|
|
|
- onCollabButtonClick = () => {
|
|
|
|
- this.setState({
|
|
|
|
- modalIsShown: true,
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- /** PRIVATE. Use `this.getContextValue()` instead. */
|
|
|
|
- private contextValue: CollabAPI | null = null;
|
|
|
|
-
|
|
|
|
- /** Getter of context value. Returned object is stable. */
|
|
|
|
- getContextValue = (): CollabAPI => {
|
|
|
|
- if (!this.contextValue) {
|
|
|
|
- this.contextValue = {} as CollabAPI;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- this.contextValue.isCollaborating = this.isCollaborating;
|
|
|
|
- this.contextValue.username = this.state.username;
|
|
|
|
- this.contextValue.onPointerUpdate = this.onPointerUpdate;
|
|
|
|
- this.contextValue.initializeSocketClient = this.initializeSocketClient;
|
|
|
|
- this.contextValue.onCollabButtonClick = this.onCollabButtonClick;
|
|
|
|
- this.contextValue.syncElements = this.syncElements;
|
|
|
|
- this.contextValue.fetchImageFilesFromFirebase =
|
|
|
|
- this.fetchImageFilesFromFirebase;
|
|
|
|
- this.contextValue.setUsername = this.setUsername;
|
|
|
|
- return this.contextValue;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
render() {
|
|
render() {
|
|
- const { modalIsShown, username, errorMessage, activeRoomLink } = this.state;
|
|
|
|
|
|
+ const { username, errorMessage, activeRoomLink } = this.state;
|
|
|
|
+
|
|
|
|
+ const { modalIsShown } = this.props;
|
|
|
|
|
|
return (
|
|
return (
|
|
<>
|
|
<>
|
|
@@ -808,8 +815,8 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
activeRoomLink={activeRoomLink}
|
|
activeRoomLink={activeRoomLink}
|
|
username={username}
|
|
username={username}
|
|
onUsernameChange={this.onUsernameChange}
|
|
onUsernameChange={this.onUsernameChange}
|
|
- onRoomCreate={this.openPortal}
|
|
|
|
- onRoomDestroy={this.closePortal}
|
|
|
|
|
|
+ onRoomCreate={() => this.startCollaboration(null)}
|
|
|
|
+ onRoomDestroy={this.stopCollaboration}
|
|
setErrorMessage={(errorMessage) => {
|
|
setErrorMessage={(errorMessage) => {
|
|
this.setState({ errorMessage });
|
|
this.setState({ errorMessage });
|
|
}}
|
|
}}
|
|
@@ -822,11 +829,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
onClose={() => this.setState({ errorMessage: "" })}
|
|
onClose={() => this.setState({ errorMessage: "" })}
|
|
/>
|
|
/>
|
|
)}
|
|
)}
|
|
- <CollabContextProvider
|
|
|
|
- value={{
|
|
|
|
- api: this.getContextValue(),
|
|
|
|
- }}
|
|
|
|
- />
|
|
|
|
</>
|
|
</>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -834,7 +836,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
|
|
|
|
|
|
declare global {
|
|
declare global {
|
|
interface Window {
|
|
interface Window {
|
|
- collab: InstanceType<typeof CollabWrapper>;
|
|
|
|
|
|
+ collab: InstanceType<typeof Collab>;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -845,4 +847,11 @@ if (
|
|
window.collab = window.collab || ({} as Window["collab"]);
|
|
window.collab = window.collab || ({} as Window["collab"]);
|
|
}
|
|
}
|
|
|
|
|
|
-export default CollabWrapper;
|
|
|
|
|
|
+const _Collab: React.FC<PublicProps> = (props) => {
|
|
|
|
+ const [collabDialogShown] = useAtom(collabDialogShownAtom);
|
|
|
|
+ return <Collab {...props} modalIsShown={collabDialogShown} />;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+export default _Collab;
|
|
|
|
+
|
|
|
|
+export type TCollabClass = Collab;
|