diff --git a/cjdic2.code-workspace b/cjdic2.code-workspace index f21927b..5a7ed06 100644 --- a/cjdic2.code-workspace +++ b/cjdic2.code-workspace @@ -15,6 +15,7 @@ ], "settings": { "importSorter.generalConfiguration.sortOnBeforeSave": true, + "importSorter.importStringConfiguration.quoteMark": "double", "json.schemaDownload.enable": false, }, } diff --git a/src-react/src/App.tsx b/src-react/src/App.tsx index 5584557..8c0d797 100644 --- a/src-react/src/App.tsx +++ b/src-react/src/App.tsx @@ -1,192 +1,14 @@ import "./App.css"; -import { CompositionEventHandler, useEffect, useState } from "react"; - -import { Box, Button, Card, Group, Input, Stack } from "@chakra-ui/react"; -import { invoke } from "@tauri-apps/api/core"; - -import Glossary from "./components/Glossary"; +import LoadingDialog from "./components/LoadingDialog"; import { Provider } from "./components/ui/provider"; - -interface Entry { - term: string; - reading: string; - glossary_json: string; - def_tags: string; - rules: string; - score: number; - sequence: number; - term_tags: string; - dict_title: string; -} +import SearchPage from "./pages/SearchPage"; function App() { - const [q, setQ] = useState(""); - const [entries, setEntries] = useState([]); - const [searchTimeout, setSearchTimeout] = useState(0); - const [furigana, setFurigana] = useState(""); - - useEffect(() => { - trySearch(); - }, [q]); - - async function trySearch() { - if (!q.trim()) { - setEntries([]); - return; - } - - if (searchTimeout) { - clearTimeout(searchTimeout); - } - - setSearchTimeout( - setTimeout(() => { - runSearch(); - }, 250), - ); - } - - async function runSearch() { - const ender = /\p{Z}$/u.test(q) ? "" : "*"; - - const re = /\<(.+?)\>\[(.+?)\]/g; - - let qTerm = ""; - let qReading = ""; - - q.trim() - .split(re) - .map((s, i) => { - switch (i % 3) { - case 1: - qTerm += s; - break; - case 2: - qReading += s; - break; - default: - qTerm += s; - qReading += s; - } - }); - - qTerm += ender; - qReading += ender; - - setEntries( - await invoke("search_yomitan", { - qTerm, - qReading, - limit: 10, - offset: 0, - }), - ); - } - - const FURIGANA_REGEX = /^[\p{scx=Hiragana}\p{scx=Katakana}]+$/u; - const KANJI_REGEX = /([\p{sc=Han}\p{N}々〆ヵヶ]+)/u; - - const updateFurigana: CompositionEventHandler = ({ - data: compositionData, - }) => { - if (FURIGANA_REGEX.test(compositionData)) { - setFurigana(compositionData); - } - }; - - const addFurigana: CompositionEventHandler = ({ data: compositionData }) => { - const cleanedFuri = [...furigana.replace(/n/g, "ん")] - .map((c) => katakanaToHiragana(c)) - .join(""); - - let parts = compositionData.split(KANJI_REGEX); - if (parts.length === 1) return; - - const hiraganaParts = parts.map((p) => - [...p].map((c) => katakanaToHiragana(c)).join(""), - ); - const regex = new RegExp( - `^${hiraganaParts.map((p, idx) => `(${idx & 1 ? ".+" : p})`).join("")}$`, - ); - let rt: (string | null)[] = furigana.match(regex) || []; - if (!rt.length) { - parts = [compositionData]; - rt = [null, cleanedFuri]; - } - rt.shift(); - - const markup = parts - .map((p, idx) => (idx & 1 ? `<${p}>[${rt[idx]}]` : p)) - .join(""); - setQ(markup); - }; - - function katakanaToHiragana(k: string) { - let c = k.charCodeAt(0); - return c >= 12449 && c <= 12531 - ? String.fromCharCode(k.charCodeAt(0) - 96) - : k; - } - return ( - -
{ - e.preventDefault(); - }} - > - - setQ(e.currentTarget.value)} - onCompositionUpdate={updateFurigana} - onCompositionEnd={addFurigana} - autoComplete="off" - spellCheck={false} - placeholder="Search..." - /> - - -
- - {entries.map( - ({ term, reading, dict_title, glossary_json, ...it }, i) => ( -
  • -
    - - {term} ({reading}) 【{dict_title}】 - - - - - {JSON.stringify(it, (_, v) => v || undefined)} - - -
    - {JSON.parse(glossary_json).map((g: any) => ( - setQ(t + " ")} - /> - ))} -
    -
    -
    -
    -
  • - ), - )} -
    -
    + +
    ); } diff --git a/src-react/src/components/LoadingDialog.tsx b/src-react/src/components/LoadingDialog.tsx new file mode 100644 index 0000000..64e3b41 --- /dev/null +++ b/src-react/src/components/LoadingDialog.tsx @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; + +import { Dialog, Portal } from "@chakra-ui/react"; + +function LoadingDialog() { + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + setIsLoading(true); + }, []); + + return ( + + + + + + + + + + + Example text + + + + + + ); +} + +export default LoadingDialog; diff --git a/src-react/src/pages/SearchPage.tsx b/src-react/src/pages/SearchPage.tsx new file mode 100644 index 0000000..cdb432c --- /dev/null +++ b/src-react/src/pages/SearchPage.tsx @@ -0,0 +1,193 @@ +import { CompositionEventHandler, useEffect, useState } from "react"; + +import { Box, Button, Card, Group, Input, Stack } from "@chakra-ui/react"; +import { invoke } from "@tauri-apps/api/core"; + +import Glossary from "../components/Glossary"; + +interface Entry { + term: string; + reading: string; + glossary_json: string; + def_tags: string; + rules: string; + score: number; + sequence: number; + term_tags: string; + dict_title: string; +} + +function SearchPage() { + const [q, setQ] = useState(""); + const [entries, setEntries] = useState([]); + const [searchTimeout, setSearchTimeout] = useState(0); + const [furigana, setFurigana] = useState(""); + + const lang = "ja-JP"; + + useEffect(() => { + trySearch(); + }, [q]); + + async function trySearch() { + if (!q.trim()) { + setEntries([]); + return; + } + + if (searchTimeout) { + clearTimeout(searchTimeout); + } + + setSearchTimeout( + setTimeout(() => { + runSearch(); + }, 250), + ); + } + + async function runSearch() { + const ender = /\p{Z}$/u.test(q) ? "" : "*"; + + const re = /\<(.+?)\>\[(.+?)\]/g; + + let qTerm = ""; + let qReading = ""; + + q.trim() + .split(re) + .map((s, i) => { + switch (i % 3) { + case 1: + qTerm += s; + break; + case 2: + qReading += s; + break; + default: + qTerm += s; + qReading += s; + } + }); + + qTerm += ender; + qReading += ender; + + setEntries( + await invoke("search_yomitan", { + qTerm, + qReading, + limit: 10, + offset: 0, + }), + ); + } + + const FURIGANA_REGEX = /^[\p{scx=Hiragana}\p{scx=Katakana}]+$/u; + const KANJI_REGEX = /([\p{sc=Han}\p{N}々〆ヵヶ]+)/u; + + const updateFurigana: CompositionEventHandler = ({ + data: compositionData, + }) => { + if (FURIGANA_REGEX.test(compositionData)) { + setFurigana(compositionData); + } + }; + + const addFurigana: CompositionEventHandler = ({ data: compositionData }) => { + const cleanedFuri = [...furigana.replace(/n/g, "ん")] + .map((c) => katakanaToHiragana(c)) + .join(""); + + let parts = compositionData.split(KANJI_REGEX); + if (parts.length === 1) return; + + const hiraganaParts = parts.map((p) => + [...p].map((c) => katakanaToHiragana(c)).join(""), + ); + const regex = new RegExp( + `^${hiraganaParts.map((p, idx) => `(${idx & 1 ? ".+" : p})`).join("")}$`, + ); + let rt: (string | null)[] = furigana.match(regex) || []; + if (!rt.length) { + parts = [compositionData]; + rt = [null, cleanedFuri]; + } + rt.shift(); + + const markup = parts + .map((p, idx) => (idx & 1 ? `<${p}>[${rt[idx]}]` : p)) + .join(""); + setQ(markup); + }; + + function katakanaToHiragana(k: string) { + let c = k.charCodeAt(0); + return c >= 12449 && c <= 12531 + ? String.fromCharCode(k.charCodeAt(0) - 96) + : k; + } + + return ( + +
    { + e.preventDefault(); + }} + > + + setQ(e.currentTarget.value)} + onCompositionUpdate={updateFurigana} + onCompositionEnd={addFurigana} + autoComplete="off" + spellCheck={false} + placeholder="Search..." + /> + + +
    + + {entries.map( + ({ term, reading, dict_title, glossary_json, ...it }, i) => ( +
  • +
    + + {term} + {reading ? ` (${reading})` : ""} 【{dict_title}】 + + + + + {JSON.stringify(it, (_, v) => v || undefined)} + + +
    + {JSON.parse(glossary_json).map((g: any) => ( + setQ(t + " ")} + /> + ))} +
    +
    +
    +
    +
  • + ), + )} +
    +
    + ); +} + +export default SearchPage;