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
+21
-8
@@ -10,16 +10,29 @@ export function formatSize(bytes: number): string {
|
||||
return `${v >= 100 ? v.toFixed(0) : v.toFixed(1)} ${units[i]}`;
|
||||
}
|
||||
|
||||
export function formatTransferProgress(state: {
|
||||
export interface TransferState {
|
||||
progress: number;
|
||||
phase?: 'encrypting' | 'uploading';
|
||||
phase?: 'queued' | 'encrypting' | 'uploading';
|
||||
speedBytesPerSecond?: number;
|
||||
}): string {
|
||||
const phase = state.phase === 'encrypting' ? '加密中' : state.phase === 'uploading' ? '上传中' : '传输中';
|
||||
const speed = state.speedBytesPerSecond && state.speedBytesPerSecond > 0
|
||||
? ` · ${formatSize(state.speedBytesPerSecond)}/s`
|
||||
: '';
|
||||
return `${phase} ${Math.round(state.progress * 100)}%${speed}`;
|
||||
etaSeconds?: number;
|
||||
}
|
||||
|
||||
export function formatEta(seconds: number): string {
|
||||
if (seconds < 60) return `剩 ${Math.max(1, Math.round(seconds))} 秒`;
|
||||
if (seconds < 3600) return `剩 ${Math.ceil(seconds / 60)} 分钟`;
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
return `剩 ${hours} 小时 ${Math.ceil((seconds % 3600) / 60)} 分钟`;
|
||||
}
|
||||
|
||||
/** 传输状态的一行文字:排队中 / 加密中 / 上传中 42% · 3.2 MB/s · 剩 12 秒 */
|
||||
export function formatTransferProgress(state: TransferState): string {
|
||||
if (state.phase === 'queued') return '排队中';
|
||||
const percent = Math.round(state.progress * 100);
|
||||
if (state.phase === 'encrypting' && percent === 0) return '加密中…';
|
||||
const parts = [`${state.phase === 'encrypting' ? '加密中' : '上传中'} ${percent}%`];
|
||||
if (state.speedBytesPerSecond && state.speedBytesPerSecond > 0) parts.push(`${formatSize(state.speedBytesPerSecond)}/s`);
|
||||
if (state.etaSeconds !== undefined) parts.push(formatEta(state.etaSeconds));
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
const timeFmt = new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { RoleInfo } from '@/core-bridge/types';
|
||||
|
||||
export const ROLE_COLORS = ['gray', 'blue', 'green', 'amber', 'red', 'purple'] as const;
|
||||
export type RoleColor = (typeof ROLE_COLORS)[number];
|
||||
|
||||
/** 与后端 WorkspacePermission 一致 */
|
||||
export const PERMISSIONS = [
|
||||
{ id: 'members.remove', label: '移除成员', hint: '移除成员、撤销邀请;不能移除空间主和同样有此权限的人' },
|
||||
{ id: 'structure.manage', label: '管理频道和分组', hint: '重命名频道、调整频道所属分组、新建和删除分组。新建频道涉及密钥,仍只有空间主' },
|
||||
] as const;
|
||||
|
||||
export interface Badge { key: string; text: string; color: RoleColor | 'owner' }
|
||||
|
||||
const hasText = (value: string | null | undefined): value is string => Boolean(value && value.trim());
|
||||
|
||||
/** 聊天里名字前后要显示的文字:空间主固定一个,其余按身份组的创建顺序 */
|
||||
export function badgesOf(roles: readonly RoleInfo[], username: string | undefined, creator: string | undefined): { prefix: Badge[]; suffix: Badge[] } {
|
||||
const prefix: Badge[] = []; const suffix: Badge[] = [];
|
||||
if (!username) return { prefix, suffix };
|
||||
if (username === creator) suffix.push({ key: 'owner', text: '空间主', color: 'owner' });
|
||||
for (const role of roles) {
|
||||
if (!role.members.includes(username)) continue;
|
||||
const color = (ROLE_COLORS as readonly string[]).includes(role.color ?? '') ? (role.color as RoleColor) : 'gray';
|
||||
if (hasText(role.prefix)) prefix.push({ key: role.id + ':p', text: role.prefix, color });
|
||||
if (hasText(role.suffix)) suffix.push({ key: role.id + ':s', text: role.suffix, color });
|
||||
}
|
||||
return { prefix, suffix };
|
||||
}
|
||||
|
||||
export function permissionsOf(roles: readonly RoleInfo[], username: string | undefined, owner: boolean): ReadonlySet<string> {
|
||||
if (owner) return new Set(PERMISSIONS.map((item) => item.id));
|
||||
const granted = new Set<string>();
|
||||
if (username) for (const role of roles) if (role.members.includes(username)) for (const id of role.permissions) granted.add(id);
|
||||
return granted;
|
||||
}
|
||||
Reference in new issue
Block a user