feat(web): space roles, upload queue with pipelined frames, background resume and faster sending
This commit is contained in:
1 parent
96c4335cb5
commit
a574583943
39 files changed
+1515
-194
No files matched your search
@@ -5,10 +5,13 @@ import { errorMessage } from '@/core-bridge/errors';
|
||||
import type { SenderInfo } from '@/core-bridge/types';
|
||||
import { useToast } from '@/store/toast';
|
||||
import { useWorkspace } from '@/store/workspace';
|
||||
import { permissionsOf } from '@/lib/roles';
|
||||
import { useAccount } from '@/store/account';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { NameWithBadges } from './RoleBadges';
|
||||
import styles from './MembersPane.module.css';
|
||||
|
||||
interface Props { owner: boolean; refreshKey: number; onInvite: () => void }
|
||||
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="" />
|
||||
@@ -22,10 +25,12 @@ function Section({ title, count, children }: { title: string; count: number; chi
|
||||
/** 右栏「成员」页:以用户为单位,按在线 / 离线 / 待接受分组。设备只是用户的属性(在线设备数由后端按账号统计) */
|
||||
|
||||
const PRESENCE_POLL_MS = 8000;
|
||||
export function MembersPane({ owner, refreshKey, onInvite }: Props) {
|
||||
export function MembersPane({ owner, canRemove, refreshKey, onInvite }: Props) {
|
||||
const channelId = useWorkspace((s) => s.channel?.id);
|
||||
const maxMembers = useWorkspace((s) => s.channel?.maxMembers);
|
||||
const people = useWorkspace((s) => s.people);
|
||||
const roles = useWorkspace((s) => s.roles);
|
||||
const self = useAccount((s) => s.username);
|
||||
const listInvitations = useWorkspace((s) => s.listInvitations);
|
||||
const revoke = useWorkspace((s) => s.revokeInvitation);
|
||||
const [members, setMembers] = useState<ChannelMember[]>([]);
|
||||
@@ -37,10 +42,10 @@ export function MembersPane({ owner, refreshKey, onInvite }: Props) {
|
||||
if (!channelId) return;
|
||||
try {
|
||||
setMembers(await channelAccessApi.members(channelId));
|
||||
setPending(owner ? await listInvitations() : []);
|
||||
setPending(canRemove ? await listInvitations() : []);
|
||||
setError('');
|
||||
} catch { setError('成员清单读取失败'); }
|
||||
}, [channelId, owner, listInvitations]);
|
||||
}, [channelId, canRemove, listInvitations]);
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
// 在线状态不走订阅流,成员页可见期间定时读取
|
||||
@@ -66,15 +71,17 @@ export function MembersPane({ owner, refreshKey, onInvite }: Props) {
|
||||
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} />
|
||||
<span className={styles.who}><strong>{name}{isOwner ? <em>空间主</em> : null}</strong>
|
||||
<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}
|
||||
{removable ? <Button className={styles.remove} variant="ghost" disabled={busy} onClick={() => { void remove(username, invited); }}>{invited ? '撤销' : '移除'}</Button> : null}
|
||||
</li>;
|
||||
};
|
||||
// 被授权的成员不能移除自己、空间主,也不能移除同样有移除权限的人
|
||||
const removable = (item: ChannelMember) => item.role !== 'owner' && (owner || (canRemove && item.username !== self && !permissionsOf(roles, item.username, false).has('members.remove')));
|
||||
const member = (item: ChannelMember) => row(item.username,
|
||||
item.devices > 1 || item.online_devices > 1 ? `${item.online_devices}/${item.devices} 台设备在线` : undefined,
|
||||
false, owner && item.role !== 'owner', item.role === 'owner', item.online);
|
||||
false, removable(item), item.role === 'owner', item.online);
|
||||
|
||||
return <div className={styles.pane}>
|
||||
<div className={styles.head}>
|
||||
@@ -86,7 +93,7 @@ export function MembersPane({ owner, refreshKey, onInvite }: Props) {
|
||||
<Section title="离线" count={offline.length}>{offline.map(member)}</Section>
|
||||
<Section title="待接受" count={pending.length}>{pending.map((invitation) => row(invitation.username, undefined, true, owner))}</Section>
|
||||
{error ? <p className={styles.error} role="alert">{error}</p> : null}
|
||||
{!owner ? <p className={styles.note}>只有空间主可以邀请或移除成员。</p> : null}
|
||||
{!owner ? <p className={styles.note}>{canRemove ? '你可以移除成员;邀请成员只有空间主可以。' : '只有空间主可以邀请或移除成员。'}</p> : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
Reference in new issue
Block a user