refactor: major package updates

This commit is contained in:
鲁树人
2025-10-16 01:00:38 +09:00
parent 2e52c67533
commit a11caaae92
34 changed files with 373 additions and 240 deletions
+49 -43
View File
@@ -2,83 +2,89 @@ import { DecryptedAudioFile, ProcessState, selectFiles } from '~/features/file-l
import { FaDownload } from 'react-icons/fa';
import { useAppSelector } from '~/hooks';
import { toast } from 'react-toastify';
import { SimpleQueue } from '~/util/SimpleQueue';
export function DownloadAll() {
const files = useAppSelector(selectFiles);
const onClickDownloadAll = async () => {
console.time('DownloadAll'); //开始计时
const fileCount = Object.keys(files).length;
const downloadAllAsync = async () => {
const fileList = Object.values(files);
const fileCount = fileList.length;
if (fileCount === 0) {
toast.warning('未添加文件');
return;
}
//判断所有文件是否处理完成
const allComplete = Object.values(files).every((file) => file.state !== ProcessState.PROCESSING);
// 判断所有文件是否处理完成
const allComplete = fileList.every((file) => file.state !== ProcessState.PROCESSING);
if (!allComplete) {
toast.warning('请等待所有文件解密完成');
return;
}
//过滤处理失败的文件
const completeFiles = Object.values(files).filter((file) => file.state === ProcessState.COMPLETE);
// 过滤处理失败的文件
const completeFiles = fileList.filter((file) => file.state === ProcessState.COMPLETE);
//开始下载
let dir: FileSystemDirectoryHandle | undefined;
// 准备下载
let dir: FileSystemDirectoryHandle | null = null;
try {
dir = await window.showDirectoryPicker({ mode: 'readwrite' });
} catch (e) {
console.error(e);
if (e instanceof Error && e.name === 'AbortError') {
return;
return; // user cancelled
}
console.error(e);
}
toast.warning('开始下载,请稍候');
const queue = new SimpleQueue(8);
const promises = Object.values(completeFiles).map(async (file) => {
console.log(`开始下载: ${file.fileName}`);
try {
if (dir) {
await DownloadNew(dir, file);
} else {
await DownloadOld(file);
}
console.log(`成功下载: ${file.fileName}`);
await queue.enter();
await downloadFile(file, dir);
} catch (e) {
console.error(`下载失败: ${file.fileName}`, e);
toast.error(`出现错误: ${e}`);
toast.error(`出现错误: ${e as Error}`);
throw e;
} finally {
queue.leave();
}
});
await Promise.allSettled(promises).then((f) => {
const success = f.filter((result) => result.status === 'fulfilled').length;
if (success === fileCount) {
toast.success(`成功下载: ${success}/${fileCount}首`);
} else {
toast.warning(`成功下载: ${success}/${fileCount}首`);
}
});
console.timeEnd('DownloadAll'); //停止计时
const promiseResults = await Promise.allSettled(promises);
const success = promiseResults.filter((result) => result.status === 'fulfilled').length;
const level = success === fileCount ? 'success' : success === 0 ? 'error' : 'warning';
toast[level](`成功下载: ${success}/${fileCount}首`);
};
function onDownloadAll() {
downloadAllAsync().catch((e) => {
// this should not happen
console.error('下载全部出现错误', e);
});
}
return (
<button className="btn btn-primary" id="downloadAll" onClick={onClickDownloadAll} title="下载全部">
<button className="btn btn-primary" id="downloadAll" onClick={onDownloadAll} title="下载全部">
<FaDownload />
</button>
);
}
async function DownloadNew(dir: FileSystemDirectoryHandle, file: DecryptedAudioFile) {
const fileHandle = await dir.getFileHandle(file.cleanName + '.' + file.ext, { create: true });
const writable = await fileHandle.createWritable();
await fetch(file.decrypted).then((res) => res.body?.pipeTo(writable));
}
async function DownloadOld(file: DecryptedAudioFile) {
const a = document.createElement('a');
a.href = file.decrypted;
a.download = file.cleanName + '.' + file.ext;
document.body.append(a);
a.click();
a.remove();
async function downloadFile(file: DecryptedAudioFile, dir: FileSystemDirectoryHandle | null) {
if (dir) {
const fileHandle = await dir.getFileHandle(file.cleanName + '.' + file.ext, { create: true });
const fileStream = await fileHandle.createWritable();
try {
const res = await fetch(file.decrypted);
await res.body?.pipeTo(fileStream);
} catch {
await fileStream.abort();
}
} else {
const anchor = document.createElement('a');
anchor.href = file.decrypted;
anchor.download = file.cleanName + '.' + file.ext;
document.body.append(anchor);
anchor.click();
anchor.remove();
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ export type DownloadBase64Props = {
filename: string;
mimetype?: string;
className?: string;
icon?: boolean | ReactNode;
icon?: ReactNode | true | false;
children?: ReactNode;
};
+1 -1
View File
@@ -2,7 +2,7 @@ import type { AnchorHTMLAttributes, ReactNode } from 'react';
import { FiExternalLink } from 'react-icons/fi';
export type ExtLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
icon?: boolean | ReactNode;
icon?: ReactNode | true | false;
};
export function ExtLink({ className, icon = true, children, ...props }: ExtLinkProps) {
+3 -4
View File
@@ -13,13 +13,12 @@ export interface ImportSecretModalProps {
export function ImportSecretModal({ clientName, children, show, onClose, onImport }: ImportSecretModalProps) {
const handleFileReceived = (files: File[]) => {
const promise = onImport(files[0]);
if (promise instanceof Promise) {
promise.catch((err) => {
const importResult = onImport(files[0]);
if (importResult instanceof Promise) {
importResult.catch((err) => {
console.error('could not import: ', err);
});
}
return promise;
};
const refModel = useRef<HTMLDialogElement>(null);
+1 -1
View File
@@ -11,7 +11,7 @@ export function SDKVersion() {
const refDialog = useRef<HTMLDialogElement>(null);
const [sdkVersion, setSdkVersion] = useState('...');
useEffect(() => {
getSDKVersion().then(setSdkVersion);
getSDKVersion().then(setSdkVersion, () => setSdkVersion('N/A'));
}, []);
return (
+4 -1
View File
@@ -27,7 +27,10 @@ export function SelectFile() {
fileName,
}),
);
dispatch(processFile({ fileId }));
dispatch(processFile({ fileId })).catch((err) => {
console.log(`failed to add file (id=${fileId}, name=${fileName}, err=${err as Error})`);
});
}
};