This commit is contained in:
Michael Zhang 2023-06-11 15:08:15 -05:00
parent a08636b3ee
commit 779fc69405
11 changed files with 202 additions and 76 deletions

1
.tokeignore Normal file
View file

@ -0,0 +1 @@
package-lock.json

36
package-lock.json generated
View file

@ -12,7 +12,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^6.11.2", "react-router": "^6.11.2",
"react-router-dom": "^6.11.2" "react-router-dom": "^6.11.2",
"swr": "^2.1.5"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.3.1", "@tauri-apps/cli": "^1.3.1",
@ -1321,6 +1322,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/swr": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.1.5.tgz",
"integrity": "sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==",
"dependencies": {
"use-sync-external-store": "^1.2.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/to-fast-properties": { "node_modules/to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -1385,6 +1397,14 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "4.3.9", "version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
@ -2310,6 +2330,14 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"swr": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.1.5.tgz",
"integrity": "sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==",
"requires": {
"use-sync-external-store": "^1.2.0"
}
},
"to-fast-properties": { "to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -2341,6 +2369,12 @@
"picocolors": "^1.0.0" "picocolors": "^1.0.0"
} }
}, },
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"vite": { "vite": {
"version": "4.3.9", "version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",

View file

@ -14,7 +14,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^6.11.2", "react-router": "^6.11.2",
"react-router-dom": "^6.11.2" "react-router-dom": "^6.11.2",
"swr": "^2.1.5"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.3.1", "@tauri-apps/cli": "^1.3.1",
@ -26,4 +27,4 @@
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.2.1" "vite": "^4.2.1"
} }
} }

12
src-tauri/Cargo.lock generated
View file

@ -657,6 +657,17 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.17" version = "0.99.17"
@ -1369,6 +1380,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"derivative",
"dirs", "dirs",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -10,8 +10,6 @@ edition = "2021"
[workspace] [workspace]
members = ["database-maker"] members = ["database-maker"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "1.3", features = [] } tauri-build = { version = "1.3", features = [] }
@ -24,7 +22,7 @@ anyhow = "1.0.71"
clap = { version = "4.3.2", features = ["derive"] } clap = { version = "4.3.2", features = ["derive"] }
sqlx = { version = "0.6.3", features = ["runtime-tokio-rustls", "sqlite"] } sqlx = { version = "0.6.3", features = ["runtime-tokio-rustls", "sqlite"] }
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
derivative = "2.2.0"
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem # this feature is used for production builds or when `devPath` points to the filesystem

View file

@ -3,41 +3,44 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tokio::{ use tokio::{
fs::File, fs::File,
io::{AsyncBufReadExt, BufReader}, io::{AsyncBufReadExt, BufReader},
}; };
const SEPARATOR: char = ':'; const SEPARATOR: char = ':';
pub async fn process_kradfile(pool: &SqlitePool, path: impl AsRef<Path>) -> Result<()> { pub async fn process_kradfile(
let file = File::open(path.as_ref()).await?; pool: &SqlitePool,
path: impl AsRef<Path>,
) -> Result<()> {
let file = File::open(path.as_ref()).await?;
let file_reader = BufReader::new(file); let file_reader = BufReader::new(file);
let mut lines = file_reader.lines(); let mut lines = file_reader.lines();
loop { loop {
let line = match lines.next_line().await? { let line = match lines.next_line().await? {
Some(v) => v, Some(v) => v,
None => break, None => break,
}; };
// Skip comments // Skip comments
if line.starts_with('#') { if line.starts_with('#') {
continue; continue;
}
let parts = line.split(SEPARATOR).collect::<Vec<_>>();
let (kanji, radicals) = match &parts[..] {
&[kanji, radicals] => {
let kanji = kanji.trim();
let radicals = radicals.trim().split_whitespace().collect::<Vec<_>>();
(kanji, radicals)
}
_ => continue,
};
println!("kanji: {}, radicals: {:?}", kanji, radicals);
} }
Ok(()) let parts = line.split(SEPARATOR).collect::<Vec<_>>();
let (kanji, radicals) = match &parts[..] {
&[kanji, radicals] => {
let kanji = kanji.trim();
let radicals = radicals.trim().split_whitespace().collect::<Vec<_>>();
(kanji, radicals)
}
_ => continue,
};
println!("kanji: {}, radicals: {:?}", kanji, radicals);
}
Ok(())
} }

View file

@ -3,14 +3,45 @@ use tauri::State;
pub struct KanjiDb(pub SqlitePool); pub struct KanjiDb(pub SqlitePool);
#[derive(Debug, Derivative, Serialize, Deserialize)]
#[derivative(Default)]
pub struct GetKanjiOptions {
#[derivative(Default(value = "10"))]
how_many: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetKanjiResult {
count: u32,
kanji: Vec<String>,
}
#[tauri::command] #[tauri::command]
pub async fn get_kanji(state: State<'_, KanjiDb>) -> Result<Vec<String>, ()> { pub async fn get_kanji(
let result = sqlx::query("SELECT * FROM KanjiSet LIMIT 5") state: State<'_, KanjiDb>,
.fetch_all(&state.0) options: Option<GetKanjiOptions>,
) -> Result<GetKanjiResult, ()> {
let opts = options.unwrap_or_default();
let result = sqlx::query(
r#"SELECT * FROM KanjiSet
LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID
WHERE MostUsedRank IS NOT NULL
ORDER BY MostUsedRank
LIMIT ?"#,
)
.bind(opts.how_many)
.fetch_all(&state.0)
.await
.map_err(|_| ())?;
let kanji = result.into_iter().map(|row| row.get("Character")).collect();
let count = sqlx::query("SELECT COUNT(*) FROM KanjiSet")
.fetch_one(&state.0)
.await .await
.map_err(|_| ())?; .map_err(|_| ())?;
let count = count.try_get(0).map_err(|_| ())?;
let result = result.into_iter().map(|row| row.get("Character")).collect(); Ok(GetKanjiResult { kanji, count })
Ok(result)
} }

View file

@ -1,10 +1,20 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[macro_use]
extern crate derivative;
#[macro_use]
extern crate serde;
mod kanji; mod kanji;
use std::str::FromStr;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use sqlx::SqlitePool; use sqlx::{
sqlite::{SqliteConnectOptions, SqlitePoolOptions},
SqlitePool,
};
use crate::kanji::KanjiDb; use crate::kanji::KanjiDb;
@ -16,7 +26,11 @@ fn greet(name: &str) -> String {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let kanji_pool = SqlitePool::connect("./KanjiDatabase.sqlite").await?; let kanji_db_options =
SqliteConnectOptions::from_str("./KanjiDatabase.sqlite")?.read_only(true);
let kanji_pool = SqlitePoolOptions::new()
.connect_with(kanji_db_options)
.await?;
tauri::Builder::default() tauri::Builder::default()
.manage(KanjiDb(kanji_pool)) .manage(KanjiDb(kanji_pool))

View file

@ -21,7 +21,7 @@
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": "all",
"identifier": "com.tauri.dev", "identifier": "io.mzhang.houhou",
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
@ -43,4 +43,4 @@
} }
] ]
} }
} }

View file

@ -1,27 +1,46 @@
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { Link, RouterProvider, createHashRouter } from "react-router-dom";
import styles from "./App.module.scss" import styles from "./App.module.scss";
import KanjiPane from "./panes/KanjiPane"; import KanjiPane from "./panes/KanjiPane";
import HomePane from "./panes/HomePane"; import HomePane from "./panes/HomePane";
import { createBrowserRouter } from "react-router-dom";
import { Outlet, Route, createRoutesFromElements } from "react-router";
export default function App() { function Layout() {
const router = createBrowserRouter(routes);
return ( return (
<> <>
<ul className={styles.header}> <ul className={styles.header}>
<li><a href="/">Home</a></li> {routes.map((route) => (
<li><a href="/srs">Srs</a></li> <li key={route.path}>
<li><a href="/kanji">Kanji</a></li> <Link to={route.path}>{route.title}</Link>
<li><a href="/vocab">Vocab</a></li> </li>
<li><a href="/settings">Settings</a></li> ))}
</ul> </ul>
<RouterProvider router={router} /> <Outlet />
</> </>
); );
} }
export default function App() {
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Layout />}>
{routes.map((route, idx) => (
<Route
key={route.path}
index={idx === 0}
path={route.path}
element={route.element}
/>
))}
</Route>
)
);
return <RouterProvider router={router} />;
}
const routes = [ const routes = [
{ path: "/", element: <HomePane /> }, { path: "/", title: "Home", element: <HomePane /> },
{ path: "/kanji", element: <KanjiPane /> }, { path: "/kanji", title: "Kanji", element: <KanjiPane /> },
]; ];

View file

@ -1,23 +1,36 @@
import { useState } from "react" import { useState } from "react";
import { Kanji } from "../types/Kanji" import { Kanji } from "../types/Kanji";
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from "@tauri-apps/api/tauri";
import styles from "./KanjiPane.module.scss" import useSWR from "swr";
import styles from "./KanjiPane.module.scss";
interface GetKanjiResult {
count: number;
kanji: string[];
}
function KanjiList({ data }: { data : GetKanjiResult}) {
return <>
Displaying {data.kanji.length} of {data.count} results.
<ul>
{data.kanji.map(kanji => <li key={kanji}>
{kanji}
</li>)}
</ul>
</>
}
export default function KanjiPane() { export default function KanjiPane() {
const [selectedKanji, setSelectedKanji] = useState(null); const { data, error, isLoading } = useSWR("get_kanji", invoke<GetKanjiResult>);
const fetchKanji = async () => { return (
const result = await invoke('get_kanji'); <>
setSelectedKanji(result); {JSON.stringify(error)}
};
return <> <div className={styles.kanjiDisplay}></div>
{JSON.stringify(selectedKanji)}
<div className={styles.kanjiDisplay}> {data && <KanjiList data={data} />}
</>
</div> );
}
<button onClick={fetchKanji}>Fetch</button>
</>
}