mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-21 05:10:41 +00:00
4b8cad6d00
- Updated `BackupCenterPage` to support download progress tracking during remote backup downloads. - Modified `ImportPage` to simplify export functionality by removing unnecessary payload handling. - Improved `JwtWarningPage` to utilize a new clipboard utility for copying text with feedback. - Enhanced `PublicSendPage` to show download progress for files being downloaded. - Updated `RecoverTwoFactorPage` to include autocomplete attributes for better user experience. - Refactored `SendsPage` to use the new clipboard utility for copying access URLs. - Enhanced `SettingsPage` to utilize the clipboard utility for copying sensitive information. - Improved `TotpCodesPage` to use the clipboard utility for copying TOTP codes. - Updated `VaultPage` and related components to support download progress for attachments. - Introduced a new `app-notify` module for consistent notification handling across the application. - Created a `clipboard` utility for improved clipboard interactions with user feedback. - Added progress tracking for file downloads in the API layer, enhancing user experience during downloads.
71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
export function downloadBytesAsFile(bytes: Uint8Array, fileName: string, mimeType: string): void {
|
|
const payload = bytes.slice();
|
|
const blob = new Blob([payload], { type: mimeType || 'application/octet-stream' });
|
|
const objectUrl = URL.createObjectURL(blob);
|
|
const anchor = document.createElement('a');
|
|
anchor.href = objectUrl;
|
|
anchor.download = fileName || 'download.bin';
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
anchor.remove();
|
|
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
|
|
}
|
|
|
|
export interface DownloadProgressState {
|
|
loaded: number;
|
|
total: number | null;
|
|
percent: number | null;
|
|
}
|
|
|
|
type ProgressCallback = (progress: DownloadProgressState) => void;
|
|
|
|
function parseContentLength(response: Response): number | null {
|
|
const raw = String(response.headers.get('Content-Length') || '').trim();
|
|
if (!raw) return null;
|
|
const parsed = Number(raw);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
}
|
|
|
|
export async function readResponseBytesWithProgress(
|
|
response: Response,
|
|
onProgress?: ProgressCallback
|
|
): Promise<Uint8Array> {
|
|
const total = parseContentLength(response);
|
|
const report = (loaded: number) => {
|
|
onProgress?.({
|
|
loaded,
|
|
total,
|
|
percent: total ? Math.max(0, Math.min(100, Math.round((loaded / total) * 100))) : null,
|
|
});
|
|
};
|
|
|
|
if (!response.body) {
|
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
report(bytes.byteLength);
|
|
return bytes;
|
|
}
|
|
|
|
const reader = response.body.getReader();
|
|
const chunks: Uint8Array[] = [];
|
|
let loaded = 0;
|
|
report(0);
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
if (!value) continue;
|
|
chunks.push(value);
|
|
loaded += value.byteLength;
|
|
report(loaded);
|
|
}
|
|
|
|
const bytes = new Uint8Array(loaded);
|
|
let offset = 0;
|
|
for (const chunk of chunks) {
|
|
bytes.set(chunk, offset);
|
|
offset += chunk.byteLength;
|
|
}
|
|
report(loaded);
|
|
return bytes;
|
|
}
|