fix: follow mode collaborator status indicator (#7459)
This commit is contained in:
parent
2a0fe2584e
commit
0808532b49
@ -1,5 +1,8 @@
|
||||
import { getClientColor } from "../clients";
|
||||
import { Avatar } from "../components/Avatar";
|
||||
import { GoToCollaboratorComponentProps } from "../components/UserList";
|
||||
import { eyeIcon } from "../components/icons";
|
||||
import { t } from "../i18n";
|
||||
import { Collaborator } from "../types";
|
||||
import { register } from "./register";
|
||||
|
||||
@ -35,38 +38,43 @@ export const actionGoToCollaborator = register({
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData, data, appState }) => {
|
||||
const [clientId, collaborator, withName] = data as [
|
||||
string,
|
||||
Collaborator,
|
||||
boolean,
|
||||
];
|
||||
const [socketId, collaborator, withName, isBeingFollowed] =
|
||||
data as GoToCollaboratorComponentProps;
|
||||
|
||||
const background = getClientColor(clientId);
|
||||
const background = getClientColor(socketId);
|
||||
|
||||
return withName ? (
|
||||
<div
|
||||
className="dropdown-menu-item dropdown-menu-item-base"
|
||||
onClick={() => updateData({ ...collaborator, clientId })}
|
||||
className="dropdown-menu-item dropdown-menu-item-base UserList__collaborator"
|
||||
onClick={() => updateData({ ...collaborator, socketId })}
|
||||
>
|
||||
<Avatar
|
||||
color={background}
|
||||
onClick={() => {}}
|
||||
name={collaborator.username || ""}
|
||||
src={collaborator.avatarUrl}
|
||||
isBeingFollowed={appState.userToFollow?.socketId === clientId}
|
||||
isBeingFollowed={isBeingFollowed}
|
||||
isCurrentUser={collaborator.isCurrentUser === true}
|
||||
/>
|
||||
{collaborator.username}
|
||||
<div
|
||||
className="UserList__collaborator-follow-status-icon"
|
||||
style={{ visibility: isBeingFollowed ? "visible" : "hidden" }}
|
||||
title={isBeingFollowed ? t("userList.hint.followStatus") : undefined}
|
||||
aria-hidden
|
||||
>
|
||||
{eyeIcon}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
color={background}
|
||||
onClick={() => {
|
||||
updateData({ ...collaborator, clientId });
|
||||
updateData({ ...collaborator, socketId });
|
||||
}}
|
||||
name={collaborator.username || ""}
|
||||
src={collaborator.avatarUrl}
|
||||
isBeingFollowed={appState.userToFollow?.socketId === clientId}
|
||||
isBeingFollowed={isBeingFollowed}
|
||||
isCurrentUser={collaborator.isCurrentUser === true}
|
||||
/>
|
||||
);
|
||||
|
@ -3472,6 +3472,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
};
|
||||
|
||||
private maybeUnfollowRemoteUser = () => {
|
||||
console.warn("maybeUnfollowRemoteUser");
|
||||
if (this.state.userToFollow) {
|
||||
this.setState({ userToFollow: null });
|
||||
}
|
||||
|
@ -339,7 +339,10 @@ const LayerUI = ({
|
||||
)}
|
||||
>
|
||||
{appState.collaborators.size > 0 && (
|
||||
<UserList collaborators={appState.collaborators} />
|
||||
<UserList
|
||||
collaborators={appState.collaborators}
|
||||
userToFollow={appState.userToFollow?.socketId || null}
|
||||
/>
|
||||
)}
|
||||
{renderTopRightUI?.(device.editor.isMobile, appState)}
|
||||
{!appState.viewModeEnabled &&
|
||||
|
@ -80,6 +80,7 @@ type TooltipProps = {
|
||||
label: string;
|
||||
long?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const Tooltip = ({
|
||||
@ -87,11 +88,15 @@ export const Tooltip = ({
|
||||
label,
|
||||
long = false,
|
||||
style,
|
||||
disabled,
|
||||
}: TooltipProps) => {
|
||||
useEffect(() => {
|
||||
return () =>
|
||||
getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
|
||||
}, []);
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="excalidraw-tooltip-wrapper"
|
||||
|
@ -51,6 +51,13 @@
|
||||
color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.UserList__collaborator-follow-status-icon {
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
width: 1rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
--userlist-hint-bg-color: var(--color-gray-10);
|
||||
--userlist-hint-heading-color: var(--color-gray-80);
|
||||
--userlist-hint-text-color: var(--color-gray-60);
|
||||
|
@ -13,51 +13,62 @@ import { searchIcon } from "./icons";
|
||||
import { t } from "../i18n";
|
||||
import { isShallowEqual } from "../utils";
|
||||
|
||||
export type GoToCollaboratorComponentProps = [
|
||||
SocketId,
|
||||
Collaborator,
|
||||
boolean,
|
||||
boolean,
|
||||
];
|
||||
|
||||
const FIRST_N_AVATARS = 3;
|
||||
const SHOW_COLLABORATORS_FILTER_AT = 8;
|
||||
|
||||
const ConditionalTooltipWrapper = ({
|
||||
shouldWrap,
|
||||
children,
|
||||
clientId,
|
||||
socketId,
|
||||
username,
|
||||
}: {
|
||||
shouldWrap: boolean;
|
||||
children: React.ReactNode;
|
||||
username?: string | null;
|
||||
clientId: string;
|
||||
socketId: string;
|
||||
}) =>
|
||||
shouldWrap ? (
|
||||
<Tooltip label={username || "Unknown user"} key={clientId}>
|
||||
<Tooltip label={username || "Unknown user"} key={socketId}>
|
||||
{children}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<React.Fragment key={clientId}>{children}</React.Fragment>
|
||||
<React.Fragment key={socketId}>{children}</React.Fragment>
|
||||
);
|
||||
|
||||
const renderCollaborator = ({
|
||||
actionManager,
|
||||
collaborator,
|
||||
clientId,
|
||||
socketId,
|
||||
withName = false,
|
||||
shouldWrapWithTooltip = false,
|
||||
isBeingFollowed,
|
||||
}: {
|
||||
actionManager: ActionManager;
|
||||
collaborator: Collaborator;
|
||||
clientId: string;
|
||||
socketId: string;
|
||||
withName?: boolean;
|
||||
shouldWrapWithTooltip?: boolean;
|
||||
isBeingFollowed: boolean;
|
||||
}) => {
|
||||
const avatarJSX = actionManager.renderAction("goToCollaborator", [
|
||||
clientId,
|
||||
const data: GoToCollaboratorComponentProps = [
|
||||
socketId,
|
||||
collaborator,
|
||||
withName,
|
||||
]);
|
||||
isBeingFollowed,
|
||||
];
|
||||
const avatarJSX = actionManager.renderAction("goToCollaborator", data);
|
||||
|
||||
return (
|
||||
<ConditionalTooltipWrapper
|
||||
key={clientId}
|
||||
clientId={clientId}
|
||||
key={socketId}
|
||||
socketId={socketId}
|
||||
username={collaborator.username}
|
||||
shouldWrap={shouldWrapWithTooltip}
|
||||
>
|
||||
@ -75,6 +86,7 @@ type UserListProps = {
|
||||
className?: string;
|
||||
mobile?: boolean;
|
||||
collaborators: Map<SocketId, UserListUserObject>;
|
||||
userToFollow: SocketId | null;
|
||||
};
|
||||
|
||||
const collaboratorComparatorKeys = [
|
||||
@ -85,7 +97,7 @@ const collaboratorComparatorKeys = [
|
||||
] as const;
|
||||
|
||||
export const UserList = React.memo(
|
||||
({ className, mobile, collaborators }: UserListProps) => {
|
||||
({ className, mobile, collaborators, userToFollow }: UserListProps) => {
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
const uniqueCollaboratorsMap = new Map<string, Collaborator>();
|
||||
@ -98,7 +110,6 @@ export const UserList = React.memo(
|
||||
);
|
||||
});
|
||||
|
||||
// const uniqueCollaboratorsMap = sampleCollaborators;
|
||||
const uniqueCollaboratorsArray = Array.from(uniqueCollaboratorsMap).filter(
|
||||
([_, collaborator]) => collaborator.username?.trim(),
|
||||
);
|
||||
@ -123,23 +134,25 @@ export const UserList = React.memo(
|
||||
);
|
||||
|
||||
const firstNAvatarsJSX = firstNCollaborators.map(
|
||||
([clientId, collaborator]) =>
|
||||
([socketId, collaborator]) =>
|
||||
renderCollaborator({
|
||||
actionManager,
|
||||
collaborator,
|
||||
clientId,
|
||||
socketId,
|
||||
shouldWrapWithTooltip: true,
|
||||
isBeingFollowed: socketId === userToFollow,
|
||||
}),
|
||||
);
|
||||
|
||||
return mobile ? (
|
||||
<div className={clsx("UserList UserList_mobile", className)}>
|
||||
{uniqueCollaboratorsArray.map(([clientId, collaborator]) =>
|
||||
{uniqueCollaboratorsArray.map(([socketId, collaborator]) =>
|
||||
renderCollaborator({
|
||||
actionManager,
|
||||
collaborator,
|
||||
clientId,
|
||||
socketId,
|
||||
shouldWrapWithTooltip: true,
|
||||
isBeingFollowed: socketId === userToFollow,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
@ -161,7 +174,7 @@ export const UserList = React.memo(
|
||||
<Popover.Content
|
||||
style={{
|
||||
zIndex: 2,
|
||||
width: "12rem",
|
||||
width: "13rem",
|
||||
textAlign: "left",
|
||||
}}
|
||||
align="end"
|
||||
@ -192,12 +205,13 @@ export const UserList = React.memo(
|
||||
<div className="UserList__hint">
|
||||
{t("userList.hint.text")}
|
||||
</div>
|
||||
{filteredCollaborators.map(([clientId, collaborator]) =>
|
||||
{filteredCollaborators.map(([socketId, collaborator]) =>
|
||||
renderCollaborator({
|
||||
actionManager,
|
||||
collaborator,
|
||||
clientId,
|
||||
socketId,
|
||||
withName: true,
|
||||
isBeingFollowed: socketId === userToFollow,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
@ -212,7 +226,8 @@ export const UserList = React.memo(
|
||||
if (
|
||||
prev.collaborators.size !== next.collaborators.size ||
|
||||
prev.mobile !== next.mobile ||
|
||||
prev.className !== next.className
|
||||
prev.className !== next.className ||
|
||||
prev.userToFollow !== next.userToFollow
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ const MainMenu = Object.assign(
|
||||
<UserList
|
||||
mobile={true}
|
||||
collaborators={appState.collaborators}
|
||||
userToFollow={appState.userToFollow?.socketId || null}
|
||||
/>
|
||||
</fieldset>
|
||||
)}
|
||||
|
@ -528,7 +528,8 @@
|
||||
"empty": "No users found"
|
||||
},
|
||||
"hint": {
|
||||
"text": "Click on user to follow"
|
||||
"text": "Click on user to follow",
|
||||
"followStatus": "You're currently following this user"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user