feat(web): avatar thumbnails with original preview, registration avatar, pure-JS crypto fallback for http [CI]
This commit is contained in:
1 parent
283196938e
commit
d66c93fe98
36 files changed
+735
-60
No files matched your search
@@ -7,14 +7,16 @@ import { useToast } from '@/store/toast';
|
||||
import { useWorkspace } from '@/store/workspace';
|
||||
import { permissionsOf } from '@/lib/roles';
|
||||
import { useAccount } from '@/store/account';
|
||||
import { accountApi } from '@/core-bridge/account';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { AvatarPreview } from '../common/AvatarPreview';
|
||||
import { NameWithBadges } from './RoleBadges';
|
||||
import styles from './MembersPane.module.css';
|
||||
|
||||
interface Props { owner: boolean; /** 空间主,或所在身份组被授予「移除成员」 */ canRemove: boolean; refreshKey: number; onInvite: () => void }
|
||||
|
||||
function Avatar({ person, name }: { person?: SenderInfo; name: string }) {
|
||||
return person?.avatar ? <img className={styles.avatar} src={person.avatar} alt="" />
|
||||
function Avatar({ person, name, onOpen }: { person?: SenderInfo; name: string; onOpen: () => void }) {
|
||||
return person?.avatar ? <button type="button" className={styles.avatarButton} aria-label={`查看 ${name} 的头像原图`} onClick={onOpen}><img className={styles.avatar} src={person.avatar} alt="" /></button>
|
||||
: <span className={`${styles.avatar} ${styles.avatarFallback}`} aria-hidden="true">{Array.from(name)[0]?.toLocaleUpperCase() ?? '?'}</span>;
|
||||
}
|
||||
|
||||
@@ -36,6 +38,7 @@ export function MembersPane({ owner, canRemove, refreshKey, onInvite }: Props) {
|
||||
const [members, setMembers] = useState<ChannelMember[]>([]);
|
||||
const [pending, setPending] = useState<AccountInvitation[]>([]);
|
||||
const [error, setError] = useState('');
|
||||
const [viewing, setViewing] = useState<string>();
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -70,7 +73,7 @@ export function MembersPane({ owner, canRemove, refreshKey, onInvite }: Props) {
|
||||
const person = people[username];
|
||||
const name = person?.displayName || username;
|
||||
return <li className={`${styles.row} ${invited || (!isOnline && !isOwner) ? styles.dim : ''}`} key={`${invited ? 'i' : 'm'}:${username}`}>
|
||||
<Avatar person={person} name={name} />
|
||||
<Avatar person={person} name={name} onOpen={() => setViewing(username)} />
|
||||
<span className={styles.who}><strong><NameWithBadges username={username}>{name}</NameWithBadges></strong>
|
||||
{detail || name !== username ? <small>{[name !== username ? username : '', detail].filter(Boolean).join(' · ')}</small> : null}</span>
|
||||
{isOnline ? <span className={styles.online} role="img" aria-label="在线" /> : null}
|
||||
@@ -83,7 +86,9 @@ export function MembersPane({ owner, canRemove, refreshKey, onInvite }: Props) {
|
||||
item.devices > 1 || item.online_devices > 1 ? `${item.online_devices}/${item.devices} 台设备在线` : undefined,
|
||||
false, removable(item), item.role === 'owner', item.online);
|
||||
|
||||
const viewed = viewing ? people[viewing] : undefined;
|
||||
return <div className={styles.pane}>
|
||||
{viewing && viewed?.avatar ? <AvatarPreview title={`${viewed.displayName || viewing} 的头像`} thumb={viewed.avatar} load={() => accountApi.avatar(viewing, 0, true)} onClose={() => setViewing(undefined)} /> : null}
|
||||
<div className={styles.head}>
|
||||
<span>成员 {members.length}{maxMembers ? `/${maxMembers}` : ''}</span>
|
||||
{owner ? <Button variant="primary" icon="plus" onClick={onInvite}>邀请成员</Button> : null}
|
||||
|
||||
Reference in new issue
Block a user