feat: 🎸 dict import update (#1040)

This commit is contained in:
divisey
2024-09-04 15:05:34 +08:00
committed by GitHub
parent e16990a8b4
commit 895d44f60b
9 changed files with 83 additions and 39 deletions

View File

@@ -772,13 +772,14 @@
"removeDictDescription": "It will delete the dictionary file from your local computer and you will have to download it again next time.",
"downloadingDict": "Downloading",
"removeDefault": "No longer as Default",
"selectAdaptionDictTitle": "Select adapted dictionary folder",
"selectAdaptionDictTitle": "Select adapted dictionary",
"selectMdictFileOrDirTitle": "Select dict files (.mdx and optional .mdd) or folder",
"dictImportSlowTip": "It may take longer if the dictionary file is large.",
"dictImportSlowTip": "Checking...(It may take longer if the dictionary file is large.)",
"importAdaptionDict": "Import the adapted dictionary",
"adaptionDictTip": "The adapted dictionaries have better usability.",
"howToDownload": "How to download?",
"selectDir": "Select Folder",
"selectFile": "Select File",
"importMdictFile": "Import the original dictionary file",
"mdictFileTip": "Directly import .mdx .mdd format files (.mdx files are required, .mdd files are optional and can have multiple), but there may be problems with style and usability."
}

View File

@@ -772,13 +772,14 @@
"removeDictDescription": "此操作将会从本地删除词典文件,下次安装需要重新下载",
"downloadingDict": "正在下载",
"removeDefault": "不再设置为默认",
"selectAdaptionDictTitle": "选择预先适配的词典文件",
"selectAdaptionDictTitle": "选择预先适配的词典文件",
"selectMdictFileOrDirTitle": "选择词典文件 (.mdx 和可选的 .mdd 文件) or 文件夹",
"dictImportSlowTip": "词典文件较大时可能需要的时间比较长",
"dictImportSlowTip": "正在检查...词典文件较大时可能需要的时间比较长",
"importAdaptionDict": "导入已经适配好的词典",
"adaptionDictTip": "已经适配好的词典可用性较好。",
"howToDownload": "如何下载?",
"selectDir": "选择文件夹",
"selectFile": "选择文件",
"importMdictFile": "导入原词典文件",
"mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个),不过样式和可用性可能存在问题。"
}

View File

@@ -23,8 +23,8 @@ class Decompresser {
await directory.extract({ path: task.destPath + ".depressing" });
await fs.rename(task.destPath + ".depressing", task.destPath);
await fs.remove(task.filePath);
this.done(task);
this.remove(task);
}
@@ -46,7 +46,7 @@ class Decompresser {
if (currentTask) {
setTimeout(() => {
this.onProgress(task, total);
}, 5000);
}, 1000);
}
}
@@ -73,6 +73,10 @@ class Decompresser {
mainWin.win.webContents.send("decompress-tasks-update", this.tasks);
}
done(task: DecompressTask) {
mainWin.win.webContents.send("decompress-task-done", task);
}
registerIpcHandlers() {
ipcMain.handle("decompress-tasks", () => {
return this.tasks;

View File

@@ -7,6 +7,7 @@ import { DICTS } from "@/constants/dicts";
import sqlite3, { Database } from "sqlite3";
import settings from "./settings";
import { hashFile } from "@/main/utils";
import decompresser from "./decompresser";
const logger = log.scope("dict");
const sqlite = sqlite3.verbose();
@@ -23,17 +24,10 @@ export class DictHandler {
return _path;
}
async import(dir: string) {
const files = await fs.readdir(dir);
async import(_path: string) {
const hash = await hashFile(_path, { algo: "md5" });
const dict = DICTS.find((dict) => dict.hash === hash);
const sqlFileName = files.find((file) => file.match(/\.sqlite$/));
if (!sqlFileName) {
throw new Error("SQLite file not found");
}
const sqlFilePath = path.join(dir, sqlFileName);
const hash = await hashFile(sqlFilePath, { algo: "md5" });
const dict = DICTS.find((dict) => dict.sqlFileHash === hash);
if (!dict) {
throw new Error("SQLite file not match with any perset dictionary");
}
@@ -42,8 +36,12 @@ export class DictHandler {
throw new Error("Current dict is already installed");
}
await fs.copy(dir, path.join(this.dictsPath, dict.name), {
recursive: true,
decompresser.depress({
id: `dict-${dict.name}`,
type: "dict",
title: dict.title,
filePath: _path,
destPath: path.join(this.dictsPath, dict.name),
});
}

View File

@@ -557,12 +557,17 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
},
},
decompress: {
onComplete: (
callback: (event: IpcRendererEvent, task: DecompressTask) => void
) => ipcRenderer.on("decompress-task-done", callback),
onUpdate: (
callback: (event: IpcRendererEvent, tasks: DecompressTask[]) => void
) => ipcRenderer.on("decompress-tasks-update", callback),
dashboard: () => ipcRenderer.invoke("decompress-tasks"),
removeAllListeners: () =>
ipcRenderer.removeAllListeners("decompress-tasks-update"),
removeAllListeners: () => {
ipcRenderer.removeAllListeners("decompress-tasks-update");
ipcRenderer.removeAllListeners("decompress-tasks-done");
},
},
download: {
onState: (

View File

@@ -1,4 +1,4 @@
import { useState, useContext } from "react";
import { useState, useContext, useEffect } from "react";
import {
AppSettingsProviderContext,
DictProviderContext,
@@ -20,7 +20,6 @@ export const DictImportButton = () => {
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [tipVisible, setTipVisible] = useState(false);
const handleOpen = (value: boolean) => {
setOpen(value);
@@ -29,17 +28,12 @@ export const DictImportButton = () => {
const handleAdaptationDictImport = async () => {
const pathes = await EnjoyApp.dialog.showOpenDialog({
title: t("selectAdaptionDictTitle"),
properties: ["openDirectory"],
properties: ["openFile"],
filters: [{ name: "zip", extensions: [".zip"] }],
});
if (!pathes[0]) return;
setLoading(true);
setTimeout(() => {
if (loading) {
setTipVisible(true);
}
}, 10000);
try {
await EnjoyApp.dict.import(pathes[0]);
@@ -49,7 +43,6 @@ export const DictImportButton = () => {
}
setLoading(false);
setTipVisible(false);
reload();
};
@@ -75,11 +68,9 @@ export const DictImportButton = () => {
<div className="px-4 py-4 flex justify-center items-center">
<LoaderIcon className="text-muted-foreground animate-spin" />
</div>
{tipVisible && (
<div className="text-xs text-center text-muted-foreground mb-8">
{t("dictImportSlowTip")}
</div>
)}
<div className="text-xs text-center text-muted-foreground mb-8">
{t("dictImportSlowTip")}
</div>
</div>
) : (
<div>
@@ -102,7 +93,7 @@ export const DictImportButton = () => {
</div>
<Button size="sm" onClick={handleAdaptationDictImport}>
{t("selectDir")}
{t("selectFile")}
</Button>
</div>

View File

@@ -17,15 +17,34 @@ import {
AlertDialogAction,
} from "@renderer/components/ui";
import { t } from "i18next";
import { LoaderIcon } from "lucide-react";
export const InstalledDictList = function () {
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const { installedDicts, reload } = useContext(DictProviderContext);
const [tasks, setTasks] = useState([]);
useEffect(() => {
reload();
EnjoyApp.decompress.dashboard().then((_tasks) => {
setTasks(_tasks.filter((_task) => _task.type === "dict"));
});
EnjoyApp.decompress.onComplete((_, task) => {
if (task.type === "dict") reload();
});
EnjoyApp.decompress.onUpdate((_, _tasks) => {
setTasks(_tasks.filter((_task) => _task.type === "dict"));
});
return () => {
EnjoyApp.decompress.removeAllListeners();
};
}, []);
if (installedDicts.length === 0) {
if (installedDicts.length === 0 && tasks.length === 0) {
return (
<div className="text-sm text-muted-foreground">{t("dictEmpty")}</div>
);
@@ -33,6 +52,10 @@ export const InstalledDictList = function () {
return (
<>
{tasks.map((task) => (
<DecompressDictItem key={task.id} task={task} />
))}
{installedDicts.map((item) => (
<InstalledDictItem key={item.name} dict={item} />
))}
@@ -40,6 +63,22 @@ export const InstalledDictList = function () {
);
};
const DecompressDictItem = function ({ task }: { task: DecompressTask }) {
return (
<div key={task.id} className="flex justify-between items-center group">
<div className="flex items-center text-sm text-left h-8 hover:opacity-80">
<span className="mr-2">{task.title}</span>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>{t("decompressing")}</span>
<span>{task.progress || 0}%</span>
<LoaderIcon className="w-4 h-4 text-muted-foreground animate-spin" />
</div>
</div>
);
};
const InstalledDictItem = function ({ dict }: { dict: Dict }) {
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const { settings, setDefault, reload, remove, removed } =
@@ -89,7 +128,10 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) {
function renderActions() {
if (removing) {
return (
<span className="text-sm text-muted-foreground">{t("removing")}</span>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">{t("removing")}</span>
<LoaderIcon className="w-4 h-4 text-muted-foreground animate-spin" />
</div>
);
}

View File

@@ -317,6 +317,7 @@ type EnjoyAppType = {
) => Promise<string>;
};
decompress: {
onComplete: (callback: (event, task: DecompressTask) => void) => void;
onUpdate: (callback: (event, tasks: DecompressTask[]) => void) => void;
dashboard: () => Promise<DecompressTask[]>;
removeAllListeners: () => void;

View File

@@ -27,9 +27,10 @@ type DownloadStateType = {
type DecompressTask = {
id: string;
type: string;
title: string;
filePath: string;
destPath: string;
hash: string;
progress?: string;
};