diff --git a/enjoy/src/constants/dicts.ts b/enjoy/src/constants/dicts.ts index f55a686b..b6133d6d 100644 --- a/enjoy/src/constants/dicts.ts +++ b/enjoy/src/constants/dicts.ts @@ -8,6 +8,7 @@ export const DICTS = [ downloadUrl: "https://dl.enjoy.bot/dicts/ccalecd.zip", size: "13.879MB", hash: "96940f85e52df4586b287e1859723a39", + sqlFileHash: "e1e7baafaa8bce936409763d27a027f8", addition: '', }, @@ -20,6 +21,7 @@ export const DICTS = [ downloadUrl: "https://dl.enjoy.bot/dicts/ccabeld.zip", size: "485.6MB", hash: "5b53498536f3ce3ed173752b7888ca51", + sqlFileHash: "0eed5c046a006f3fe0c7a4af1bee5da1", addition: '', }, { @@ -31,6 +33,7 @@ export const DICTS = [ downloadUrl: "https://dl.enjoy.bot/dicts/ldoce5.zip", size: "1.63GB", hash: "4a03ce291ea7b6e0ea46f4c2fc335ad4", + sqlFileHash: "b38a9e55bd97c5acfa267b87c825baf7", addition: '', }, { @@ -41,6 +44,7 @@ export const DICTS = [ lang: "En-En", downloadUrl: "https://dl.enjoy.bot/dicts/oxford_en_mac.zip", hash: "cffaef4b3ed6ec7d3ee7209b18e05c6f", + sqlFileHash: "3362137bf8e2e2578665db3ad8d49814", size: "33.6MB", addition: '', }, @@ -53,6 +57,7 @@ export const DICTS = [ downloadUrl: "https://dl.enjoy.bot/dicts/koen_mac.zip", size: "52.1MB", hash: "fa028c585de10e54a7028c6683738499", + sqlFileHash: "e55f771e6acd50d757428fe61f13650e", addition: '', }, { @@ -63,6 +68,7 @@ export const DICTS = [ lang: "Ja-En", downloadUrl: "https://dl.enjoy.bot/dicts/jaen_mac.zip", hash: "3008e1cd2a8b6f224f90d14a8e1de9cb", + sqlFileHash: "76597b8608ba085b9b88adce10449bf3", size: "39.8MB", addition: '', }, @@ -74,6 +80,7 @@ export const DICTS = [ lang: "Ge-En", downloadUrl: "https://dl.enjoy.bot/dicts/deen_mac.zip", hash: "3fedde07108236f6e6cfe907bd60faba", + sqlFileHash: "84b3097bfa83cdae8c264ca595923101", size: "32.1MB", addition: '', }, @@ -85,6 +92,7 @@ export const DICTS = [ lang: "Ru-En", downloadUrl: "https://dl.enjoy.bot/dicts/ruen_mac.zip", hash: "5b98fc0e5c3de9df43189cb79d5bf4cc", + sqlFileHash: "1d72eadd51d82d48b3c772e5580d3524", size: "18.1MB", addition: '', }, diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 9a89cbb3..12dfa491 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -744,7 +744,7 @@ "recorderConfig": "Recorder config", "recorderConfigSaved": "Recorder config saved", "recorderConfigDescription": "Advanced settings for recorder", - "lookupOnMouseOver": "Lookup On MouseOver", + "lookupOnMouseOver": "Lookup On Click", "selectDictFile": "Select Dict Files (extension with .mdx and .mdd files)", "dictFiles": "Dict Files", "dictFileRequired": "Dict file (the extension is mdx) not found.", @@ -771,5 +771,14 @@ "removeDictTitle": "Are you sure you want to delete this dictionary? ", "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" + "removeDefault": "No longer as Default", + "selectAdaptionDictTitle": "Select adapted dictionary folder", + "selectMdictFileOrDirTitle": "Select dict files (.mdx and optional .mdd) or folder", + "dictImportSlowTip": "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", + "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." } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index a51cc072..5af46464 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -744,7 +744,7 @@ "recorderConfig": "录音设置", "recorderConfigSaved": "录音设置已保存", "recorderConfigDescription": "调整录音高级设置", - "lookupOnMouseOver": "鼠标悬停查询单词", + "lookupOnMouseOver": "鼠标点击查询单词", "selectDictFile": "选择字典文件 (扩展名为 .mdx 和 .mdd 的文件)", "dictFiles": "字典文件", "dictFileRequired": "未找到字典文件 (扩展名为 .mdx 的文件是必须的) ", @@ -771,5 +771,14 @@ "removeDictTitle": "你确定要删除词典吗?", "removeDictDescription": "此操作将会从本地删除词典文件,下次安装需要重新下载", "downloadingDict": "正在下载", - "removeDefault": "不再设置为默认" + "removeDefault": "不再设置为默认", + "selectAdaptionDictTitle": "选择预先适配的词典文件夹", + "selectMdictFileOrDirTitle": "选择词典文件 (.mdx 和可选的 .mdd 文件) or 文件夹", + "dictImportSlowTip": "词典文件较大时可能需要的时间比较长", + "importAdaptionDict": "导入已经适配好的词典", + "adaptionDictTip": "已经适配好的词典可用性较好。", + "howToDownload": "如何下载?", + "selectDir": "选择文件夹", + "importMdictFile": "导入原词典文件", + "mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个),不过样式和可用性可能存在问题。" } diff --git a/enjoy/src/index.css b/enjoy/src/index.css index f8e1396a..88aca05f 100644 --- a/enjoy/src/index.css +++ b/enjoy/src/index.css @@ -37,6 +37,7 @@ --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 10% 3.9%; + --active-word: 201 94% 86%; --radius: 0.5rem; } @@ -69,6 +70,7 @@ --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --ring: 240 4.9% 83.9%; + --active-word: 201 93% 30%; } } diff --git a/enjoy/src/main/dict.ts b/enjoy/src/main/dict.ts index 24578a54..a120732c 100644 --- a/enjoy/src/main/dict.ts +++ b/enjoy/src/main/dict.ts @@ -6,8 +6,6 @@ import log from "@main/logger"; import { DICTS } from "@/constants/dicts"; import sqlite3, { Database } from "sqlite3"; import settings from "./settings"; -import downloader from "./downloader"; -import decompresser from "./decompresser"; import { hashFile } from "@/main/utils"; const logger = log.scope("dict"); @@ -25,54 +23,28 @@ export class DictHandler { return _path; } - async isDictFileValid(dict: Dict) { - const filePath = path.join(this.dictsPath, dict.fileName); + async import(dir: string) { + const files = await fs.readdir(dir); - if (!fs.existsSync(filePath)) return false; - - const hash = await hashFile(filePath, { algo: "md5" }); - - return hash === dict.hash; - } - - async download(dict: Dict) { - const filePath = path.join(this.dictsPath, dict.fileName); - const dictPath = path.join(this.dictsPath, dict.name); - - if (fs.existsSync(dictPath)) { - throw new Error("Dictionary already exists"); + const sqlFileName = files.find((file) => file.match(/\.sqlite$/)); + if (!sqlFileName) { + throw new Error("SQLite file not found"); } - const isDictFileValid = await this.isDictFileValid(dict); - - if (isDictFileValid) { - this.decompress(dict); - } else { - if (fs.existsSync(filePath)) { - await fs.remove(filePath); - } - - downloader.download(dict.downloadUrl, { - savePath: this.dictsPath, - }); - } - } - - async decompress(dict: Dict) { - const filePath = path.join(this.dictsPath, dict.fileName); - const dictPath = path.join(this.dictsPath, dict.name); - const isDictFileValid = await this.isDictFileValid(dict); - - if (isDictFileValid) { - await decompresser.depress({ - filePath, - hash: dict.hash, - destPath: dictPath, - id: `dict-${dict.fileName}`, - }); + 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"); } - downloader.remove(dict.fileName); + if (this.isInstalled(dict)) { + throw new Error("Current dict is already installed"); + } + + await fs.copy(dir, path.join(this.dictsPath, dict.name), { + recursive: true, + }); } async remove(dict: Dict) { @@ -84,6 +56,7 @@ export class DictHandler { this.db = new sqlite.Database( path.join(this.dictsPath, dict.name, `${dict.name}.sqlite`) ); + this.currentDict = dict.name; } @@ -104,41 +77,17 @@ export class DictHandler { }); } + isInstalled(dict: Dict) { + const files = fs.readdirSync(this.dictsPath); + return files.find((file) => file === dict.name); + } + async getDicts() { const dicts = DICTS.map((dict: Dict) => { - let state: DictState = "uninstall"; - let downloadState; - let decompressProgress; - - const files = fs.readdirSync(this.dictsPath); - const isInstalled = files.find((file) => file === dict.name); - - const decompressTask = decompresser.tasks.find( - (task) => task.id === `dict-${dict.fileName}` - ); - - const downloadTask = downloader.tasks.find( - (task) => task.getFilename() === dict.fileName - ); - - if (decompressTask) { - state = "decompressing"; - decompressProgress = decompressTask.progress; - } else if (isInstalled) { - state = "installed"; - } else if (downloadTask) { - state = "downloading"; - downloadState = { - name: downloadTask.getFilename(), - state: downloadTask.getState(), - isPaused: downloadTask.isPaused(), - canResume: downloadTask.canResume(), - total: downloadTask.getTotalBytes(), - received: downloadTask.getReceivedBytes(), - }; - } - - return { ...dict, state, downloadState, decompressProgress }; + return { + ...dict, + state: this.isInstalled(dict) ? "installed" : "uninstall", + }; }); return dicts; @@ -161,12 +110,8 @@ export class DictHandler { } registerIpcHandlers() { - ipcMain.handle("dict-download", async (_event, dict: Dict) => - this.download(dict) - ); - - ipcMain.handle("dict-decompress", async (_event, dict: Dict) => - this.decompress(dict) + ipcMain.handle("dict-import", async (_event, dir: string) => + this.import(dir) ); ipcMain.handle("dict-remove", async (_event, dict: Dict) => diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 017241b8..9f7a8384 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -281,13 +281,12 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { }, dict: { getDicts: () => ipcRenderer.invoke("dict-list"), - download: (dict: Dict) => ipcRenderer.invoke("dict-download", dict), - decompress: (dict: Dict) => ipcRenderer.invoke("dict-decompress", dict), remove: (dict: Dict) => ipcRenderer.invoke("dict-remove", dict), getResource: (key: string, dict: Dict) => ipcRenderer.invoke("dict-read-file", key, dict), lookup: (word: string, dict: Dict) => ipcRenderer.invoke("dict-lookup", word, dict), + import: (path: string) => ipcRenderer.invoke("dict-import", path), }, audios: { findAll: (params: { diff --git a/enjoy/src/renderer.ts b/enjoy/src/renderer.ts index f3c7ec81..3b6239b8 100644 --- a/enjoy/src/renderer.ts +++ b/enjoy/src/renderer.ts @@ -33,4 +33,10 @@ declare global { interface Window { __ENJOY_APP__: EnjoyAppType; } + + namespace JSX { + interface IntrinsicElements { + vocabulary: any; + } + } } diff --git a/enjoy/src/renderer/components/meanings/meaning-memorizing-card.tsx b/enjoy/src/renderer/components/meanings/meaning-memorizing-card.tsx index 4838a187..2a0420b4 100644 --- a/enjoy/src/renderer/components/meanings/meaning-memorizing-card.tsx +++ b/enjoy/src/renderer/components/meanings/meaning-memorizing-card.tsx @@ -4,6 +4,7 @@ import { Button, ScrollArea, Separator } from "@renderer/components/ui"; import Mark from "mark.js"; import { useHotkeys } from "react-hotkeys-hook"; import { HotKeysSettingsProviderContext } from "@renderer/context"; +import { Sentence } from "@renderer/components"; export const MeaningMemorizingCard = (props: { meaning: MeaningType }) => { const { @@ -73,7 +74,7 @@ const FrontSide = (props: {
{lookups.map((lookup) => (

- {lookup.context} +

))}
@@ -159,8 +160,10 @@ const BackSide = (props: { meaning: MeaningType; onFlip: () => void }) => {
{lookups.map((lookup) => (
-
{lookup.context}
-
{lookup.contextTranslation}
+ +
+ {lookup.contextTranslation} +
))}
diff --git a/enjoy/src/renderer/components/medias/media-caption.tsx b/enjoy/src/renderer/components/medias/media-caption.tsx index 644796b9..26b3d9c3 100644 --- a/enjoy/src/renderer/components/medias/media-caption.tsx +++ b/enjoy/src/renderer/components/medias/media-caption.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useContext } from "react"; +import { useEffect, useState, useContext, useRef } from "react"; import { AppSettingsProviderContext, MediaPlayerProviderContext, @@ -51,6 +51,7 @@ export const MediaCaption = () => { const [copied, setCopied] = useState(false); const [caption, setCaption] = useState(null); + const [tab, setTab] = useState("translation"); const toggleMultiSelect = (event: KeyboardEvent) => { setMultiSelecting(event.shiftKey && event.type === "keydown"); @@ -362,12 +363,15 @@ export const MediaCaption = () => {
{ export const Caption = (props: { caption: TimelineEntry; + tab: string; language?: string; selectedIndices?: number[]; currentSegmentIndex: number; @@ -544,7 +549,7 @@ export const Caption = (props: { }`} onClick={() => onClick && onClick(index)} > - + {word}
{displayIpa && ( diff --git a/enjoy/src/renderer/components/medias/media-captions/media-caption-tabs.tsx b/enjoy/src/renderer/components/medias/media-captions/media-caption-tabs.tsx index 6436cd17..6137521f 100644 --- a/enjoy/src/renderer/components/medias/media-captions/media-caption-tabs.tsx +++ b/enjoy/src/renderer/components/medias/media-captions/media-caption-tabs.tsx @@ -13,8 +13,10 @@ import { TabContentNote } from "./tab-content-note"; export const MediaCaptionTabs = (props: { caption: TimelineEntry; + tab: string; currentSegmentIndex: number; selectedIndices: number[]; + setTab: (v: string) => void; setSelectedIndices: (indices: number[]) => void; children?: React.ReactNode; }) => { @@ -24,10 +26,10 @@ export const MediaCaptionTabs = (props: { selectedIndices, setSelectedIndices, children, + tab, + setTab, } = props; - const [tab, setTab] = useState("translation"); - if (!caption) return null; return ( diff --git a/enjoy/src/renderer/components/medias/media-transcription.tsx b/enjoy/src/renderer/components/medias/media-transcription.tsx index 49443a7e..d0b1e689 100644 --- a/enjoy/src/renderer/components/medias/media-transcription.tsx +++ b/enjoy/src/renderer/components/medias/media-transcription.tsx @@ -29,6 +29,7 @@ import { MediaTranscriptionPrint, TranscriptionEditButton, } from "@renderer/components"; +import { Sentence } from "@renderer/components"; export const MediaTranscription = (props: { display?: boolean }) => { const { display } = props; @@ -202,7 +203,8 @@ export const MediaTranscription = (props: { display?: boolean }) => {
-

{sentence.text}

+ + ) )} diff --git a/enjoy/src/renderer/components/misc/markdown-wrapper.tsx b/enjoy/src/renderer/components/misc/markdown-wrapper.tsx index 04135588..5339ecb0 100644 --- a/enjoy/src/renderer/components/misc/markdown-wrapper.tsx +++ b/enjoy/src/renderer/components/misc/markdown-wrapper.tsx @@ -1,4 +1,21 @@ import Markdown from "react-markdown"; +import { visitParents } from "unist-util-visit-parents"; +import { Sentence } from "@renderer/components"; + +function rehypeWrapText() { + return function wrapTextTransform(tree: any) { + visitParents(tree, "text", (node, ancestors) => { + const parent = ancestors.at(-1); + + if (parent.tagName !== "vocabulary") { + node.type = "element"; + node.tagName = "vocabulary"; + node.properties = { text: node.value }; + node.children = [{ type: "text", value: node.value }]; + } + }); + }; +} export const MarkdownWrapper = ({ children, @@ -11,6 +28,7 @@ export const MarkdownWrapper = ({ return ( {children}; }, + vocabulary({ node, children, ...props }) { + return ; + }, }} {...props} > diff --git a/enjoy/src/renderer/components/notes/note-segment.tsx b/enjoy/src/renderer/components/notes/note-segment.tsx index f2f1eb9d..854eafea 100644 --- a/enjoy/src/renderer/components/notes/note-segment.tsx +++ b/enjoy/src/renderer/components/notes/note-segment.tsx @@ -3,6 +3,7 @@ import { useContext, useState } from "react"; import { WavesurferPlayer } from "@/renderer/components/misc"; import { AppSettingsProviderContext } from "@/renderer/context"; import { convertWordIpaToNormal } from "@/utils"; +import { Vocabulary } from "@renderer/components"; export const NoteSemgent = (props: { segment: SegmentType; @@ -51,7 +52,7 @@ export const NoteSemgent = (props: { } `} > - {word} +
- {recording.referenceText} +
)} diff --git a/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx b/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx index 1a10591b..448a9421 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/dict-import-button.tsx @@ -1,4 +1,8 @@ -import { useState } from "react"; +import { useState, useContext } from "react"; +import { + AppSettingsProviderContext, + DictProviderContext, +} from "@/renderer/context"; import { Button, Dialog, @@ -6,20 +10,54 @@ import { DialogContent, DialogHeader, DialogTitle, - ScrollArea, + toast, } from "@/renderer/components/ui"; -import { UninstallDictList } from "."; import { t } from "i18next"; +import { LoaderIcon } from "lucide-react"; export const DictImportButton = () => { + const { reload } = useContext(DictProviderContext); + 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); }; - const handleDownload = () => { - setOpen(false); + const handleAdaptationDictImport = async () => { + const pathes = await EnjoyApp.dialog.showOpenDialog({ + title: t("selectAdaptionDictTitle"), + properties: ["openDirectory"], + }); + + if (!pathes[0]) return; + + setLoading(true); + setTimeout(() => { + if (loading) { + setTipVisible(true); + } + }, 10000); + + try { + await EnjoyApp.dict.import(pathes[0]); + setOpen(false); + } catch (err) { + toast.error(err.message); + } + + setLoading(false); + setTipVisible(false); + reload(); + }; + + const handleOriginDictImport = async () => { + const pathes = await EnjoyApp.dialog.showOpenDialog({ + title: t("selectMdictFileOrDirTitle"), + properties: ["multiSelections", "openFile", "openDirectory"], + }); }; return ( @@ -32,9 +70,56 @@ export const DictImportButton = () => { {t("importDict")} - - - + {loading ? ( +
+
+ +
+ {tipVisible && ( +
+ {t("dictImportSlowTip")} +
+ )} +
+ ) : ( +
+
+ + + +
+ + {/*
+
+
{t("importMdictFile")}
+
+ {t("mdictFileTip")} +
+
+ + +
*/} +
+ )} ); diff --git a/enjoy/src/renderer/components/preferences/dict-settings/dict-settings.tsx b/enjoy/src/renderer/components/preferences/dict-settings/dict-settings.tsx index 519b84c8..37c84c99 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/dict-settings.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/dict-settings.tsx @@ -1,6 +1,6 @@ import { t } from "i18next"; import { DictImportButton } from "./dict-import-button"; -import { DownloadingDictList, InstalledDictList } from "."; +import { InstalledDictList } from "."; export const DictSettings = () => { return ( @@ -12,7 +12,6 @@ export const DictSettings = () => {
-
diff --git a/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx b/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx deleted file mode 100644 index 767b41c7..00000000 --- a/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { - DictProviderContext, - AppSettingsProviderContext, -} from "@/renderer/context"; -import { useContext, useEffect, useState } from "react"; -import { Button, toast } from "@renderer/components/ui"; -import { t } from "i18next"; -import { LoaderIcon } from "lucide-react"; - -export const DownloadingDictList = function () { - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { downloadingDicts, reload } = useContext(DictProviderContext); - - useEffect(() => { - listenToDownloadState(); - listenDecompressState(); - - return () => { - EnjoyApp.download.removeAllListeners(); - EnjoyApp.decompress.removeAllListeners(); - }; - }, []); - - const listenToDownloadState = () => { - EnjoyApp.download.onState((_event, state) => { - reload(); - }); - }; - - const listenDecompressState = () => { - EnjoyApp.decompress.onUpdate((_event, tasks) => { - reload(); - }); - }; - - return ( - <> - {downloadingDicts.map((item) => ( - - ))} - - ); -}; - -const DownloadingDictItem = function ({ dict }: { dict: Dict }) { - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { reload } = useContext(DictProviderContext); - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (dict.downloadState?.state === "completed") { - EnjoyApp.dict.decompress(dict); - } - }, [dict]); - - async function handlePause() { - setLoading(true); - - try { - await EnjoyApp.download.pause(dict.downloadState.name); - reload(); - } catch (err) { - toast.error(err.message); - } - - setLoading(false); - } - - async function handleResume() { - setLoading(true); - - try { - await EnjoyApp.download.resume(dict.downloadState.name); - reload(); - } catch (err) { - toast.error(err.message); - } - - setLoading(false); - } - - async function handleRemove() { - setLoading(true); - - try { - await EnjoyApp.download.remove(dict.downloadState.name); - toast.success(t("dictRemoved")); - reload(); - } catch (err) { - toast.error(err.message); - } - - setLoading(false); - } - - function displaySize(bytes: number) { - return Number((bytes / 1024 / 1024).toFixed(0)).toLocaleString() + "MB"; - } - - function renderDownloadState() { - const text = - dict.downloadState.state === "cancelled" - ? t("cancelled") - : dict.downloadState.state === "completed" - ? t("completedAndChecking") - : dict.downloadState.state === "interrupted" - ? t("interrupted") - : dict.downloadState.isPaused - ? t("paused") - : t("downloadingDict"); - - return ( -
- {text} - {displaySize(dict.downloadState.received)} - / - {displaySize(dict.downloadState.total)} -
- ); - } - - function renderDecompressState() { - return ( -
- {t("decompressing")} - {dict.decompressProgress ?? "0"}% -
- ); - } - - function renderActions() { - if (loading) - return ( -
- -
- ); - - if ( - dict.downloadState?.state === "progressing" && - !dict.downloadState?.isPaused - ) { - return ( - - ); - } - - if ( - dict.downloadState?.state === "cancelled" || - dict.downloadState?.state === "interrupted" || - (dict.downloadState?.state === "progressing" && - dict.downloadState?.isPaused) - ) { - return ( - <> - {dict.downloadState.canResume && ( - - )} - - - - ); - } - } - - return ( -
-
-
{dict.title}
-
- {dict.state === "decompressing" && renderDecompressState()} - {dict.downloadState && renderDownloadState()} -
-
- {renderActions()} -
- ); -}; diff --git a/enjoy/src/renderer/components/preferences/dict-settings/index.ts b/enjoy/src/renderer/components/preferences/dict-settings/index.ts index 498272e7..4b22593c 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/index.ts +++ b/enjoy/src/renderer/components/preferences/dict-settings/index.ts @@ -1,5 +1,3 @@ export * from "./dict-settings"; export * from "./dict-import-button"; -export * from "./downloading-dict-list"; export * from "./installed-dict-list"; -export * from "./uninstall-dict-list"; diff --git a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx index 8ee55dc3..e5484950 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx @@ -19,14 +19,13 @@ import { import { t } from "i18next"; export const InstalledDictList = function () { - const { installedDicts, downloadingDicts, reload } = - useContext(DictProviderContext); + const { installedDicts, reload } = useContext(DictProviderContext); useEffect(() => { reload(); }, []); - if (installedDicts.length === 0 && downloadingDicts.length === 0) { + if (installedDicts.length === 0) { return (
{t("dictEmpty")}
); @@ -119,6 +118,7 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) { - - ); -}; diff --git a/enjoy/src/renderer/components/stories/story-viewer.tsx b/enjoy/src/renderer/components/stories/story-viewer.tsx index 20742732..08b61b69 100644 --- a/enjoy/src/renderer/components/stories/story-viewer.tsx +++ b/enjoy/src/renderer/components/stories/story-viewer.tsx @@ -5,6 +5,7 @@ import { ChevronLeftIcon, ExternalLinkIcon } from "lucide-react"; import { Button } from "@renderer/components/ui"; import uniq from "lodash/uniq"; import Mark from "mark.js"; +import { Vocabulary } from "@/renderer/components"; export const StoryViewer = (props: { story: Partial & Partial; @@ -112,11 +113,14 @@ export const StoryViewer = (props: { key={`paragraph-${i}-sentence-${j}`} > {sentence.terms.map((term) => ( - + <> {term.pre} - {term.text} + {term.post} - + ))} ); diff --git a/enjoy/src/renderer/components/widgets/index.ts b/enjoy/src/renderer/components/widgets/index.ts index 174ce4b9..07dffa31 100644 --- a/enjoy/src/renderer/components/widgets/index.ts +++ b/enjoy/src/renderer/components/widgets/index.ts @@ -1,4 +1,5 @@ export * from "./lookup"; export * from "./translate-widget"; export * from "./vocabulary"; +export * from "./sentence"; export * from "./lookup/dict-lookup-result"; diff --git a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx index 10372be6..8c95eac2 100644 --- a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx +++ b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx @@ -50,6 +50,10 @@ export function DictLookupResult({ revoke(); setLooking(true); + if (autoHeight) { + setHeight(0); + } + const _word = word.trim().indexOf(" ") > -1 ? word : word.toLowerCase(); EnjoyApp.dict @@ -107,7 +111,7 @@ export function DictLookupResult({ { + let words = sentence.split(" "); + + return ( + + {words.map((word, index) => { + return ( + <> + + {index === words.length - 1 ? " " : " "} + + ); + })} + + ); +}; diff --git a/enjoy/src/renderer/components/widgets/vocabulary.tsx b/enjoy/src/renderer/components/widgets/vocabulary.tsx index a51c1e63..d8f269a4 100644 --- a/enjoy/src/renderer/components/widgets/vocabulary.tsx +++ b/enjoy/src/renderer/components/widgets/vocabulary.tsx @@ -13,6 +13,19 @@ export const Vocabulary = ({ let [timer, setTimer] = useState>(); const { vocabularyConfig, EnjoyApp } = useContext(AppSettingsProviderContext); + const handleLookup = (e: any) => { + if (!context) { + context = e.target?.parentElement + .closest(".sentence, h2, p, div") + ?.textContent?.trim(); + } + + const { x, bottom: y } = e.target.getBoundingClientRect(); + const _word = word.replace(/[^\w\s]|_/g, ""); + + EnjoyApp.lookup(_word, context, { x, y }); + }; + const handleMouseEnter = (e: any) => { let _timer = setTimeout(() => { if (!context) { @@ -25,7 +38,7 @@ export const Vocabulary = ({ const _word = word.replace(/[^\w\s]|_/g, ""); EnjoyApp.lookup(_word, context, { x, y }); - }, 1000); + }, 800); setTimer(_timer); }; @@ -36,9 +49,8 @@ export const Vocabulary = ({ return vocabularyConfig.lookupOnMouseOver ? ( {word || children} diff --git a/enjoy/src/renderer/context/dict-provider.tsx b/enjoy/src/renderer/context/dict-provider.tsx index cd692be9..f6e1085e 100644 --- a/enjoy/src/renderer/context/dict-provider.tsx +++ b/enjoy/src/renderer/context/dict-provider.tsx @@ -5,8 +5,6 @@ import { t } from "i18next"; type DictProviderState = { settings: DictSettingType; dicts: Dict[]; - downloadingDicts: Dict[]; - uninstallDicts: Dict[]; installedDicts: Dict[]; dictSelectItems: { text: string; value: string }[]; reload?: () => void; @@ -30,8 +28,6 @@ const CamDict = { const initialState: DictProviderState = { dicts: [], - downloadingDicts: [], - uninstallDicts: [], installedDicts: [], dictSelectItems: [AIDict], settings: { @@ -78,16 +74,6 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { ]; }, [availableDicts, learningLanguage]); - const downloadingDicts = useMemo(() => { - return dicts.filter( - (dict) => dict.state === "downloading" || dict.state === "decompressing" - ); - }, [dicts]); - - const uninstallDicts = useMemo(() => { - return dicts.filter((dict) => dict.state === "uninstall"); - }, [dicts]); - const installedDicts = useMemo(() => { return dicts.filter((dict) => dict.state === "installed"); }, [dicts]); @@ -168,8 +154,6 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { removed, reload: fetchDicts, dictSelectItems, - downloadingDicts, - uninstallDicts, installedDicts, currentDict, currentDictValue, diff --git a/enjoy/src/types/enjoy-app.d.ts b/enjoy/src/types/enjoy-app.d.ts index 0238b9c2..71bb2edd 100644 --- a/enjoy/src/types/enjoy-app.d.ts +++ b/enjoy/src/types/enjoy-app.d.ts @@ -161,11 +161,10 @@ type EnjoyAppType = { }; dict: { getDicts: () => Promise; - download: (dict: Dict) => Promise; - decompress: (dict: Dict) => Promise; remove: (dict: Dict) => Promise; getResource: (key: string, dict: Dict) => Promise; lookup: (word: string, dict: Dict) => Promise; + import: (path: string) => Promise; }; audios: { findAll: (params: any) => Promise; diff --git a/enjoy/tailwind.config.js b/enjoy/tailwind.config.js index b5c10514..55cefd5c 100644 --- a/enjoy/tailwind.config.js +++ b/enjoy/tailwind.config.js @@ -20,6 +20,7 @@ module.exports = { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", + "active-word": "hsl(var(--active-word))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: {