feat: Enhance user color and banner management
- Updated `useUserColor` hook to support fetching and embedding user color from avatar metadata with authentication. - Introduced `useUserBanner` hook for managing user banners stored in avatar metadata, including fetching and embedding functionality. - Added `fetchAndExtractMetadata` utility to retrieve both color and banner from images. - Implemented `CustomStatusDialog` component for users to set and clear custom status messages. - Modified `SettingsTab` to include custom status functionality and improved user experience. - Enhanced image metadata utilities to support banner extraction and embedding in PNG format. - Updated sidebar settings to handle user profile changes more gracefully.
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
color,
|
||||
Icon,
|
||||
Icons,
|
||||
Modal,
|
||||
@@ -9,38 +10,51 @@ import {
|
||||
OverlayBackdrop,
|
||||
OverlayCenter,
|
||||
Text,
|
||||
toRem,
|
||||
} from 'folds';
|
||||
import classNames from 'classnames';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import * as css from './styles.css';
|
||||
import { UserAvatar } from '../user-avatar';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||
import { BreakWord, LineClamp3 } from '../../styles/Text.css';
|
||||
import { UserPresence } from '../../hooks/useUserPresence';
|
||||
import { AvatarPresence, PresenceBadge } from '../presence';
|
||||
import { ImageViewer } from '../image-viewer';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useOtherUserColor } from '../../hooks/useUserColor';
|
||||
import { useOtherUserBanner } from '../../hooks/useUserBanner';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
|
||||
type UserHeroProps = {
|
||||
userId: string;
|
||||
avatarUrl?: string;
|
||||
avatarMxc?: string;
|
||||
presence?: UserPresence;
|
||||
};
|
||||
export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
|
||||
export function UserHero({ userId, avatarUrl, avatarMxc, presence }: UserHeroProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [viewAvatar, setViewAvatar] = useState<string>();
|
||||
const bannerUrl = useOtherUserBanner(userId, avatarMxc); // Now returns blob URL directly
|
||||
|
||||
return (
|
||||
<Box direction="Column" className={css.UserHero}>
|
||||
<div
|
||||
className={css.UserHeroCoverContainer}
|
||||
style={{
|
||||
backgroundColor: colorMXID(userId),
|
||||
filter: avatarUrl ? undefined : 'brightness(50%)',
|
||||
backgroundColor: bannerUrl ? undefined : colorMXID(userId),
|
||||
filter: bannerUrl || avatarUrl ? undefined : 'brightness(50%)',
|
||||
}}
|
||||
>
|
||||
{avatarUrl && (
|
||||
<img className={css.UserHeroCover} src={avatarUrl} alt={userId} draggable="false" />
|
||||
{bannerUrl ? (
|
||||
<img className={css.UserHeroBanner} src={bannerUrl} alt={userId} draggable="false" />
|
||||
) : (
|
||||
avatarUrl && (
|
||||
<img className={css.UserHeroCover} src={avatarUrl} alt={userId} draggable="false" />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className={css.UserHeroAvatarContainer}>
|
||||
@@ -65,6 +79,23 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
|
||||
/>
|
||||
</Avatar>
|
||||
</AvatarPresence>
|
||||
{presence?.status && (
|
||||
<Box
|
||||
className={css.UserStatusBubble}
|
||||
style={{
|
||||
backgroundColor: color.Surface.Container,
|
||||
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
|
||||
padding: `${toRem(6)} ${toRem(10)}`,
|
||||
borderRadius: toRem(16),
|
||||
maxWidth: toRem(250),
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
}}
|
||||
>
|
||||
<Text size="T300" className={BreakWord}>
|
||||
{presence.status}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{viewAvatar && (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
@@ -76,7 +107,7 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal size="500" onContextMenu={(evt: any) => evt.stopPropagation()}>
|
||||
<Modal size="500" onContextMenu={(evt: React.MouseEvent) => evt.stopPropagation()}>
|
||||
<ImageViewer
|
||||
src={viewAvatar}
|
||||
alt={userId}
|
||||
@@ -95,12 +126,14 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
|
||||
type UserHeroNameProps = {
|
||||
displayName?: string;
|
||||
userId: string;
|
||||
avatarMxc?: string;
|
||||
};
|
||||
export function UserHeroName({ displayName, userId }: UserHeroNameProps) {
|
||||
export function UserHeroName({ displayName, userId, avatarMxc }: UserHeroNameProps) {
|
||||
const username = getMxIdLocalPart(userId);
|
||||
const userColor = useOtherUserColor(userId, avatarMxc);
|
||||
|
||||
return (
|
||||
<Box grow="Yes" direction="Column" gap="0">
|
||||
<Box grow="Yes" direction="Column" gap="100">
|
||||
<Box alignItems="Baseline" gap="200" wrap="Wrap">
|
||||
<Text
|
||||
size="H4"
|
||||
@@ -111,8 +144,13 @@ export function UserHeroName({ displayName, userId }: UserHeroNameProps) {
|
||||
</Text>
|
||||
</Box>
|
||||
<Box alignItems="Center" gap="100" wrap="Wrap">
|
||||
<Text size="T200" className={classNames(BreakWord, LineClamp3)} title={username}>
|
||||
@{username}
|
||||
<Text
|
||||
size="T200"
|
||||
className={BreakWord}
|
||||
title={userId}
|
||||
style={{ color: userColor || colorMXID(userId) }}
|
||||
>
|
||||
{userId}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user