diff --git a/src/actions/actionNavigate.tsx b/src/actions/actionNavigate.tsx index 126e547ae..09ffa0cbf 100644 --- a/src/actions/actionNavigate.tsx +++ b/src/actions/actionNavigate.tsx @@ -9,7 +9,9 @@ export const actionGoToCollaborator = register({ viewMode: true, trackEvent: { category: "collab" }, perform: (_elements, appState, value) => { - const point = value as Collaborator["pointer"]; + const _value = value as Collaborator & { clientId: string }; + const point = _value.pointer; + if (!point) { return { appState, commitToHistory: false }; } @@ -17,6 +19,8 @@ export const actionGoToCollaborator = register({ return { appState: { ...appState, + // ˇˇ or maybe an atom? 🤔 + userToFollow: _value.clientId, ...centerScrollOn({ scenePoint: point, viewportDimensions: { @@ -39,7 +43,7 @@ export const actionGoToCollaborator = register({ return ( updateData(collaborator.pointer)} + onClick={() => updateData({ ...collaborator, clientId })} name={collaborator.username || ""} src={collaborator.avatarUrl} /> diff --git a/src/appState.ts b/src/appState.ts index 104fbcbf5..8be06c8b9 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -98,6 +98,7 @@ export const getDefaultAppState = (): Omit< pendingImageElementId: null, showHyperlinkPopup: false, selectedLinearElement: null, + userToFollow: null, }; }; @@ -204,6 +205,7 @@ const APP_STATE_STORAGE_CONF = (< pendingImageElementId: { browser: false, export: false, server: false }, showHyperlinkPopup: { browser: false, export: false, server: false }, selectedLinearElement: { browser: true, export: false, server: false }, + userToFollow: { browser: false, export: false, server: false }, }); const _clearAppStateForStorage = < diff --git a/src/components/App.tsx b/src/components/App.tsx index 7f2c98e50..46e8017ae 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1500,6 +1500,8 @@ class App extends React.Component { this.refreshDeviceState(this.excalidrawContainerRef.current); } + // TODO follow-participant + // add zoom change if ( prevState.scrollX !== this.state.scrollX || prevState.scrollY !== this.state.scrollY diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx index ec0c2a348..2a5b5552e 100644 --- a/src/excalidraw-app/collab/Collab.tsx +++ b/src/excalidraw-app/collab/Collab.tsx @@ -89,6 +89,7 @@ export interface CollabAPI { /** function so that we can access the latest value from stale callbacks */ isCollaborating: () => boolean; onPointerUpdate: CollabInstance["onPointerUpdate"]; + onScrollChange: CollabInstance["onScrollChange"]; startCollaboration: CollabInstance["startCollaboration"]; stopCollaboration: CollabInstance["stopCollaboration"]; syncElements: CollabInstance["syncElements"]; @@ -162,6 +163,7 @@ class Collab extends PureComponent { const collabAPI: CollabAPI = { isCollaborating: this.isCollaborating, onPointerUpdate: this.onPointerUpdate, + onScrollChange: this.onScrollChange, startCollaboration: this.startCollaboration, syncElements: this.syncElements, fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase, @@ -506,6 +508,8 @@ class Collab extends PureComponent { break; } case WS_SCENE_EVENT_TYPES.UPDATE: + console.log("received update", decryptedData); + console.log(this.excalidrawAPI.getAppState()); this.handleRemoteSceneUpdate( this.reconcileElements(decryptedData.payload.elements), ); @@ -513,6 +517,9 @@ class Collab extends PureComponent { case "MOUSE_LOCATION": { const { pointer, button, username, selectedElementIds } = decryptedData.payload; + + // console.log({ decryptedData }); + const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] = decryptedData.payload.socketId || // @ts-ignore legacy, see #2094 (#2097) @@ -530,6 +537,35 @@ class Collab extends PureComponent { }); break; } + // TODO follow-participant + // case "SCROLL_LOCATION" + // case "ZOOM_VALUE" + // if following someone, update scroll and zoom + + case "SCROLL_LOCATION": + const { + scroll: { x, y }, + } = decryptedData.payload; + + const socketId: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["socketId"] = + decryptedData.payload.socketId; + + console.log({ decryptedData }); + + const appState = this.excalidrawAPI.getAppState(); + console.log({ appState }); + + if (appState.userToFollow === socketId) { + this.excalidrawAPI.updateScene({ + appState: { + scrollX: x, + scrollY: y, + }, + }); + } + + break; + case "IDLE_STATUS": { const { userState, socketId, username } = decryptedData.payload; const collaborators = new Map(this.collaborators); @@ -756,6 +792,7 @@ class Collab extends PureComponent { button: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["button"]; pointersMap: Gesture["pointers"]; }) => { + // console.log({ payload }); payload.pointersMap.size < 2 && this.portal.socket && this.portal.broadcastMouseLocation(payload); @@ -763,6 +800,21 @@ class Collab extends PureComponent { CURSOR_SYNC_TIMEOUT, ); + // TODO follow-participant + // - onScrollChange + // -- broadCastScrollLocation + // - onZoomChange + // -- broadCastZoomValue + + onScrollChange = throttle( + (payload: { + scrollX: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["x"]; + scrollY: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["y"]; + }) => { + this.portal.socket && this.portal.broadcastScrollLocation(payload); + }, + ); + onIdleStateChange = (userState: UserIdleState) => { this.portal.broadcastIdleChange(userState); }; diff --git a/src/excalidraw-app/collab/Portal.tsx b/src/excalidraw-app/collab/Portal.tsx index 1d4db3c0c..209f84358 100644 --- a/src/excalidraw-app/collab/Portal.tsx +++ b/src/excalidraw-app/collab/Portal.tsx @@ -213,6 +213,36 @@ class Portal { username: this.collab.state.username, }, }; + + // console.log("broadcastMouseLocation data", data); + + return this._broadcastSocketData( + data as SocketUpdateData, + true, // volatile + ); + } + }; + + // TODO follow-participant + // - broadCastScrollLocation + // - broadCastZoomValue + + broadcastScrollLocation = (payload: { + scrollX: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["x"]; + scrollY: SocketUpdateDataSource["SCROLL_LOCATION"]["payload"]["scroll"]["y"]; + }) => { + if (this.socket?.id) { + const data: SocketUpdateDataSource["SCROLL_LOCATION"] = { + type: "SCROLL_LOCATION", + payload: { + socketId: this.socket.id, + scroll: { x: payload.scrollX, y: payload.scrollY }, + username: this.collab.state.username, + }, + }; + + console.log("broadcastScrollLocation data", data); + return this._broadcastSocketData( data as SocketUpdateData, true, // volatile diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts index c85eb27c0..c8732ff25 100644 --- a/src/excalidraw-app/data/index.ts +++ b/src/excalidraw-app/data/index.ts @@ -113,6 +113,14 @@ export type SocketUpdateDataSource = { username: string; }; }; + SCROLL_LOCATION: { + type: "SCROLL_LOCATION"; + payload: { + socketId: string; + scroll: { x: number; y: number }; + username: string; + }; + }; IDLE_STATUS: { type: "IDLE_STATUS"; payload: { diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index 860437f3c..e48692c2b 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -649,6 +649,13 @@ const ExcalidrawWrapper = () => { })} > { + // console.log({ x, y }); + + collabAPI?.onScrollChange({ scrollX: x, scrollY: y }); + }} ref={excalidrawRefCallback} onChange={onChange} initialData={initialStatePromiseRef.current.promise} diff --git a/src/types.ts b/src/types.ts index 40f54831d..3932f8df6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -223,6 +223,7 @@ export type AppState = { pendingImageElementId: ExcalidrawImageElement["id"] | null; showHyperlinkPopup: false | "info" | "editor"; selectedLinearElement: LinearElementEditor | null; + userToFollow: string | null; }; export type UIAppState = Omit<