From e97a426680f096c3fa129ef13e3f69fc1a4999a4 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Fri, 24 Mar 2023 13:16:35 +0100 Subject: [PATCH] refactor: split avatar component to handle displaynames Signed-off-by: Tilman Vatteroth --- .../user-avatar/user-avatar-for-user.tsx | 23 +++++++++++++++ .../user-avatar/user-avatar-for-username.tsx | 26 ++++++++--------- .../common/user-avatar/user-avatar.test.tsx | 12 ++++---- .../common/user-avatar/user-avatar.tsx | 28 +++++++++++-------- .../permissions/permission-entry-user.tsx | 6 ++-- .../revisions/revision-list-entry.tsx | 4 +-- .../sidebar/user-line/user-line.tsx | 26 +++++++++++++---- .../users-online-sidebar-menu.tsx | 3 +- .../navigation/user-dropdown.tsx | 4 +-- 9 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 frontend/src/components/common/user-avatar/user-avatar-for-user.tsx diff --git a/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx b/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx new file mode 100644 index 000000000..9af787f71 --- /dev/null +++ b/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { UserInfo } from '../../../api/users/types' +import type { UserAvatarProps } from './user-avatar' +import { UserAvatar } from './user-avatar' +import React from 'react' + +export interface UserAvatarForUserProps extends Omit { + user: UserInfo +} + +/** + * Renders the avatar image of a user, optionally altogether with their name. + * + * @param user The user object with the display name and photo. + * @param props remaining avatar props + */ +export const UserAvatarForUser: React.FC = ({ user, ...props }) => { + return +} diff --git a/frontend/src/components/common/user-avatar/user-avatar-for-username.tsx b/frontend/src/components/common/user-avatar/user-avatar-for-username.tsx index a4a26329e..5bd329818 100644 --- a/frontend/src/components/common/user-avatar/user-avatar-for-username.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar-for-username.tsx @@ -4,15 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { getUser } from '../../../api/users' -import type { UserInfo } from '../../../api/users/types' import { AsyncLoadingBoundary } from '../async-loading-boundary/async-loading-boundary' import type { UserAvatarProps } from './user-avatar' import { UserAvatar } from './user-avatar' -import React from 'react' +import React, { Fragment, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useAsync } from 'react-use' -export interface UserAvatarForUsernameProps extends Omit { +export interface UserAvatarForUsernameProps extends Omit { username: string | null } @@ -27,20 +26,21 @@ export interface UserAvatarForUsernameProps extends Omit = ({ username, ...props }) => { const { t } = useTranslation() - const { error, value, loading } = useAsync(async (): Promise => { - if (username) { - return await getUser(username) - } - return { - displayName: t('common.guestUser'), - photo: `public/img/avatar.png`, - username: '' - } + const { error, value, loading } = useAsync(async (): Promise<{ displayName: string; photo?: string }> => { + return username + ? await getUser(username) + : { + displayName: t('common.guestUser') + } }, [username, t]) + const avatar = useMemo(() => { + return !value ? : + }, [props, value]) + return ( - + {avatar} ) } diff --git a/frontend/src/components/common/user-avatar/user-avatar.test.tsx b/frontend/src/components/common/user-avatar/user-avatar.test.tsx index 3e8f75bad..4ca1ba94f 100644 --- a/frontend/src/components/common/user-avatar/user-avatar.test.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar.test.tsx @@ -5,7 +5,7 @@ */ import type { UserInfo } from '../../../api/users/types' import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n' -import { UserAvatar } from './user-avatar' +import { UserAvatarForUser } from './user-avatar-for-user' import { render } from '@testing-library/react' describe('UserAvatar', () => { @@ -20,25 +20,25 @@ describe('UserAvatar', () => { }) it('renders the user avatar correctly', () => { - const view = render() + const view = render() expect(view.container).toMatchSnapshot() }) describe('renders the user avatar in size', () => { it('sm', () => { - const view = render() + const view = render() expect(view.container).toMatchSnapshot() }) it('lg', () => { - const view = render() + const view = render() expect(view.container).toMatchSnapshot() }) }) it('adds additionalClasses props to wrapping span', () => { - const view = render() + const view = render() expect(view.container).toMatchSnapshot() }) it('does not show names if showName prop is false', () => { - const view = render() + const view = render() expect(view.container).toMatchSnapshot() }) }) diff --git a/frontend/src/components/common/user-avatar/user-avatar.tsx b/frontend/src/components/common/user-avatar/user-avatar.tsx index e69853e40..2dedc3f96 100644 --- a/frontend/src/components/common/user-avatar/user-avatar.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar.tsx @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import type { UserInfo } from '../../../api/users/types' import { ShowIf } from '../show-if/show-if' import defaultAvatar from './default-avatar.png' import styles from './user-avatar.module.scss' @@ -16,7 +15,8 @@ export interface UserAvatarProps { size?: 'sm' | 'lg' additionalClasses?: string showName?: boolean - user: UserInfo + photoUrl?: string + displayName: string } /** @@ -27,7 +27,13 @@ export interface UserAvatarProps { * @param additionalClasses Additional CSS classes that will be added to the container. * @param showName true when the name should be displayed alongside the image, false otherwise. Defaults to true. */ -export const UserAvatar: React.FC = ({ user, size, additionalClasses = '', showName = true }) => { +export const UserAvatar: React.FC = ({ + photoUrl, + displayName, + size, + additionalClasses = '', + showName = true +}) => { const { t } = useTranslation() const imageSize = useMemo(() => { @@ -42,18 +48,18 @@ export const UserAvatar: React.FC = ({ user, size, additionalCl }, [size]) const avatarUrl = useMemo(() => { - return user.photo !== '' ? user.photo : defaultAvatar.src - }, [user.photo]) + return photoUrl || defaultAvatar.src + }, [photoUrl]) - const imgDescription = useMemo(() => t('common.avatarOf', { name: user.displayName }), [t, user]) + const imgDescription = useMemo(() => t('common.avatarOf', { name: displayName }), [t, displayName]) const tooltip = useCallback( - (props: OverlayInjectedProps) => ( - - {user.displayName} + (overlayInjectedProps: OverlayInjectedProps) => ( + + {displayName} ), - [user] + [displayName] ) return ( @@ -69,7 +75,7 @@ export const UserAvatar: React.FC = ({ user, size, additionalCl /> - {user.displayName} + {displayName} diff --git a/frontend/src/components/editor-page/document-bar/permissions/permission-entry-user.tsx b/frontend/src/components/editor-page/document-bar/permissions/permission-entry-user.tsx index da5647e71..4eb2e8cb8 100644 --- a/frontend/src/components/editor-page/document-bar/permissions/permission-entry-user.tsx +++ b/frontend/src/components/editor-page/document-bar/permissions/permission-entry-user.tsx @@ -9,7 +9,7 @@ import { getUser } from '../../../../api/users' import { useApplicationState } from '../../../../hooks/common/use-application-state' import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods' import { ShowIf } from '../../../common/show-if/show-if' -import { UserAvatar } from '../../../common/user-avatar/user-avatar' +import { UserAvatarForUser } from '../../../common/user-avatar/user-avatar-for-user' import { useUiNotifications } from '../../../notifications/ui-notification-boundary' import { PermissionEntryButtons, PermissionType } from './permission-entry-buttons' import { AccessLevel } from './types' @@ -58,13 +58,13 @@ export const PermissionEntryUser: React.FC = ({ entry }, [entry.username]) if (!value) { - return null + return <> } return (
  • - + = ({ active, on try { const authorDetails = await getUserDataForRevision(revision.authorUsernames) return authorDetails.map((author) => ( - + )) } catch (error) { showErrorNotification('editor.modal.revision.errorUser')(error as Error) diff --git a/frontend/src/components/editor-page/sidebar/user-line/user-line.tsx b/frontend/src/components/editor-page/sidebar/user-line/user-line.tsx index 7f07cc333..77725a67a 100644 --- a/frontend/src/components/editor-page/sidebar/user-line/user-line.tsx +++ b/frontend/src/components/editor-page/sidebar/user-line/user-line.tsx @@ -3,14 +3,16 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ +import { UserAvatar } from '../../../common/user-avatar/user-avatar' import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username' import { createCursorCssClass } from '../../editor-pane/codemirror-extensions/remote-cursors/create-cursor-css-class' import { ActiveIndicator } from '../users-online-sidebar-menu/active-indicator' import styles from './user-line.module.scss' -import React from 'react' +import React, { useMemo } from 'react' export interface UserLineProps { username: string | null + displayName: string active: boolean color: number } @@ -22,7 +24,22 @@ export interface UserLineProps { * @param color The color of the user's edits. * @param status The user's current online status. */ -export const UserLine: React.FC = ({ username, active, color }) => { +export const UserLine: React.FC = ({ username, displayName, active, color }) => { + const avatar = useMemo(() => { + if (username) { + return ( + + ) + } else { + return ( + + ) + } + }, [displayName, username]) + return (
    = ({ username, active, color }) = color )}`} /> - + {avatar}
    diff --git a/frontend/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx b/frontend/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx index 179e33fd2..2a54a4f3f 100644 --- a/frontend/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx +++ b/frontend/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx @@ -56,7 +56,8 @@ export const UsersOnlineSidebarMenu: React.FC = ({ return ( diff --git a/frontend/src/components/landing-layout/navigation/user-dropdown.tsx b/frontend/src/components/landing-layout/navigation/user-dropdown.tsx index 9d07b0624..f50f182b5 100644 --- a/frontend/src/components/landing-layout/navigation/user-dropdown.tsx +++ b/frontend/src/components/landing-layout/navigation/user-dropdown.tsx @@ -6,7 +6,7 @@ import { useApplicationState } from '../../../hooks/common/use-application-state' import { cypressId } from '../../../utils/cypress-attribute' import { UiIcon } from '../../common/icons/ui-icon' -import { UserAvatar } from '../../common/user-avatar/user-avatar' +import { UserAvatarForUser } from '../../common/user-avatar/user-avatar-for-user' import { SignOutDropdownButton } from './sign-out-dropdown-button' import Link from 'next/link' import React from 'react' @@ -29,7 +29,7 @@ export const UserDropdown: React.FC = () => { return ( - +