fix: mixing clientId & socketId in UserList (#7461)
This commit is contained in:
parent
0808532b49
commit
57ea4e61d1
@ -123,7 +123,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
|
|
||||||
private socketInitializationTimer?: number;
|
private socketInitializationTimer?: number;
|
||||||
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
private lastBroadcastedOrReceivedSceneVersion: number = -1;
|
||||||
private collaborators = new Map<string, Collaborator>();
|
private collaborators = new Map<SocketId, Collaborator>();
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -618,7 +618,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
|
|
||||||
this.portal.socket.on(
|
this.portal.socket.on(
|
||||||
WS_EVENTS.USER_FOLLOW_ROOM_CHANGE,
|
WS_EVENTS.USER_FOLLOW_ROOM_CHANGE,
|
||||||
(followedBy: string[]) => {
|
(followedBy: SocketId[]) => {
|
||||||
this.excalidrawAPI.updateScene({
|
this.excalidrawAPI.updateScene({
|
||||||
appState: { followedBy: new Set(followedBy) },
|
appState: { followedBy: new Set(followedBy) },
|
||||||
});
|
});
|
||||||
@ -795,7 +795,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
setCollaborators(sockets: string[]) {
|
setCollaborators(sockets: SocketId[]) {
|
||||||
const collaborators: InstanceType<typeof Collab>["collaborators"] =
|
const collaborators: InstanceType<typeof Collab>["collaborators"] =
|
||||||
new Map();
|
new Map();
|
||||||
for (const socketId of sockets) {
|
for (const socketId of sockets) {
|
||||||
|
@ -10,6 +10,7 @@ import { ExcalidrawElement } from "../../packages/excalidraw/element/types";
|
|||||||
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
|
||||||
import {
|
import {
|
||||||
OnUserFollowedPayload,
|
OnUserFollowedPayload,
|
||||||
|
SocketId,
|
||||||
UserIdleState,
|
UserIdleState,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "../../packages/excalidraw/types";
|
||||||
import { trackEvent } from "../../packages/excalidraw/analytics";
|
import { trackEvent } from "../../packages/excalidraw/analytics";
|
||||||
@ -51,7 +52,7 @@ class Portal {
|
|||||||
/* syncAll */ true,
|
/* syncAll */ true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.socket.on("room-user-change", (clients: string[]) => {
|
this.socket.on("room-user-change", (clients: SocketId[]) => {
|
||||||
this.collab.setCollaborators(clients);
|
this.collab.setCollaborators(clients);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -186,7 +187,7 @@ class Portal {
|
|||||||
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
const data: SocketUpdateDataSource["IDLE_STATUS"] = {
|
||||||
type: WS_SUBTYPES.IDLE_STATUS,
|
type: WS_SUBTYPES.IDLE_STATUS,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id as SocketId,
|
||||||
userState,
|
userState,
|
||||||
username: this.collab.state.username,
|
username: this.collab.state.username,
|
||||||
},
|
},
|
||||||
@ -206,7 +207,7 @@ class Portal {
|
|||||||
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
|
||||||
type: WS_SUBTYPES.MOUSE_LOCATION,
|
type: WS_SUBTYPES.MOUSE_LOCATION,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id as SocketId,
|
||||||
pointer: payload.pointer,
|
pointer: payload.pointer,
|
||||||
button: payload.button || "up",
|
button: payload.button || "up",
|
||||||
selectedElementIds:
|
selectedElementIds:
|
||||||
@ -232,7 +233,7 @@ class Portal {
|
|||||||
const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
|
const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
|
||||||
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
|
||||||
payload: {
|
payload: {
|
||||||
socketId: this.socket.id,
|
socketId: this.socket.id as SocketId,
|
||||||
username: this.collab.state.username,
|
username: this.collab.state.username,
|
||||||
sceneBounds: payload.sceneBounds,
|
sceneBounds: payload.sceneBounds,
|
||||||
},
|
},
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
AppState,
|
AppState,
|
||||||
BinaryFileData,
|
BinaryFileData,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
|
SocketId,
|
||||||
UserIdleState,
|
UserIdleState,
|
||||||
} from "../../packages/excalidraw/types";
|
} from "../../packages/excalidraw/types";
|
||||||
import { bytesToHexString } from "../../packages/excalidraw/utils";
|
import { bytesToHexString } from "../../packages/excalidraw/utils";
|
||||||
@ -117,7 +118,7 @@ export type SocketUpdateDataSource = {
|
|||||||
MOUSE_LOCATION: {
|
MOUSE_LOCATION: {
|
||||||
type: WS_SUBTYPES.MOUSE_LOCATION;
|
type: WS_SUBTYPES.MOUSE_LOCATION;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
pointer: { x: number; y: number; tool: "pointer" | "laser" };
|
pointer: { x: number; y: number; tool: "pointer" | "laser" };
|
||||||
button: "down" | "up";
|
button: "down" | "up";
|
||||||
selectedElementIds: AppState["selectedElementIds"];
|
selectedElementIds: AppState["selectedElementIds"];
|
||||||
@ -127,7 +128,7 @@ export type SocketUpdateDataSource = {
|
|||||||
USER_VISIBLE_SCENE_BOUNDS: {
|
USER_VISIBLE_SCENE_BOUNDS: {
|
||||||
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS;
|
type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
username: string;
|
username: string;
|
||||||
sceneBounds: SceneBounds;
|
sceneBounds: SceneBounds;
|
||||||
};
|
};
|
||||||
@ -135,7 +136,7 @@ export type SocketUpdateDataSource = {
|
|||||||
IDLE_STATUS: {
|
IDLE_STATUS: {
|
||||||
type: WS_SUBTYPES.IDLE_STATUS;
|
type: WS_SUBTYPES.IDLE_STATUS;
|
||||||
payload: {
|
payload: {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
userState: UserIdleState;
|
userState: UserIdleState;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,7 @@ export const actionGoToCollaborator = register({
|
|||||||
trackEvent: { category: "collab" },
|
trackEvent: { category: "collab" },
|
||||||
perform: (_elements, appState, collaborator: Collaborator) => {
|
perform: (_elements, appState, collaborator: Collaborator) => {
|
||||||
if (
|
if (
|
||||||
|
!collaborator.socketId ||
|
||||||
appState.userToFollow?.socketId === collaborator.socketId ||
|
appState.userToFollow?.socketId === collaborator.socketId ||
|
||||||
collaborator.isCurrentUser
|
collaborator.isCurrentUser
|
||||||
) {
|
) {
|
||||||
@ -28,7 +29,7 @@ export const actionGoToCollaborator = register({
|
|||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
userToFollow: {
|
userToFollow: {
|
||||||
socketId: collaborator.socketId!,
|
socketId: collaborator.socketId,
|
||||||
username: collaborator.username || "",
|
username: collaborator.username || "",
|
||||||
},
|
},
|
||||||
// Close mobile menu
|
// Close mobile menu
|
||||||
|
@ -3472,7 +3472,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private maybeUnfollowRemoteUser = () => {
|
private maybeUnfollowRemoteUser = () => {
|
||||||
console.warn("maybeUnfollowRemoteUser");
|
|
||||||
if (this.state.userToFollow) {
|
if (this.state.userToFollow) {
|
||||||
this.setState({ userToFollow: null });
|
this.setState({ userToFollow: null });
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { LaserPointer } from "@excalidraw/laser-pointer";
|
|||||||
import { sceneCoordsToViewportCoords } from "../../utils";
|
import { sceneCoordsToViewportCoords } from "../../utils";
|
||||||
import App from "../App";
|
import App from "../App";
|
||||||
import { getClientColor } from "../../clients";
|
import { getClientColor } from "../../clients";
|
||||||
|
import { SocketId } from "../../types";
|
||||||
|
|
||||||
// decay time in milliseconds
|
// decay time in milliseconds
|
||||||
const DECAY_TIME = 1000;
|
const DECAY_TIME = 1000;
|
||||||
@ -88,7 +89,7 @@ type CollabolatorState = {
|
|||||||
|
|
||||||
export class LaserPathManager {
|
export class LaserPathManager {
|
||||||
private ownState: CollabolatorState;
|
private ownState: CollabolatorState;
|
||||||
private collaboratorsState: Map<string, CollabolatorState> = new Map();
|
private collaboratorsState: Map<SocketId, CollabolatorState> = new Map();
|
||||||
|
|
||||||
private rafId: number | undefined;
|
private rafId: number | undefined;
|
||||||
private isDrawing = false;
|
private isDrawing = false;
|
||||||
|
@ -14,51 +14,54 @@ import { t } from "../i18n";
|
|||||||
import { isShallowEqual } from "../utils";
|
import { isShallowEqual } from "../utils";
|
||||||
|
|
||||||
export type GoToCollaboratorComponentProps = [
|
export type GoToCollaboratorComponentProps = [
|
||||||
SocketId,
|
ClientId,
|
||||||
Collaborator,
|
Collaborator,
|
||||||
boolean,
|
boolean,
|
||||||
boolean,
|
boolean,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** collaborator user id or socket id (fallback) */
|
||||||
|
type ClientId = string & { _brand: "UserId" };
|
||||||
|
|
||||||
const FIRST_N_AVATARS = 3;
|
const FIRST_N_AVATARS = 3;
|
||||||
const SHOW_COLLABORATORS_FILTER_AT = 8;
|
const SHOW_COLLABORATORS_FILTER_AT = 8;
|
||||||
|
|
||||||
const ConditionalTooltipWrapper = ({
|
const ConditionalTooltipWrapper = ({
|
||||||
shouldWrap,
|
shouldWrap,
|
||||||
children,
|
children,
|
||||||
socketId,
|
clientId,
|
||||||
username,
|
username,
|
||||||
}: {
|
}: {
|
||||||
shouldWrap: boolean;
|
shouldWrap: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
username?: string | null;
|
username?: string | null;
|
||||||
socketId: string;
|
clientId: ClientId;
|
||||||
}) =>
|
}) =>
|
||||||
shouldWrap ? (
|
shouldWrap ? (
|
||||||
<Tooltip label={username || "Unknown user"} key={socketId}>
|
<Tooltip label={username || "Unknown user"} key={clientId}>
|
||||||
{children}
|
{children}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment key={socketId}>{children}</React.Fragment>
|
<React.Fragment key={clientId}>{children}</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCollaborator = ({
|
const renderCollaborator = ({
|
||||||
actionManager,
|
actionManager,
|
||||||
collaborator,
|
collaborator,
|
||||||
socketId,
|
clientId,
|
||||||
withName = false,
|
withName = false,
|
||||||
shouldWrapWithTooltip = false,
|
shouldWrapWithTooltip = false,
|
||||||
isBeingFollowed,
|
isBeingFollowed,
|
||||||
}: {
|
}: {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
collaborator: Collaborator;
|
collaborator: Collaborator;
|
||||||
socketId: string;
|
clientId: ClientId;
|
||||||
withName?: boolean;
|
withName?: boolean;
|
||||||
shouldWrapWithTooltip?: boolean;
|
shouldWrapWithTooltip?: boolean;
|
||||||
isBeingFollowed: boolean;
|
isBeingFollowed: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const data: GoToCollaboratorComponentProps = [
|
const data: GoToCollaboratorComponentProps = [
|
||||||
socketId,
|
clientId,
|
||||||
collaborator,
|
collaborator,
|
||||||
withName,
|
withName,
|
||||||
isBeingFollowed,
|
isBeingFollowed,
|
||||||
@ -67,8 +70,8 @@ const renderCollaborator = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionalTooltipWrapper
|
<ConditionalTooltipWrapper
|
||||||
key={socketId}
|
key={clientId}
|
||||||
socketId={socketId}
|
clientId={clientId}
|
||||||
username={collaborator.username}
|
username={collaborator.username}
|
||||||
shouldWrap={shouldWrapWithTooltip}
|
shouldWrap={shouldWrapWithTooltip}
|
||||||
>
|
>
|
||||||
@ -100,12 +103,13 @@ export const UserList = React.memo(
|
|||||||
({ className, mobile, collaborators, userToFollow }: UserListProps) => {
|
({ className, mobile, collaborators, userToFollow }: UserListProps) => {
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
const uniqueCollaboratorsMap = new Map<string, Collaborator>();
|
const uniqueCollaboratorsMap = new Map<ClientId, Collaborator>();
|
||||||
|
|
||||||
collaborators.forEach((collaborator, socketId) => {
|
collaborators.forEach((collaborator, socketId) => {
|
||||||
|
const userId = (collaborator.id || socketId) as ClientId;
|
||||||
uniqueCollaboratorsMap.set(
|
uniqueCollaboratorsMap.set(
|
||||||
// filter on user id, else fall back on unique socketId
|
// filter on user id, else fall back on unique socketId
|
||||||
collaborator.id || socketId,
|
userId,
|
||||||
{ ...collaborator, socketId },
|
{ ...collaborator, socketId },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -134,25 +138,25 @@ export const UserList = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const firstNAvatarsJSX = firstNCollaborators.map(
|
const firstNAvatarsJSX = firstNCollaborators.map(
|
||||||
([socketId, collaborator]) =>
|
([clientId, collaborator]) =>
|
||||||
renderCollaborator({
|
renderCollaborator({
|
||||||
actionManager,
|
actionManager,
|
||||||
collaborator,
|
collaborator,
|
||||||
socketId,
|
clientId,
|
||||||
shouldWrapWithTooltip: true,
|
shouldWrapWithTooltip: true,
|
||||||
isBeingFollowed: socketId === userToFollow,
|
isBeingFollowed: collaborator.socketId === userToFollow,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return mobile ? (
|
return mobile ? (
|
||||||
<div className={clsx("UserList UserList_mobile", className)}>
|
<div className={clsx("UserList UserList_mobile", className)}>
|
||||||
{uniqueCollaboratorsArray.map(([socketId, collaborator]) =>
|
{uniqueCollaboratorsArray.map(([clientId, collaborator]) =>
|
||||||
renderCollaborator({
|
renderCollaborator({
|
||||||
actionManager,
|
actionManager,
|
||||||
collaborator,
|
collaborator,
|
||||||
socketId,
|
clientId,
|
||||||
shouldWrapWithTooltip: true,
|
shouldWrapWithTooltip: true,
|
||||||
isBeingFollowed: socketId === userToFollow,
|
isBeingFollowed: collaborator.socketId === userToFollow,
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -205,13 +209,13 @@ export const UserList = React.memo(
|
|||||||
<div className="UserList__hint">
|
<div className="UserList__hint">
|
||||||
{t("userList.hint.text")}
|
{t("userList.hint.text")}
|
||||||
</div>
|
</div>
|
||||||
{filteredCollaborators.map(([socketId, collaborator]) =>
|
{filteredCollaborators.map(([clientId, collaborator]) =>
|
||||||
renderCollaborator({
|
renderCollaborator({
|
||||||
actionManager,
|
actionManager,
|
||||||
collaborator,
|
collaborator,
|
||||||
socketId,
|
clientId,
|
||||||
withName: true,
|
withName: true,
|
||||||
isBeingFollowed: socketId === userToFollow,
|
isBeingFollowed: collaborator.socketId === userToFollow,
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@ import { Merge, ValueOf } from "./utility-types";
|
|||||||
|
|
||||||
export type Point = Readonly<RoughPoint>;
|
export type Point = Readonly<RoughPoint>;
|
||||||
|
|
||||||
export type SocketId = string;
|
export type SocketId = string & { _brand: "SocketId" };
|
||||||
|
|
||||||
export type Collaborator = Readonly<{
|
export type Collaborator = Readonly<{
|
||||||
pointer?: CollaboratorPointer;
|
pointer?: CollaboratorPointer;
|
||||||
@ -128,7 +128,7 @@ export type SidebarName = string;
|
|||||||
export type SidebarTabName = string;
|
export type SidebarTabName = string;
|
||||||
|
|
||||||
export type UserToFollow = {
|
export type UserToFollow = {
|
||||||
socketId: string;
|
socketId: SocketId;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ export interface AppState {
|
|||||||
offsetLeft: number;
|
offsetLeft: number;
|
||||||
|
|
||||||
fileHandle: FileSystemHandle | null;
|
fileHandle: FileSystemHandle | null;
|
||||||
collaborators: Map<string, Collaborator>;
|
collaborators: Map<SocketId, Collaborator>;
|
||||||
showStats: boolean;
|
showStats: boolean;
|
||||||
currentChartType: ChartType;
|
currentChartType: ChartType;
|
||||||
pasteDialog:
|
pasteDialog:
|
||||||
@ -321,7 +321,7 @@ export interface AppState {
|
|||||||
/** the user's clientId & username who is being followed on the canvas */
|
/** the user's clientId & username who is being followed on the canvas */
|
||||||
userToFollow: UserToFollow | null;
|
userToFollow: UserToFollow | null;
|
||||||
/** the clientIds of the users following the current user */
|
/** the clientIds of the users following the current user */
|
||||||
followedBy: Set<string>;
|
followedBy: Set<SocketId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UIAppState = Omit<
|
export type UIAppState = Omit<
|
||||||
@ -474,7 +474,7 @@ export interface ExcalidrawProps {
|
|||||||
export type SceneData = {
|
export type SceneData = {
|
||||||
elements?: ImportedDataState["elements"];
|
elements?: ImportedDataState["elements"];
|
||||||
appState?: ImportedDataState["appState"];
|
appState?: ImportedDataState["appState"];
|
||||||
collaborators?: Map<string, Collaborator>;
|
collaborators?: Map<SocketId, Collaborator>;
|
||||||
commitToHistory?: boolean;
|
commitToHistory?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user