upd
This commit is contained in:
parent
779fc69405
commit
15cbfeb0d6
23 changed files with 4679 additions and 204 deletions
7
.prettierrc.json5
Normal file
7
.prettierrc.json5
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
singleQuote: false,
|
||||
trailingComma: "all",
|
||||
printWidth: 100,
|
||||
}
|
4481
package-lock.json
generated
4481
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,13 @@
|
|||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.7.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"classnames": "^2.3.2",
|
||||
"flowbite": "^1.6.5",
|
||||
"framer-motion": "^10.12.16",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.11.2",
|
||||
|
@ -23,7 +29,10 @@
|
|||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.24",
|
||||
"sass": "^1.62.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.2.1"
|
||||
}
|
||||
|
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
36
src-tauri/Cargo.lock
generated
36
src-tauri/Cargo.lock
generated
|
@ -1656,12 +1656,46 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gtk",
|
||||
"gtk-sys",
|
||||
"libappindicator-sys",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator-sys"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa"
|
||||
dependencies = [
|
||||
"gtk-sys",
|
||||
"libloading",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.24.2"
|
||||
|
@ -3048,6 +3082,7 @@ dependencies = [
|
|||
"core-foundation",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dirs-next",
|
||||
"dispatch",
|
||||
"gdk",
|
||||
"gdk-pixbuf",
|
||||
|
@ -3062,6 +3097,7 @@ dependencies = [
|
|||
"instant",
|
||||
"jni",
|
||||
"lazy_static",
|
||||
"libappindicator",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
|
|
|
@ -14,7 +14,7 @@ members = ["database-maker"]
|
|||
tauri-build = { version = "1.3", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.3", features = ["shell-open"] }
|
||||
tauri = { version = "1.3", features = ["shell-open", "system-tray"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
dirs = "5.0.1"
|
||||
|
|
|
@ -6,14 +6,24 @@ pub struct KanjiDb(pub SqlitePool);
|
|||
#[derive(Debug, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Default)]
|
||||
pub struct GetKanjiOptions {
|
||||
/// For looking up an existing one
|
||||
character: Option<String>,
|
||||
|
||||
#[derivative(Default(value = "10"))]
|
||||
how_many: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Kanji {
|
||||
character: String,
|
||||
meaning: String,
|
||||
most_used_rank: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GetKanjiResult {
|
||||
count: u32,
|
||||
kanji: Vec<String>,
|
||||
kanji: Vec<Kanji>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -24,18 +34,33 @@ pub async fn get_kanji(
|
|||
let opts = options.unwrap_or_default();
|
||||
|
||||
let result = sqlx::query(
|
||||
r#"SELECT * FROM KanjiSet
|
||||
r#"
|
||||
SELECT * FROM KanjiSet
|
||||
LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID
|
||||
WHERE MostUsedRank IS NOT NULL
|
||||
GROUP BY KanjiSet.ID
|
||||
HAVING MostUsedRank IS NOT NULL
|
||||
ORDER BY MostUsedRank
|
||||
LIMIT ?"#,
|
||||
LIMIT ?
|
||||
"#,
|
||||
)
|
||||
.bind(opts.how_many)
|
||||
.fetch_all(&state.0)
|
||||
.await
|
||||
.map_err(|_| ())?;
|
||||
|
||||
let kanji = result.into_iter().map(|row| row.get("Character")).collect();
|
||||
let kanji = result
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let character = row.get("Character");
|
||||
let meaning = row.get("Meaning");
|
||||
let most_used_rank = row.get("MostUsedRank");
|
||||
Kanji {
|
||||
character,
|
||||
meaning,
|
||||
most_used_rank,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let count = sqlx::query("SELECT COUNT(*) FROM KanjiSet")
|
||||
.fetch_one(&state.0)
|
||||
|
|
|
@ -15,6 +15,7 @@ use sqlx::{
|
|||
sqlite::{SqliteConnectOptions, SqlitePoolOptions},
|
||||
SqlitePool,
|
||||
};
|
||||
use tauri::SystemTray;
|
||||
|
||||
use crate::kanji::KanjiDb;
|
||||
|
||||
|
@ -26,14 +27,20 @@ fn greet(name: &str) -> String {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Open kanji db
|
||||
let kanji_db_options =
|
||||
SqliteConnectOptions::from_str("./KanjiDatabase.sqlite")?.read_only(true);
|
||||
let kanji_pool = SqlitePoolOptions::new()
|
||||
.connect_with(kanji_db_options)
|
||||
.await?;
|
||||
|
||||
// System tray
|
||||
let tray = SystemTray::new();
|
||||
|
||||
// Build tauri
|
||||
tauri::Builder::default()
|
||||
.manage(KanjiDb(kanji_pool))
|
||||
.system_tray(tray)
|
||||
.invoke_handler(tauri::generate_handler![greet, kanji::get_kanji])
|
||||
.run(tauri::generate_context!())
|
||||
.context("error while running tauri application")?;
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"systemTray": {
|
||||
"iconPath": "icons/icon.ico",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
main.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
padding: 12px;
|
||||
|
||||
}
|
||||
|
||||
.link-active {
|
||||
background-color: skyblue;
|
||||
}
|
42
src/App.tsx
42
src/App.tsx
|
@ -1,23 +1,35 @@
|
|||
import { Link, RouterProvider, createHashRouter } from "react-router-dom";
|
||||
import styles from "./App.module.scss";
|
||||
import KanjiPane from "./panes/KanjiPane";
|
||||
import classNames from "classnames";
|
||||
import { ChakraProvider } from '@chakra-ui/react'
|
||||
import HomePane from "./panes/HomePane";
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
import { Outlet, Route, createRoutesFromElements } from "react-router";
|
||||
import { Outlet, Route, createRoutesFromElements, useLocation } from "react-router";
|
||||
import SrsPane from "./panes/SrsPane";
|
||||
import VocabPane from "./panes/VocabPane";
|
||||
import SettingsPane from "./panes/SettingsPane";
|
||||
import { StrictMode } from "react";
|
||||
|
||||
import styles from "./App.module.scss";
|
||||
|
||||
function Layout() {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={styles.main}>
|
||||
<ul className={styles.header}>
|
||||
{routes.map((route) => (
|
||||
<li key={route.path}>
|
||||
<Link to={route.path}>{route.title}</Link>
|
||||
{routes.map((route) => {
|
||||
if (!route.title) return undefined;
|
||||
const active = route.path == location.pathname;
|
||||
const className = classNames(styles.link, active && styles['link-active']);
|
||||
return <li key={route.path}>
|
||||
<Link to={route.path} className={className}>{route.title}</Link>
|
||||
</li>
|
||||
))}
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<Outlet />
|
||||
</>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -37,10 +49,18 @@ export default function App() {
|
|||
)
|
||||
);
|
||||
|
||||
return <RouterProvider router={router} />;
|
||||
return <StrictMode>
|
||||
<ChakraProvider>
|
||||
<RouterProvider router={router} />
|
||||
</ChakraProvider>
|
||||
</StrictMode>
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{ path: "/", title: "Home", element: <HomePane /> },
|
||||
{ path: "/kanji", title: "Kanji", element: <KanjiPane /> },
|
||||
{ key: "home", path: "/", title: "Home", element: <HomePane /> },
|
||||
{ key: "srs", path: "/srs", title: "SRS", element: <SrsPane /> },
|
||||
{ key: "kanji", path: "/kanji", title: "Kanji", element: <KanjiPane /> },
|
||||
{ key: "kanjiSelected", path: "/kanji/:selectedKanji", element: <KanjiPane /> },
|
||||
{ key: "vocab", path: "/vocab", title: "Vocab", element: <VocabPane /> },
|
||||
{ key: "settings", path: "/settings", title: "Settings", element: <SettingsPane /> },
|
||||
];
|
||||
|
|
10
src/components/KanjiDisplay.module.scss
Normal file
10
src/components/KanjiDisplay.module.scss
Normal file
|
@ -0,0 +1,10 @@
|
|||
$kanjiDisplaySize: 80px;
|
||||
|
||||
.display {
|
||||
border: 1px solid rgb(87, 87, 210);
|
||||
width: $kanjiDisplaySize;
|
||||
height: $kanjiDisplaySize;
|
||||
font-size: $kanjiDisplaySize * 0.8;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
19
src/components/KanjiDisplay.tsx
Normal file
19
src/components/KanjiDisplay.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { GetKanjiResult } from "../panes/KanjiPane";
|
||||
import { Kanji } from "../types/Kanji"
|
||||
import styles from "./KanjiDisplay.module.scss"
|
||||
import useSWR from "swr";
|
||||
|
||||
interface KanjiDisplayProps {
|
||||
kanjiCharacter: string;
|
||||
}
|
||||
|
||||
export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
||||
const { data, error, isLoading } = useSWR(["get_kanji", kanjiCharacter], invoke<GetKanjiResult>);
|
||||
|
||||
return <>
|
||||
<div className={styles.display}>
|
||||
{kanjiCharacter}
|
||||
</div>
|
||||
</>
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function KanjiSearch() {
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
$kanjiDisplaySize: 80px;
|
||||
|
||||
.kanjiDisplay {
|
||||
border: 1px solid rgb(87, 87, 210);
|
||||
width: $kanjiDisplaySize;
|
||||
height: $kanjiDisplaySize;
|
||||
font-size: $kanjiDisplaySize * 0.8;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
.kanji-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.kanji-link {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid lightgray;
|
||||
}
|
|
@ -1,36 +1,57 @@
|
|||
import { useState } from "react";
|
||||
import { Kanji } from "../types/Kanji";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import useSWR from "swr";
|
||||
import styles from "./KanjiPane.module.scss";
|
||||
import { Box, Grid, GridItem, LinkBox, Stack } from "@chakra-ui/layout";
|
||||
|
||||
interface GetKanjiResult {
|
||||
import styles from "./KanjiPane.module.scss";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import KanjiDisplay from "../components/KanjiDisplay";
|
||||
import { Kanji } from "../types/Kanji";
|
||||
|
||||
export interface GetKanjiResult {
|
||||
count: number;
|
||||
kanji: string[];
|
||||
kanji: Kanji[];
|
||||
}
|
||||
|
||||
function KanjiList({ data }: { data : GetKanjiResult}) {
|
||||
interface KanjiListProps {
|
||||
data: GetKanjiResult;
|
||||
selectedCharacter: string;
|
||||
}
|
||||
|
||||
function KanjiList({ data, selectedCharacter }: KanjiListProps) {
|
||||
return <>
|
||||
Displaying {data.kanji.length} of {data.count} results.
|
||||
|
||||
<ul>
|
||||
{data.kanji.map(kanji => <li key={kanji}>
|
||||
{kanji}
|
||||
</li>)}
|
||||
</ul>
|
||||
{data.kanji.map(kanji => <Link key={kanji.character} className={styles['kanji-link']} to={`/kanji/${kanji.character}`}>
|
||||
<Grid
|
||||
templateRows='repeat(2, 1fr)'
|
||||
templateColumns='1fr 3fr'>
|
||||
<GridItem rowSpan={2} style={{ fontSize: '24px', textAlign: 'center' }}>{kanji.character}</GridItem>
|
||||
<GridItem>{kanji.meaning}</GridItem>
|
||||
<GridItem>#{kanji.most_used_rank} most used</GridItem>
|
||||
</Grid>
|
||||
</Link>)}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
export default function KanjiPane() {
|
||||
const { selectedKanji } = useParams();
|
||||
const { data, error, isLoading } = useSWR("get_kanji", invoke<GetKanjiResult>);
|
||||
|
||||
return (
|
||||
<>
|
||||
{JSON.stringify(error)}
|
||||
{JSON.stringify(selectedKanji)}
|
||||
|
||||
<div className={styles.kanjiDisplay}></div>
|
||||
<Stack spacing={7} direction='row'>
|
||||
<Box p={2} className={styles['kanji-list']}>
|
||||
{data && <KanjiList data={data} selectedCharacter={selectedKanji} />}
|
||||
</Box>
|
||||
|
||||
{data && <KanjiList data={data} />}
|
||||
<Box p={5}>
|
||||
{selectedKanji ? <KanjiDisplay kanjiCharacter={selectedKanji} /> : "nothing selected"}
|
||||
</Box>
|
||||
</Stack >
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
3
src/panes/SettingsPane.tsx
Normal file
3
src/panes/SettingsPane.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function SettingsPane() {
|
||||
return <></>
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
export default function SrsPane() {
|
||||
|
||||
return <></>
|
||||
}
|
3
src/panes/VocabPane.tsx
Normal file
3
src/panes/VocabPane.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function VocabPane() {
|
||||
return <></>
|
||||
}
|
112
src/styles.css
112
src/styles.css
|
@ -1,109 +1,3 @@
|
|||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
|
@ -1,4 +1,5 @@
|
|||
export interface Kanji {
|
||||
character: string;
|
||||
translation: string;
|
||||
meaning: string;
|
||||
most_used_rank: number;
|
||||
}
|
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./src/**/*.{html,js,jsx,ts,tsx}",
|
||||
"./node_modules/flowbite/**/*.js"],
|
||||
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
plugins: [require("flowbite/plugin")],
|
||||
};
|
Loading…
Reference in a new issue