diff --git a/.changeset/pmp_user_heroes_fixrefactor_persona_picker.md b/.changeset/pmp_user_heroes_fixrefactor_persona_picker.md new file mode 100644 index 0000000000..5331bc273d --- /dev/null +++ b/.changeset/pmp_user_heroes_fixrefactor_persona_picker.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +# PMP User Heroes, Fix+Refactor Persona Picker diff --git a/src/app/components/UserRoomProfileRenderer.tsx b/src/app/components/UserRoomProfileRenderer.tsx index 462e51b6b5..9bbdcc99a0 100644 --- a/src/app/components/UserRoomProfileRenderer.tsx +++ b/src/app/components/UserRoomProfileRenderer.tsx @@ -9,7 +9,7 @@ import { UserRoomProfile } from './user-profile'; import { ResponsiveMenu } from './ResponsiveMenu'; function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState }) { - const { roomId, spaceId, userId, cords, position, initialProfile } = state; + const { roomId, spaceId, userId, pmp, cords, position, initialProfile } = state; const allJoinedRooms = useAllJoinedRoomsSet(); const getRoom = useGetRoom(allJoinedRooms); const room = getRoom(roomId); @@ -37,6 +37,7 @@ function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState }) userId={userId} initialProfile={initialProfile} onSurfaceColorChange={setSurfaceColor} + pmp={pmp} /> diff --git a/src/app/components/event-history/EventHistory.tsx b/src/app/components/event-history/EventHistory.tsx index 810cbd9b0e..6cc59126c1 100644 --- a/src/app/components/event-history/EventHistory.tsx +++ b/src/app/components/event-history/EventHistory.tsx @@ -253,6 +253,7 @@ export const EventHistory = as<'div', EventHistoryProps>( room.roomId, space?.roomId, readerId, + undefined, getMouseEventCords(event.nativeEvent), 'Bottom' ); diff --git a/src/app/components/event-readers/EventReaders.tsx b/src/app/components/event-readers/EventReaders.tsx index 6bc4842eb4..e1a4d08a64 100644 --- a/src/app/components/event-readers/EventReaders.tsx +++ b/src/app/components/event-readers/EventReaders.tsx @@ -93,6 +93,7 @@ export const EventReaders = as<'div', EventReadersProps>( room.roomId, space?.roomId, readerId, + undefined, getMouseEventCords(event.nativeEvent), 'Bottom' ); diff --git a/src/app/components/message-preview/MessagePreview.tsx b/src/app/components/message-preview/MessagePreview.tsx index f78a64a121..a47645c7ca 100644 --- a/src/app/components/message-preview/MessagePreview.tsx +++ b/src/app/components/message-preview/MessagePreview.tsx @@ -451,10 +451,11 @@ export function MessagePreview({ room.roomId, undefined, sender, + perMessageProfile, evt.currentTarget.getBoundingClientRect() ); }, - [openUserRoomProfile, room.roomId, sender] + [openUserRoomProfile, room.roomId, perMessageProfile, sender] ); return ( diff --git a/src/app/components/message/modals/Options.tsx b/src/app/components/message/modals/Options.tsx index 79e47e4cbe..bf018c863e 100644 --- a/src/app/components/message/modals/Options.tsx +++ b/src/app/components/message/modals/Options.tsx @@ -430,7 +430,6 @@ function OptionsReproxyPersonaPicker({ <> prev.some((claim) => claim.id === id) ? prev - : [...prev, { id, seq }].sort((a, b) => a.seq - b.seq) + : [...prev, { id, seq }].toSorted((a, b) => a.seq - b.seq) ), release: (id) => setClaims((prev) => prev.filter((claim) => claim.id !== id)), }), diff --git a/src/app/components/user-profile/UserHero.tsx b/src/app/components/user-profile/UserHero.tsx index 0a12511618..7f9b421c92 100644 --- a/src/app/components/user-profile/UserHero.tsx +++ b/src/app/components/user-profile/UserHero.tsx @@ -43,6 +43,9 @@ import { useTimeoutToggle } from '$hooks/useTimeoutToggle'; import { CopyIcon, CrossIcon } from '@phosphor-icons/react'; import { useOpenSettings } from '$features/settings'; import { ModalOverlay } from '$components/modal-overlay/ModalOverlay'; +import type { Persona } from '$app/persona'; +import type { MatrixClient } from 'matrix-js-sdk'; +import { usePersonaCosmetics } from '$hooks/usePerMessageProfile'; type UserHeroProps = { userId: string; @@ -258,10 +261,13 @@ export function UserHero({ } type UserHeroNameProps = { + mx?: MatrixClient; displayName?: string; userId: string; server?: string; customHeroCards?: boolean; + pmp?: Persona; + clearPmp?: () => void; }; type UserHeroNameInnerProps = { @@ -272,6 +278,8 @@ type UserHeroNameInnerProps = { color?: string; font?: string; customHeroCards?: boolean; + isPmp?: boolean; + clearPmp?: () => void; }; function UserHeroNameInner({ @@ -281,6 +289,8 @@ function UserHeroNameInner({ server, color, font, + isPmp, + clearPmp, }: UserHeroNameInnerProps) { const [copied, setCopied] = useTimeoutToggle(); const [isHovered, setIsHovered] = useState(false); @@ -332,26 +342,61 @@ function UserHeroNameInner({ ) } /> + {isPmp && ( + <> + {' - '} + { + evt.stopPropagation(); + clearPmp?.(); + }} + style={{ backgroundColor: 'transparent', color: 'inherit', padding: '0' }} + before={ + + View account profile + + } + /> + + )} ); } -export function UserHeroName({ displayName, userId, server, customHeroCards }: UserHeroNameProps) { +export function UserHeroName({ + mx, + displayName, + userId, + server, + customHeroCards, + pmp, + clearPmp, +}: UserHeroNameProps) { const username = getMxIdLocalPart(userId); const nick = useNickname(userId); + // personas + const { nameColor: getPmpNameColor } = usePersonaCosmetics(mx); + const pmpNameColor = pmp?.['eu.she-a.color'] ? getPmpNameColor?.(pmp) : null; + // Sable username color and fonts const { color, font } = useSableCosmetics(userId, useRoom(), customHeroCards); - const shownName = nick ?? displayName ?? username ?? userId; + const shownName = pmp?.displayname ?? nick ?? displayName ?? username ?? userId; return ( ); } diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index d1b07d58f9..9c99193c0e 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -62,6 +62,8 @@ import { KnownMembership } from '$types/matrix-sdk'; import { useRoomMemberHydration } from '$hooks/useRoomMemberHydration'; import * as css from './styles.css'; import * as prefix from '$unstable/prefixes'; +import type { Persona } from '$app/persona'; +import { usePersonaCosmetics } from '$hooks/usePerMessageProfile'; const KNOWN_KEYS = new Set([ prefix.MATRIX_SABLE_UNSTABLE_PROFILE_BIOGRAPHY_PROPERTY_NAME, @@ -80,6 +82,7 @@ const KNOWN_KEYS = new Set([ type UserExtendedSectionProps = { profile: UserProfile; + pmp?: Persona; htmlReactParserOptions: HTMLReactParserOptions; linkifyOpts: LinkifyOpts; innerColor?: string; @@ -96,6 +99,7 @@ const renderValue = (val: unknown) => { function UserExtendedSection({ profile, + pmp, htmlReactParserOptions, linkifyOpts, innerColor, @@ -127,7 +131,7 @@ function UserExtendedSection({ const languagesToFilterFor = getSettings().filterPronounsLanguages ?? ['en']; const pronouns = filterPronounsByLanguage( - profile.pronouns, + pmp?.['io.fsky.nyx.pronouns'] ?? profile.pronouns, languageFilterEnabled, languagesToFilterFor ) @@ -403,11 +407,13 @@ function UserExtendedSection({ type UserRoomProfileProps = { userId: string; + pmp?: Persona; initialProfile?: Partial; onSurfaceColorChange?: (color: string) => void; }; export function UserRoomProfile({ userId, + pmp: initialPmp, initialProfile, onSurfaceColorChange, }: Readonly) { @@ -456,8 +462,17 @@ export function UserRoomProfile({ useRoomMemberHydration(room, userId); + const [pmp, setPmp] = useState(initialPmp); + const { avatarUrl: getPmpAvatarUrl } = usePersonaCosmetics(mx, false); + const pmpAvatarUrl = pmp?.avatar_url ? getPmpAvatarUrl?.(pmp) : null; + + const handleClearPmp = () => { + setPmp(undefined); + }; + const avatarMxc = getMemberAvatarMxc(room, userId) ?? extendedProfile.avatarUrl; - const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined; + const avatarUrl = + pmpAvatarUrl ?? (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined; const parsedBanner = typeof extendedProfile.bannerUrl === 'string' @@ -607,10 +622,13 @@ export function UserRoomProfile({ > {userId !== myUserId && (