fix(web): make cancel, delete and chat work while a file is uploading [CI]
This commit is contained in:
1 parent
1245aedfd2
commit
7ef298f587
4 files changed
+34
-6
No files matched your search
@@ -109,9 +109,26 @@ async function dispatch(operation: string, input: unknown): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号和空间的操作按顺序执行(登录、同步、改设置等互相依赖)。但上传、发消息、删除、取消是长任务或互不依赖:
|
||||||
|
* 如果也排在这条队列里,一个大文件的 sendFiles 会让取消按钮、聊天消息、删除全部等到上传结束才生效。
|
||||||
|
*/
|
||||||
|
function laneOf(operation: string, payload: Record<string, unknown>): string | undefined {
|
||||||
|
const space = String(payload.id ?? '');
|
||||||
|
switch (operation) {
|
||||||
|
case 'cancelUpload': return undefined;
|
||||||
|
case 'sendFiles': return `upload:${space}`;
|
||||||
|
case 'sendText': case 'editChannelText': return `chat:${space}`;
|
||||||
|
case 'removeChannelItem': return `item:${space}`;
|
||||||
|
default: return 'account-scope';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scope.onmessage = (event) => {
|
scope.onmessage = (event) => {
|
||||||
const request = event.data;
|
const request = event.data;
|
||||||
const task = serial('account-scope', () => dispatch(request.operation, request.payload));
|
const lane = laneOf(request.operation, request.payload as Record<string, unknown>);
|
||||||
|
const run = () => dispatch(request.operation, request.payload);
|
||||||
|
const task = lane ? serial(lane, run) : run();
|
||||||
void task.then(
|
void task.then(
|
||||||
(result) => scope.postMessage({ requestId: request.requestId, result }),
|
(result) => scope.postMessage({ requestId: request.requestId, result }),
|
||||||
(error: unknown) => scope.postMessage({
|
(error: unknown) => scope.postMessage({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { dequeueFrame, dequeueItem, queueFrame, queueLocation, saveResource, typ
|
|||||||
import { errorMessage } from '../errors';
|
import { errorMessage } from '../errors';
|
||||||
import type { FileItem, FileUploadOptions } from '../types';
|
import type { FileItem, FileUploadOptions } from '../types';
|
||||||
import { prepareObject } from './channelDataKeys';
|
import { prepareObject } from './channelDataKeys';
|
||||||
|
import { removeChannelItem } from './channelItems';
|
||||||
import { queueFileCatalog } from './channelThreads';
|
import { queueFileCatalog } from './channelThreads';
|
||||||
import { deviceIdentity, publishWorkspace } from './presentation';
|
import { deviceIdentity, publishWorkspace } from './presentation';
|
||||||
import { senderSnapshot } from './senderSnapshot';
|
import { senderSnapshot } from './senderSnapshot';
|
||||||
@@ -160,6 +161,8 @@ async function discard(resource: SavedResource, item: FileItem | undefined): Pro
|
|||||||
await saveResource(resource);
|
await saveResource(resource);
|
||||||
blobs.delete(`${resource.id}:${item.id}`);
|
blobs.delete(`${resource.id}:${item.id}`);
|
||||||
publishWorkspace(resource.id, { t: 'removed', id: item.id });
|
publishWorkspace(resource.id, { t: 'removed', id: item.id });
|
||||||
|
// 服务端可能已收下前几帧:删掉这个没传完的条目,不然它会一直占着空间配额(没传完的条目服务端允许直接删)。第 0 帧都没到服务端时返回 404,忽略
|
||||||
|
void removeChannelItem(resource.id, item.id).catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function ChatMessage({ id, compact = false, onOpenFile, onDelete }: Props
|
|||||||
<Button variant="ghost" icon="eye" label="预览" disabled={!ready} onClick={() => onOpenFile(id)} />
|
<Button variant="ghost" icon="eye" label="预览" disabled={!ready} onClick={() => onOpenFile(id)} />
|
||||||
<Button variant="ghost" icon="download" label={downloading ? '下载中…' : '下载'} disabled={!ready || downloading} onClick={() => { void save(); }} />
|
<Button variant="ghost" icon="download" label={downloading ? '下载中…' : '下载'} disabled={!ready || downloading} onClick={() => { void save(); }} />
|
||||||
</div>}
|
</div>}
|
||||||
{item.state.kind === 'receiving' && item.kind === 'file' ? <TransferProgress state={item.state} {...(self && item.localId ? { onCancel: () => { void cancelUpload(item.localId!); } } : {})} />
|
{item.state.kind === 'receiving' && item.kind === 'file' ? <TransferProgress state={item.state} {...(self && item.localId ? { onCancel: () => { void cancelUpload(item.localId!, item.id); } } : {})} />
|
||||||
: failed ? <span className={styles.failure} role="alert">发送失败。<button type="button" onClick={() => { void retry(id); }}>重试</button></span>
|
: failed ? <span className={styles.failure} role="alert">发送失败。<button type="button" onClick={() => { void retry(id); }}>重试</button></span>
|
||||||
: item.state.kind === 'incomplete' ? <span className={styles.delivery}>发送未完成,等待续传。</span> : null}
|
: item.state.kind === 'incomplete' ? <span className={styles.delivery}>发送未完成,等待续传。</span> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ interface Actions {
|
|||||||
/** 重试发送失败的本地占位项 */
|
/** 重试发送失败的本地占位项 */
|
||||||
retrySend: (localId: string) => Promise<void>;
|
retrySend: (localId: string) => Promise<void>;
|
||||||
/** 取消排队中或上传中的文件 */
|
/** 取消排队中或上传中的文件 */
|
||||||
cancelUpload: (localId: string) => Promise<void>;
|
cancelUpload: (localId: string, itemId?: string) => Promise<void>;
|
||||||
updateChannelSettings: (patch: ChannelSettingsPatch) => Promise<void>;
|
updateChannelSettings: (patch: ChannelSettingsPatch) => Promise<void>;
|
||||||
sendFiles: (files: File[], options?: FileUploadOptions, threadId?: string, folderId?: string) => Promise<void>;
|
sendFiles: (files: File[], options?: FileUploadOptions, threadId?: string, folderId?: string) => Promise<void>;
|
||||||
createThread: (name: string, groupId?: string) => Promise<string>;
|
createThread: (name: string, groupId?: string) => Promise<string>;
|
||||||
@@ -105,10 +105,12 @@ export const useWorkspace = create<WorkspaceData & Actions>()(() => ({
|
|||||||
return deliver(id);
|
return deliver(id);
|
||||||
},
|
},
|
||||||
retrySend: (id) => deliver(id),
|
retrySend: (id) => deliver(id),
|
||||||
cancelUpload: async (localId) => {
|
cancelUpload: async (localId, itemId) => {
|
||||||
pendingJobs.delete(localId);
|
pendingJobs.delete(localId);
|
||||||
useWorkspace.setState((s) => withoutItem(s, localId));
|
useWorkspace.setState((s) => withoutItem(s, localId));
|
||||||
await api.cancelUpload(localId).catch(() => false);
|
const found = await api.cancelUpload(localId).catch(() => false);
|
||||||
|
// worker 里没有这个任务(上次会话遗留的半截上传):当普通条目删除
|
||||||
|
if (!found && itemId) { useWorkspace.setState((s) => withoutItem(s, itemId)); await api.remove(itemId).catch(() => undefined); }
|
||||||
},
|
},
|
||||||
updateChannelSettings: (patch) => api.updateChannelSettings(patch),
|
updateChannelSettings: (patch) => api.updateChannelSettings(patch),
|
||||||
sendFiles: async (files, options, threadId, folderId) => {
|
sendFiles: async (files, options, threadId, folderId) => {
|
||||||
@@ -139,7 +141,13 @@ export const useWorkspace = create<WorkspaceData & Actions>()(() => ({
|
|||||||
moveItems: (itemIds, threadId, folderId) => api.moveItems(itemIds, threadId, folderId),
|
moveItems: (itemIds, threadId, folderId) => api.moveItems(itemIds, threadId, folderId),
|
||||||
editText: (id, t): Promise<void> => api.editText(id, t, useWorkspace.getState().byId[id]?.threadId),
|
editText: (id, t): Promise<void> => api.editText(id, t, useWorkspace.getState().byId[id]?.threadId),
|
||||||
resolveConflict: (id, c) => api.resolveConflict(id, c),
|
resolveConflict: (id, c) => api.resolveConflict(id, c),
|
||||||
remove: (id) => { if (!pendingJobs.has(id)) return api.remove(id); settleLocal(id); return Promise.resolve(); },
|
remove: async (id) => {
|
||||||
|
if (pendingJobs.has(id)) { settleLocal(id); await api.cancelUpload(id).catch(() => false); return; }
|
||||||
|
const item = useWorkspace.getState().byId[id];
|
||||||
|
// 还在上传的文件:删除就是取消(先停掉上传,否则后面的帧会打到已删除的条目上)
|
||||||
|
if (item?.kind === 'file' && item.state.kind === 'receiving') { await useWorkspace.getState().cancelUpload(item.localId ?? id, id); return; }
|
||||||
|
await api.remove(id);
|
||||||
|
},
|
||||||
setSnapshot: (t) => api.setSnapshot(t),
|
setSnapshot: (t) => api.setSnapshot(t),
|
||||||
getBlob: (id, v) => api.getBlob(id, v),
|
getBlob: (id, v) => api.getBlob(id, v),
|
||||||
markSeen: (id) => api.markSeen(id),
|
markSeen: (id) => api.markSeen(id),
|
||||||
|
|||||||
Reference in new issue
Block a user