feat(bots): render Matrix-native bot avatar in BotCard sidebar row and BotShellHero so server-side avatar_url propagates without client patches
This commit is contained in:
parent
316c3eb9fd
commit
bae6761683
3 changed files with 62 additions and 6 deletions
|
|
@ -1,7 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Avatar, Box, Text, toRem } from 'folds';
|
||||
import { Avatar, AvatarImage, Box, Text, toRem } from 'folds';
|
||||
import { NavItem, NavItemContent, NavLink } from '../../components/nav';
|
||||
import { getBotPath } from '../../pages/pathUtils';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useUserProfile } from '../../hooks/useUserProfile';
|
||||
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||
import type { BotPreset } from './catalog';
|
||||
|
||||
const MONO_FONT = '"JetBrains Mono Variable", ui-monospace, monospace';
|
||||
|
|
@ -14,8 +18,21 @@ type BotCardProps = {
|
|||
};
|
||||
|
||||
export function BotCard({ preset, selected }: BotCardProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const initial = preset.name.trim().charAt(0).toUpperCase() || '?';
|
||||
|
||||
// Standard Matrix avatar resolution. `useUserProfile` returns the cached
|
||||
// profile synchronously and subscribes to live `UserEvent.AvatarUrl`
|
||||
// updates — when the operator sets the bot's `avatar_url` server-side
|
||||
// (mautrix-telegram bridge config), every BotCard remount picks it up
|
||||
// without client deploys. The fleet-blue letter square remains as the
|
||||
// fallback when the bot has no profile avatar yet.
|
||||
const { avatarUrl: avatarMxc } = useUserProfile(preset.mxid);
|
||||
const avatarUrl = avatarMxc
|
||||
? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 56, 56, 'crop') ?? undefined
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<NavItem
|
||||
variant="Background"
|
||||
|
|
@ -37,9 +54,13 @@ export function BotCard({ preset, selected }: BotCardProps) {
|
|||
}}
|
||||
>
|
||||
<Avatar size="300" radii="400" style={{ background: AVATAR_BG, color: '#0c0c0e' }}>
|
||||
<Text as="span" size="H6" style={{ color: '#0c0c0e', fontWeight: 700 }}>
|
||||
{initial}
|
||||
</Text>
|
||||
{avatarUrl ? (
|
||||
<AvatarImage src={avatarUrl} alt={preset.name} />
|
||||
) : (
|
||||
<Text as="span" size="H6" style={{ color: '#0c0c0e', fontWeight: 700 }}>
|
||||
{initial}
|
||||
</Text>
|
||||
)}
|
||||
</Avatar>
|
||||
<Box
|
||||
as="span"
|
||||
|
|
|
|||
|
|
@ -99,7 +99,10 @@ export const HeroBack = style([
|
|||
|
||||
// 56×56 square avatar with 14px radius, fleet violet (DAWN.fleet) bg.
|
||||
// Fleet color is hardcoded here because it's the canonical bot accent in
|
||||
// the mockup and we don't want it varying with Folds palette swaps.
|
||||
// the mockup and we don't want it varying with Folds palette swaps. The
|
||||
// violet square shows when the bot's Matrix profile has no `avatar_url`
|
||||
// (fallback to the initial letter); when it does, the inner <img> covers
|
||||
// the violet — `overflow: hidden` keeps it inside the rounded corners.
|
||||
export const HeroAvatar = style([
|
||||
DefaultReset,
|
||||
{
|
||||
|
|
@ -114,6 +117,7 @@ export const HeroAvatar = style([
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
|
||||
'@media': {
|
||||
'(max-width: 600px)': {
|
||||
|
|
@ -126,6 +130,20 @@ export const HeroAvatar = style([
|
|||
},
|
||||
]);
|
||||
|
||||
// Avatar image fills the violet square. `objectFit: cover` plus the
|
||||
// container's `overflow: hidden` means non-square Matrix avatars (which
|
||||
// arbitrarily sized) render correctly without showing the violet bg
|
||||
// around the corners.
|
||||
export const HeroAvatarImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
},
|
||||
]);
|
||||
|
||||
export const HeroBody = style([
|
||||
DefaultReset,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import { BotShellMenu } from './BotShellMenu';
|
|||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||
import * as css from './BotShell.css';
|
||||
|
||||
type BotShellHeroProps = {
|
||||
|
|
@ -28,10 +31,24 @@ const heroInitial = (preset: BotPreset): string => {
|
|||
|
||||
export function BotShellHero({ preset, room }: BotShellHeroProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const screenSize = useScreenSizeContext();
|
||||
const isMobile = screenSize === ScreenSize.Mobile;
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
|
||||
// Standard Matrix avatar resolution. The bot user's profile carries the
|
||||
// canonical `avatar_url` (set server-side by the bridge or homeserver
|
||||
// admin); every Matrix client reads the same source. We pull it via the
|
||||
// bot's RoomMember in this DM rather than `mx.getUser()` because the
|
||||
// member event in this room is guaranteed to be loaded by the time we
|
||||
// render — we wouldn't be on `/bots/<id>` otherwise — and avoids any
|
||||
// edge case where `mx.getUser()` returns a stale or missing entry.
|
||||
const avatarMxc = room.getMember(preset.mxid)?.getMxcAvatarUrl();
|
||||
const avatarUrl = avatarMxc
|
||||
? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined
|
||||
: undefined;
|
||||
|
||||
const handleOpenMenu: React.MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
|
@ -66,7 +83,7 @@ export function BotShellHero({ preset, room }: BotShellHeroProps) {
|
|||
</BackRouteHandler>
|
||||
)}
|
||||
<div className={css.HeroAvatar} aria-hidden="true">
|
||||
{initial}
|
||||
{avatarUrl ? <img className={css.HeroAvatarImg} src={avatarUrl} alt="" /> : initial}
|
||||
</div>
|
||||
<div className={css.HeroBody}>
|
||||
<div className={css.HeroTitleRow}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue