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
@@ -8,7 +8,7 @@ const text = (id: string, ts: number, body = id): TextItem => ({
|
||||
|
||||
describe('reduce', () => {
|
||||
it('renders server structure and preserves server author and location across late decrypted content', () => {
|
||||
const stateEvent = { t: 'structure' as const, state: { revision: 1,
|
||||
const stateEvent = { t: 'structure' as const, state: { revision: 1, roles: [], rolesSeeded: true,
|
||||
channels: [{ id: 'channel-a', name: '常规', groupId: 'group-a', createdAt: 1, createdBy: 'alice', chatEpoch: 1, filesEpoch: 1, chatHistory: true, filesHistory: true, fileBytes: 0 }],
|
||||
groups: [{ id: 'group-a', name: '项目', createdAt: 1 }],
|
||||
folders: [{ id: 'folder-a', name: '目录', threadId: 'channel-a' }],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ChannelInfo,
|
||||
ChannelGroupInfo,
|
||||
RoleInfo,
|
||||
ConnectionState,
|
||||
FolderInfo,
|
||||
Item,
|
||||
@@ -16,6 +17,7 @@ export interface WorkspaceData {
|
||||
channel: ChannelInfo | null;
|
||||
threads: ThreadInfo[];
|
||||
groups: ChannelGroupInfo[];
|
||||
roles: RoleInfo[];
|
||||
folders: FolderInfo[];
|
||||
locations: Record<string, { threadId: string; folderId?: string; authorAccount?: string }>;
|
||||
byId: Record<string, Item>;
|
||||
@@ -34,6 +36,7 @@ export const initialData: WorkspaceData = {
|
||||
channel: null,
|
||||
threads: [],
|
||||
groups: [],
|
||||
roles: [],
|
||||
folders: [],
|
||||
locations: {},
|
||||
byId: {},
|
||||
@@ -71,7 +74,7 @@ export function reduce(s: WorkspaceData, e: WorkspaceEvent): WorkspaceData {
|
||||
...item, ...(locations[id] ?? {}), ...(authors[id] ? { author: authors[id] } : {}),
|
||||
}]));
|
||||
const profiles = Object.fromEntries(e.people.map(person => [person.username, { username: person.username, displayName: person.displayName || person.username, ...(person.avatar ? { avatar: person.avatar } : {}) }]));
|
||||
return { ...s, people: profiles, threads: e.state.channels.map(c => ({ ...c, groupId: c.groupId ?? undefined })), groups: e.state.groups,
|
||||
return { ...s, people: profiles, threads: e.state.channels.map(c => ({ ...c, groupId: c.groupId ?? undefined })), groups: e.state.groups, roles: e.state.roles ?? [],
|
||||
folders: e.state.folders.map(f => ({ ...f, parentId: f.parentId ?? undefined })), locations, authors, byId };
|
||||
}
|
||||
case 'reset':
|
||||
|
||||
@@ -44,7 +44,8 @@ function settleLocal(id: string): void {
|
||||
async function deliver(id: string): Promise<void> {
|
||||
const job = pendingJobs.get(id);
|
||||
if (!job) return;
|
||||
useWorkspace.setState((s) => s.byId[id] ? { byId: { ...s.byId, [id]: { ...s.byId[id], state: { kind: 'receiving', progress: 0 } } } } : s);
|
||||
const phase = job.kind === 'file' ? 'queued' as const : undefined;
|
||||
useWorkspace.setState((s) => s.byId[id] ? { byId: { ...s.byId, [id]: { ...s.byId[id], state: { kind: 'receiving', progress: 0, ...(phase ? { phase } : {}) } } } } : s);
|
||||
try {
|
||||
if (job.kind === 'text') await api.sendText(job.text, job.threadId, id);
|
||||
else await api.sendFiles([job.file], job.options, job.threadId, job.folderId, [id]);
|
||||
@@ -61,12 +62,18 @@ interface Actions {
|
||||
sendText: (text: string, threadId?: string) => Promise<void>;
|
||||
/** 重试发送失败的本地占位项 */
|
||||
retrySend: (localId: string) => Promise<void>;
|
||||
/** 取消排队中或上传中的文件 */
|
||||
cancelUpload: (localId: string) => Promise<void>;
|
||||
updateChannelSettings: (patch: ChannelSettingsPatch) => Promise<void>;
|
||||
sendFiles: (files: File[], options?: FileUploadOptions, threadId?: string, folderId?: string) => Promise<void>;
|
||||
createThread: (name: string, groupId?: string) => Promise<string>;
|
||||
createGroup: typeof api.createGroup;
|
||||
renameGroup: typeof api.renameGroup;
|
||||
deleteGroup: typeof api.deleteGroup;
|
||||
createRole: typeof api.createRole;
|
||||
updateRole: typeof api.updateRole;
|
||||
deleteRole: typeof api.deleteRole;
|
||||
setRoleMembers: typeof api.setRoleMembers;
|
||||
groupThread: typeof api.groupThread;
|
||||
renameThread: (threadId: string, name: string) => Promise<void>;
|
||||
setChannelHistory: typeof api.setChannelHistory;
|
||||
@@ -98,22 +105,32 @@ export const useWorkspace = create<WorkspaceData & Actions>()(() => ({
|
||||
return deliver(id);
|
||||
},
|
||||
retrySend: (id) => deliver(id),
|
||||
cancelUpload: async (localId) => {
|
||||
pendingJobs.delete(localId);
|
||||
useWorkspace.setState((s) => withoutItem(s, localId));
|
||||
await api.cancelUpload(localId).catch(() => false);
|
||||
},
|
||||
updateChannelSettings: (patch) => api.updateChannelSettings(patch),
|
||||
sendFiles: async (files, options, threadId, folderId) => {
|
||||
const ids = files.map((file) => {
|
||||
const id = newLocalId();
|
||||
pendingJobs.set(id, { kind: 'file', file, ...(options ? { options } : {}), ...(threadId ? { threadId } : {}), ...(folderId ? { folderId } : {}) });
|
||||
const item: FileItem = { id, kind: 'file', localId: id, name: file.name, mime: file.type || 'application/octet-stream', size: file.size, remaining: null,
|
||||
preview: previewKindOf(file.type, file.name), ...localBase(threadId, folderId), state: { kind: 'receiving', progress: 0, phase: 'encrypting' } };
|
||||
preview: previewKindOf(file.type, file.name), ...localBase(threadId, folderId), state: { kind: 'receiving', progress: 0, phase: 'queued' } };
|
||||
putLocal(item);
|
||||
return id;
|
||||
});
|
||||
for (const id of ids) await deliver(id);
|
||||
// 全部交给 worker 的上传队列:一次传一个,其余显示「排队中」
|
||||
await Promise.all(ids.map(deliver));
|
||||
},
|
||||
createThread: (name, groupId) => api.createThread(name, groupId),
|
||||
createGroup: (name) => api.createGroup(name),
|
||||
renameGroup: (id, name) => api.renameGroup(id, name),
|
||||
deleteGroup: (id) => api.deleteGroup(id),
|
||||
createRole: (role) => api.createRole(role),
|
||||
updateRole: (id, role) => api.updateRole(id, role),
|
||||
deleteRole: (id) => api.deleteRole(id),
|
||||
setRoleMembers: (id, members) => api.setRoleMembers(id, members),
|
||||
groupThread: (id, groupId) => api.groupThread(id, groupId),
|
||||
renameThread: (threadId, name) => api.renameThread(threadId, name),
|
||||
setChannelHistory: (threadId, chatHistory, filesHistory) => api.setChannelHistory(threadId, chatHistory, filesHistory),
|
||||
|
||||
Reference in new issue
Block a user