diff --git a/src/appState.ts b/src/appState.ts index 7f0c19782..950ac49bc 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -100,6 +100,7 @@ export const getDefaultAppState = (): Omit< selectedLinearElement: null, userToFollow: null, shouldDisconnectFollowModeOnCanvasInteraction: true, + amIBeingFollowed: false, }; }; @@ -212,6 +213,7 @@ const APP_STATE_STORAGE_CONF = (< export: false, server: false, }, + amIBeingFollowed: { browser: false, export: false, server: false }, }); const _clearAppStateForStorage = < diff --git a/src/components/App.tsx b/src/components/App.tsx index b7adef66f..1d26ac966 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1543,14 +1543,14 @@ class App extends React.Component { if (prevState.userToFollow) { this.props?.onUserFollowed?.({ userToFollow: prevState.userToFollow, - action: "unsubscribe", + action: "unfollow", }); } if (this.state.userToFollow) { this.props?.onUserFollowed?.({ userToFollow: this.state.userToFollow, - action: "subscribe", + action: "follow", }); } } diff --git a/src/excalidraw-app/collab/Collab.tsx b/src/excalidraw-app/collab/Collab.tsx index 3e38a8c01..0fa5fb3ed 100644 --- a/src/excalidraw-app/collab/Collab.tsx +++ b/src/excalidraw-app/collab/Collab.tsx @@ -1,6 +1,10 @@ import throttle from "lodash.throttle"; import { PureComponent } from "react"; -import { AppState, ExcalidrawImperativeAPI, UserToFollow } from "../../types"; +import { + AppState, + ExcalidrawImperativeAPI, + OnUserFollowedPayload, +} from "../../types"; import { ErrorDialog } from "../../components/ErrorDialog"; import { APP_NAME, ENV, EVENT } from "../../constants"; import { ImportedDataState } from "../../data/types"; @@ -541,12 +545,8 @@ class Collab extends PureComponent { case "SCROLL_AND_ZOOM": { const { bounds } = decryptedData.payload; - const socketId: SocketUpdateDataSource["SCROLL_AND_ZOOM"]["payload"]["socketId"] = - decryptedData.payload.socketId; - const _appState = this.excalidrawAPI.getAppState(); - // TODO follow-participant - // if (_appState.userToFollow?.clientId === socketId) { + const { appState } = zoomToFitBounds({ appState: _appState, bounds, @@ -554,7 +554,6 @@ class Collab extends PureComponent { viewportZoomFactor: 1, }); this.excalidrawAPI.updateScene({ appState }); - // } break; } @@ -585,15 +584,16 @@ class Collab extends PureComponent { scenePromise.resolve(sceneData); }); - // TODO follow-participant - // set amIBeingFollowed flag this.portal.socket.on("broadcast-follow", () => { - console.log("broadcast-follow"); + this.excalidrawAPI.updateScene({ + appState: { amIBeingFollowed: true }, + }); }); - // TODO follow-participant - // set amIBeingFollowed flag + this.portal.socket.on("broadcast-unfollow", () => { - console.log("broadcast-unfollow"); + this.excalidrawAPI.updateScene({ + appState: { amIBeingFollowed: false }, + }); }); this.initializeIdleDetector(); @@ -803,48 +803,44 @@ class Collab extends PureComponent { CURSOR_SYNC_TIMEOUT, ); - // TODO follow-participant - // only calculate this + broadcast if some flag on appState is set - // (eg amIBeingFollowed) onScrollAndZoomChange = throttle( (payload: { zoom: AppState["zoom"]; scroll: { x: number; y: number } }) => { const appState = this.excalidrawAPI.getAppState(); - const { x: x1, y: y1 } = viewportCoordsToSceneCoords( - { clientX: 0, clientY: 0 }, - { - offsetLeft: appState.offsetLeft, - offsetTop: appState.offsetTop, - scrollX: payload.scroll.x, - scrollY: payload.scroll.y, - zoom: payload.zoom, - }, - ); - - const { x: x2, y: y2 } = viewportCoordsToSceneCoords( - { clientX: appState.width, clientY: appState.height }, - { - offsetLeft: appState.offsetLeft, - offsetTop: appState.offsetTop, - scrollX: payload.scroll.x, - scrollY: payload.scroll.y, - zoom: payload.zoom, - }, - ); - - this.portal.socket && - this.portal.broadcastScrollAndZoom( - { bounds: [x1, y1, x2, y2] }, - // TODO follow-participant - `follow_${this.portal.socket.id}`, + if (appState.amIBeingFollowed) { + const { x: x1, y: y1 } = viewportCoordsToSceneCoords( + { clientX: 0, clientY: 0 }, + { + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + scrollX: payload.scroll.x, + scrollY: payload.scroll.y, + zoom: payload.zoom, + }, ); + + const { x: x2, y: y2 } = viewportCoordsToSceneCoords( + { clientX: appState.width, clientY: appState.height }, + { + offsetLeft: appState.offsetLeft, + offsetTop: appState.offsetTop, + scrollX: payload.scroll.x, + scrollY: payload.scroll.y, + zoom: payload.zoom, + }, + ); + + this.portal.socket && + this.portal.broadcastScrollAndZoom( + { bounds: [x1, y1, x2, y2] }, + // TODO follow-participant + `follow_${this.portal.socket.id}`, + ); + } }, ); - onUserFollowed = (payload: { - userToFollow: UserToFollow; - action: "subscribe" | "unsubscribe"; - }) => { + onUserFollowed = (payload: OnUserFollowedPayload) => { this.portal.socket && this.portal.broadcastUserFollowed(payload); }; diff --git a/src/excalidraw-app/collab/Portal.tsx b/src/excalidraw-app/collab/Portal.tsx index e1c0d399c..0fab855be 100644 --- a/src/excalidraw-app/collab/Portal.tsx +++ b/src/excalidraw-app/collab/Portal.tsx @@ -12,7 +12,7 @@ import { FILE_UPLOAD_TIMEOUT, WS_SCENE_EVENT_TYPES, } from "../app_constants"; -import { UserIdleState, UserToFollow } from "../../types"; +import { OnUserFollowedPayload, UserIdleState } from "../../types"; import { trackEvent } from "../../analytics"; import throttle from "lodash.throttle"; import { newElementWith } from "../../element/mutateElement"; @@ -248,10 +248,7 @@ class Portal { } }; - broadcastUserFollowed = (payload: { - userToFollow: UserToFollow; - action: "subscribe" | "unsubscribe"; - }) => { + broadcastUserFollowed = (payload: OnUserFollowedPayload) => { if (this.socket?.id) { this.socket?.emit("on-user-follow", payload); } diff --git a/src/types.ts b/src/types.ts index 4d251acaa..edc90c125 100644 --- a/src/types.ts +++ b/src/types.ts @@ -229,6 +229,8 @@ export type AppState = { userToFollow: UserToFollow | null; /** whether follow mode should be disconnected when the non-remote user interacts with the canvas */ shouldDisconnectFollowModeOnCanvasInteraction: boolean; + /** whether the user is being followed on the canvas */ + amIBeingFollowed: boolean; }; export type UIAppState = Omit< @@ -313,6 +315,11 @@ export type ExcalidrawInitialDataState = Merge< } >; +export type OnUserFollowedPayload = { + userToFollow: UserToFollow; + action: "follow" | "unfollow"; +}; + export interface ExcalidrawProps { onChange?: ( elements: readonly ExcalidrawElement[], @@ -373,12 +380,7 @@ export interface ExcalidrawProps { zoom: Zoom; scroll: { x: number; y: number }; }) => void; - // TODO follow-participant - onUserFollowed?: (payload: { - userToFollow: UserToFollow; - // TODO follow-participant extract to own type - action: "subscribe" | "unsubscribe"; - }) => void; + onUserFollowed?: (payload: OnUserFollowedPayload) => void; children?: React.ReactNode; }