feat: 🎸 dict import update (#1040)
This commit is contained in:
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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 文件是可选的且可以有多个),不过样式和可用性可能存在问题。"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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: (
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1
enjoy/src/types/enjoy-app.d.ts
vendored
1
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -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;
|
||||
|
||||
3
enjoy/src/types/index.d.ts
vendored
3
enjoy/src/types/index.d.ts
vendored
@@ -27,9 +27,10 @@ type DownloadStateType = {
|
||||
|
||||
type DecompressTask = {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
filePath: string;
|
||||
destPath: string;
|
||||
hash: string;
|
||||
progress?: string;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user