polish canvas toolbar and upload stall handling

This commit is contained in:
134 2026-07-08 20:29:46 +08:00
parent 05b21707c1
commit 7ed0f08269
2 changed files with 34 additions and 1 deletions

View File

@ -10193,7 +10193,7 @@ export function ImageNode({
createPortal(
<TooltipProvider delayDuration={200}>
<div
className="fixed z-[9999] flex items-center gap-0.5 rounded-[24px] border border-slate-200 bg-white px-2 py-1.5 text-gray-800 shadow-[0_14px_38px_rgba(15,23,42,0.16)] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100 dark:shadow-[0_16px_44px_rgba(0,0,0,0.45)]"
className="fixed z-[9999] flex items-center gap-0.5 rounded-[24px] border border-white/70 bg-white/82 px-2 py-1.5 text-gray-800 shadow-[0_18px_52px_rgba(15,23,42,0.20)] backdrop-blur-2xl before:pointer-events-none before:absolute before:inset-[-10px] before:-z-10 before:rounded-[32px] before:bg-white/35 before:shadow-[0_18px_64px_rgba(15,23,42,0.18)] before:backdrop-blur-2xl before:content-[''] supports-[backdrop-filter]:bg-white/72 dark:border-white/12 dark:bg-[#08090c]/72 dark:text-zinc-100 dark:shadow-[0_24px_72px_rgba(0,0,0,0.58)] dark:before:bg-black/35 dark:before:shadow-[0_24px_80px_rgba(0,0,0,0.62)] supports-[backdrop-filter]:dark:bg-[#08090c]/62"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}

View File

@ -7,6 +7,8 @@ const SERVER_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
const DIRECT_UPLOAD_PART_CONCURRENCY = 4;
const DIRECT_UPLOAD_SUPPRESS_MS = 2 * 60 * 1000;
const CLIENT_UPLOAD_ID_FIELD = "clientUploadId";
const XHR_UPLOAD_TOTAL_TIMEOUT_MS = 6 * 60 * 1000;
const XHR_UPLOAD_STALL_TIMEOUT_MS = 30 * 1000;
const RETRYABLE_UPLOAD_STATUSES = new Set([
408,
@ -388,6 +390,8 @@ function xhrUpload(
options?: {
signal?: AbortSignal;
onProgress?: (loaded: number, total: number) => void;
timeoutMs?: number;
stallTimeoutMs?: number;
}
) {
return new Promise<Response>((resolve, reject) => {
@ -398,10 +402,18 @@ function xhrUpload(
const xhr = new XMLHttpRequest();
let settled = false;
let stallTimer: ReturnType<typeof setTimeout> | null = null;
const clearStallTimer = () => {
if (!stallTimer) return;
clearTimeout(stallTimer);
stallTimer = null;
};
const settle = (callback: () => void) => {
if (settled) return;
settled = true;
clearStallTimer();
options?.signal?.removeEventListener("abort", onAbort);
callback();
};
@ -412,12 +424,32 @@ function xhrUpload(
};
xhr.open(init.method, url, true);
xhr.timeout = options?.timeoutMs ?? XHR_UPLOAD_TOTAL_TIMEOUT_MS;
for (const [key, value] of normalizeXhrHeaders(init.headers)) {
xhr.setRequestHeader(key, value);
}
const resetStallTimer = () => {
clearStallTimer();
if (settled) return;
const stallTimeoutMs =
options?.stallTimeoutMs ?? XHR_UPLOAD_STALL_TIMEOUT_MS;
if (stallTimeoutMs <= 0) return;
stallTimer = setTimeout(() => {
const error = new Error(
`Upload stalled for ${Math.round(stallTimeoutMs / 1000)}s`
);
error.name = "TimeoutError";
settle(() => reject(error));
xhr.abort();
}, stallTimeoutMs);
};
xhr.upload.onprogress = (event) => {
resetStallTimer();
const total = event.lengthComputable ? event.total : 0;
options?.onProgress?.(event.loaded, total);
};
@ -435,6 +467,7 @@ function xhrUpload(
};
options?.signal?.addEventListener("abort", onAbort, { once: true });
resetStallTimer();
xhr.send(init.body);
});
}