diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx index 6ef4d1468..8e144cd2b 100644 --- a/src/excalidraw-app/collab/Collab.tsx +++ b/src/excalidraw-app/collab/Collab.tsx @@ -16,6 +16,7 @@ import { Collaborator, Gesture } from "../../types"; import { preventUnload, resolvablePromise, + upsertMap, withBatchedUpdates, } from "../../utils"; import { @@ -74,6 +75,7 @@ import { resetBrowserStateVersions } from "../data/tabSync"; import { LocalData } from "../data/LocalData"; import { atom, useAtom } from "jotai"; import { appJotaiStore } from "../app-jotai"; +import { nanoid } from "nanoid"; export const collabAPIAtom = atom(null); export const collabDialogShownAtom = atom(false); @@ -85,6 +87,7 @@ interface CollabState { errorMessage: string; username: string; activeRoomLink: string; + userId: string; } type CollabInstance = InstanceType; @@ -125,6 +128,7 @@ class Collab extends PureComponent { errorMessage: "", username: importUsernameFromLocalStorage() || "", activeRoomLink: "", + userId: nanoid(), }; this.portal = new Portal(this); this.fileManager = new FileManager({ @@ -644,33 +648,36 @@ class Collab extends PureComponent { ); break; case "MOUSE_LOCATION": { - const { pointer, button, username, selectedElementIds } = + const { pointer, button, username, selectedElementIds, userId } = decryptedData.payload; - const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] = - decryptedData.payload.socketId || - // @ts-ignore legacy, see #2094 (#2097) - decryptedData.payload.socketID; - - const collaborators = new Map(this.collaborators); - const user = collaborators.get(socketId) || {}!; - user.pointer = pointer; - user.button = button; - user.selectedElementIds = selectedElementIds; - user.username = username; - collaborators.set(socketId, user); + const collaborators = upsertMap( + userId, + { + username, + pointer, + button, + selectedElementIds, + }, + this.collaborators, + ); this.excalidrawAPI.updateScene({ - collaborators, + collaborators: new Map(collaborators), }); break; } case "IDLE_STATUS": { - const { userState, socketId, username } = decryptedData.payload; - const collaborators = new Map(this.collaborators); - const user = collaborators.get(socketId) || {}!; - user.userState = userState; - user.username = username; + const { userState, username, userId } = decryptedData.payload; + const collaborators = upsertMap( + userId, + { + username, + userState, + userId, + }, + this.collaborators, + ); this.excalidrawAPI.updateScene({ - collaborators, + collaborators: new Map(collaborators), }); break; } diff --git a/src/excalidraw-app/collab/Portal.tsx b/src/excalidraw-app/collab/Portal.tsx index 00d170208..15ee62730 100644 --- a/src/excalidraw-app/collab/Portal.tsx +++ b/src/excalidraw-app/collab/Portal.tsx @@ -74,9 +74,6 @@ class Portal { /* syncAll */ true, ); }); - this.socket.on("room-user-change", (clients: string[]) => { - this.collab.setCollaborators(clients); - }); } isOpen() { @@ -189,13 +186,13 @@ class Portal { }; broadcastIdleChange = (userState: UserIdleState) => { - if (this.socket?.id) { + if (this.socket) { const data: SocketUpdateDataSource["IDLE_STATUS"] = { type: "IDLE_STATUS", payload: { - socketId: this.socket.id, userState, username: this.collab.state.username, + userId: this.collab.state.userId, }, }; return this._broadcastSocketData( @@ -209,16 +206,16 @@ class Portal { pointer: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["pointer"]; button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"]; }) => { - if (this.socket?.id) { + if (this.socket) { const data: SocketUpdateDataSource["MOUSE_LOCATION"] = { type: "MOUSE_LOCATION", payload: { - socketId: this.socket.id, pointer: payload.pointer, button: payload.button || "up", selectedElementIds: this.collab.excalidrawAPI.getAppState().selectedElementIds, username: this.collab.state.username, + userId: this.collab.state.userId, }, }; return this._broadcastSocketData( diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts index c85eb27c0..f7882827e 100644 --- a/src/excalidraw-app/data/index.ts +++ b/src/excalidraw-app/data/index.ts @@ -106,19 +106,19 @@ export type SocketUpdateDataSource = { MOUSE_LOCATION: { type: "MOUSE_LOCATION"; payload: { - socketId: string; pointer: { x: number; y: number }; button: "down" | "up"; selectedElementIds: AppState["selectedElementIds"]; username: string; + userId: string; }; }; IDLE_STATUS: { type: "IDLE_STATUS"; payload: { - socketId: string; userState: UserIdleState; username: string; + userId: string; }; }; }; diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 1344329e5..1e2f3bdd0 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -619,8 +619,8 @@ export const _renderScene = ({ if (renderConfig.remoteSelectedElementIds[element.id]) { selectionColors.push( ...renderConfig.remoteSelectedElementIds[element.id].map( - (socketId) => { - const background = getClientColor(socketId); + (userId) => { + const background = getClientColor(userId); return background; }, ), diff --git a/src/utils.ts b/src/utils.ts index c644efd81..e4c2f28a4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,6 +20,7 @@ import { unstable_batchedUpdates } from "react-dom"; import { SHAPES } from "./shapes"; import { isEraserActive, isHandToolActive } from "./appState"; import { ResolutionType } from "./utility-types"; +import { reconcileElements } from "./excalidraw-app/collab/reconciliation"; let mockDateTime: string | null = null; @@ -907,3 +908,14 @@ export const isOnlyExportingSingleFrame = ( ) ); }; + +export const upsertMap = (key: T, value: object, map: Map) => { + if (!map.has(key)) { + map.set(key, value); + } else { + const old = map.get(key); + map.set(key, { ...old, ...value }); + } + + return map; +};