follow scroll location POC

This commit is contained in:
barnabasmolnar 2023-07-21 00:34:29 +02:00
parent a80ac4c748
commit 9152ce24f2
8 changed files with 108 additions and 2 deletions

View File

@ -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 (
<Avatar
color={background}
onClick={() => updateData(collaborator.pointer)}
onClick={() => updateData({ ...collaborator, clientId })}
name={collaborator.username || ""}
src={collaborator.avatarUrl}
/>

View File

@ -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 = <

View File

@ -1500,6 +1500,8 @@ class App extends React.Component<AppProps, AppState> {
this.refreshDeviceState(this.excalidrawContainerRef.current);
}
// TODO follow-participant
// add zoom change
if (
prevState.scrollX !== this.state.scrollX ||
prevState.scrollY !== this.state.scrollY

View File

@ -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<Props, CollabState> {
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<Props, CollabState> {
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<Props, CollabState> {
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<Props, CollabState> {
});
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<Props, CollabState> {
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<Props, CollabState> {
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);
};

View File

@ -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

View File

@ -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: {

View File

@ -649,6 +649,13 @@ const ExcalidrawWrapper = () => {
})}
>
<Excalidraw
// TODO follow-participant
// add onZoomChange
onScrollChange={(x, y) => {
// console.log({ x, y });
collabAPI?.onScrollChange({ scrollX: x, scrollY: y });
}}
ref={excalidrawRefCallback}
onChange={onChange}
initialData={initialStatePromiseRef.current.promise}

View File

@ -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<