feat: deduplicate collab avatars based on id
(#5309)
This commit is contained in:
parent
ec35d5db51
commit
5feacd9a3b
@ -31,16 +31,7 @@ export const actionGoToCollaborator = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, data }) => {
|
PanelComponent: ({ appState, updateData, data }) => {
|
||||||
const clientId: string | undefined = data?.id;
|
const [clientId, collaborator] = data as [string, Collaborator];
|
||||||
if (!clientId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const collaborator = appState.collaborators.get(clientId);
|
|
||||||
|
|
||||||
if (!collaborator) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { background, stroke } = getClientColors(clientId, appState);
|
const { background, stroke } = getClientColors(clientId, appState);
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
ExcalidrawProps,
|
ExcalidrawProps,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { ToolButtonSize } from "../components/ToolButton";
|
|
||||||
|
|
||||||
export type ActionSource = "ui" | "keyboard" | "contextMenu" | "api";
|
export type ActionSource = "ui" | "keyboard" | "contextMenu" | "api";
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ export type PanelComponentProps = {
|
|||||||
appState: AppState;
|
appState: AppState;
|
||||||
updateData: (formData?: any) => void;
|
updateData: (formData?: any) => void;
|
||||||
appProps: ExcalidrawProps;
|
appProps: ExcalidrawProps;
|
||||||
data?: Partial<{ id: string; size: ToolButtonSize }>;
|
data?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
|
@ -25,7 +25,6 @@ import { PasteChartDialog } from "./PasteChartDialog";
|
|||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
import { HelpDialog } from "./HelpDialog";
|
import { HelpDialog } from "./HelpDialog";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { Tooltip } from "./Tooltip";
|
|
||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||||
import { JSONExportDialog } from "./JSONExportDialog";
|
import { JSONExportDialog } from "./JSONExportDialog";
|
||||||
@ -380,22 +379,10 @@ const LayerUI = ({
|
|||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<UserList>
|
<UserList
|
||||||
{appState.collaborators.size > 0 &&
|
collaborators={appState.collaborators}
|
||||||
Array.from(appState.collaborators)
|
actionManager={actionManager}
|
||||||
// Collaborator is either not initialized or is actually the current user.
|
/>
|
||||||
.filter(([_, client]) => Object.keys(client).length !== 0)
|
|
||||||
.map(([clientId, client]) => (
|
|
||||||
<Tooltip
|
|
||||||
label={client.username || "Unknown user"}
|
|
||||||
key={clientId}
|
|
||||||
>
|
|
||||||
{actionManager.renderAction("goToCollaborator", {
|
|
||||||
id: clientId,
|
|
||||||
})}
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</UserList>
|
|
||||||
{renderTopRightUI?.(deviceType.isMobile, appState)}
|
{renderTopRightUI?.(deviceType.isMobile, appState)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -202,20 +202,11 @@ export const MobileMenu = ({
|
|||||||
{appState.collaborators.size > 0 && (
|
{appState.collaborators.size > 0 && (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.collaborators")}</legend>
|
<legend>{t("labels.collaborators")}</legend>
|
||||||
<UserList mobile>
|
<UserList
|
||||||
{Array.from(appState.collaborators)
|
mobile
|
||||||
// Collaborator is either not initialized or is actually the current user.
|
collaborators={appState.collaborators}
|
||||||
.filter(
|
actionManager={actionManager}
|
||||||
([_, client]) => Object.keys(client).length !== 0,
|
/>
|
||||||
)
|
|
||||||
.map(([clientId, client]) => (
|
|
||||||
<React.Fragment key={clientId}>
|
|
||||||
{actionManager.renderAction("goToCollaborator", {
|
|
||||||
id: clientId,
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</UserList>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
)}
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
|
@ -2,17 +2,51 @@ import "./UserList.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { AppState, Collaborator } from "../types";
|
||||||
|
import { Tooltip } from "./Tooltip";
|
||||||
|
import { ActionManager } from "../actions/manager";
|
||||||
|
|
||||||
type UserListProps = {
|
export const UserList: React.FC<{
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
};
|
collaborators: AppState["collaborators"];
|
||||||
|
actionManager: ActionManager;
|
||||||
|
}> = ({ className, mobile, collaborators, actionManager }) => {
|
||||||
|
const uniqueCollaborators = new Map<string, Collaborator>();
|
||||||
|
|
||||||
|
collaborators.forEach((collaborator, socketId) => {
|
||||||
|
uniqueCollaborators.set(
|
||||||
|
// filter on user id, else fall back on unique socketId
|
||||||
|
collaborator.id || socketId,
|
||||||
|
collaborator,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const avatars =
|
||||||
|
uniqueCollaborators.size > 0 &&
|
||||||
|
Array.from(uniqueCollaborators)
|
||||||
|
.filter(([_, client]) => Object.keys(client).length !== 0)
|
||||||
|
.map(([clientId, collaborator]) => {
|
||||||
|
const avatarJSX = actionManager.renderAction("goToCollaborator", [
|
||||||
|
clientId,
|
||||||
|
collaborator,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return mobile ? (
|
||||||
|
<Tooltip
|
||||||
|
label={collaborator.username || "Unknown user"}
|
||||||
|
key={clientId}
|
||||||
|
>
|
||||||
|
{avatarJSX}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<React.Fragment key={clientId}>{avatarJSX}</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const UserList = ({ children, className, mobile }: UserListProps) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("UserList", className, { UserList_mobile: mobile })}>
|
<div className={clsx("UserList", className, { UserList_mobile: mobile })}>
|
||||||
{children}
|
{avatars}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
|
|
||||||
|
- Added support for supplying user `id` in the Collaborator object (see `collaborators` in [`updateScene()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#updateScene)), which will be used to deduplicate users when rendering collaborator avatar list. Cursors will still be rendered for every user. [#5309](https://github.com/excalidraw/excalidraw/pull/5309)
|
||||||
|
|
||||||
- Export API to [set](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setCursor) and [reset](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#resetCursor) mouse cursor on the canvas [#5215](https://github.com/excalidraw/excalidraw/pull/5215).
|
- Export API to [set](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#setCursor) and [reset](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#resetCursor) mouse cursor on the canvas [#5215](https://github.com/excalidraw/excalidraw/pull/5215).
|
||||||
|
|
||||||
- Export [`sceneCoordsToViewportCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) and [`viewportCoordsToSceneCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) utilities [#5187](https://github.com/excalidraw/excalidraw/pull/5187).
|
- Export [`sceneCoordsToViewportCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) and [`viewportCoordsToSceneCoords`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#onPointerDown) utilities [#5187](https://github.com/excalidraw/excalidraw/pull/5187).
|
||||||
|
@ -48,6 +48,8 @@ export type Collaborator = {
|
|||||||
// The url of the collaborator's avatar, defaults to username intials
|
// The url of the collaborator's avatar, defaults to username intials
|
||||||
// if not present
|
// if not present
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
// user id. If supplied, we'll filter out duplicates when rendering user avatars.
|
||||||
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataURL = string & { _brand: "DataURL" };
|
export type DataURL = string & { _brand: "DataURL" };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user