diff --git a/src-tauri/src/kanji.rs b/src-tauri/src/kanji.rs index 98ddb09..55bf2fc 100644 --- a/src-tauri/src/kanji.rs +++ b/src-tauri/src/kanji.rs @@ -1,4 +1,4 @@ -use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; +use sqlx::{sqlite::SqliteRow, Encode, Row, SqlitePool, Type}; use tauri::State; pub struct KanjiDb(pub SqlitePool); @@ -7,15 +7,28 @@ pub struct KanjiDb(pub SqlitePool); #[derivative(Default)] pub struct GetKanjiOptions { #[serde(default)] + character: Option, + + #[serde(default = "default_how_many")] #[derivative(Default(value = "10"))] how_many: u32, } +fn default_how_many() -> u32 { + 10 +} + #[derive(Debug, Serialize, Deserialize)] pub struct Kanji { character: String, - meaning: String, most_used_rank: u32, + meanings: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KanjiMeaning { + id: u32, + meaning: String, } #[derive(Debug, Serialize, Deserialize)] @@ -24,73 +37,97 @@ pub struct GetKanjiResult { kanji: Vec, } -impl Kanji { - fn from_sqlite_row(row: SqliteRow) -> Self { - let character = row.get("Character"); - let meaning = row.get("Meaning"); - let most_used_rank = row.get("MostUsedRank"); - - Kanji { - character, - meaning, - most_used_rank, - } - } -} - #[tauri::command] pub async fn get_kanji( db: State<'_, KanjiDb>, options: Option, -) -> Result { +) -> Result { let opts = options.unwrap_or_default(); + println!("Options: {:?}", opts); - let result = sqlx::query( + let looking_for_character_clause = match opts.character { + None => String::new(), + Some(_) => format!(" AND KanjiSet.Character = ?"), + }; + + let query_string = format!( r#" - SELECT * FROM KanjiSet - LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID - GROUP BY KanjiSet.ID - HAVING MostUsedRank IS NOT NULL - ORDER BY MostUsedRank - LIMIT ? + SELECT *, KanjiMeaningSet.ID as KanjiMeaningID + FROM ( + SELECT * + FROM KanjiSet + WHERE MostUsedRank IS NOT NULL + {looking_for_character_clause} + ORDER BY MostUsedRank LIMIT ? + ) as Kanji + JOIN KanjiMeaningSet ON Kanji.ID = KanjiMeaningSet.Kanji_ID + ORDER BY MostUsedRank, KanjiMeaningSet.ID "#, - ) - .bind(opts.how_many) - .fetch_all(&db.0) - .await - .map_err(|_| ())?; + ); - let kanji = result.into_iter().map(Kanji::from_sqlite_row).collect(); + let mut query = sqlx::query(&query_string); + + // Do all the binds + + if let Some(character) = opts.character { + println!("Bind {}", character); + query = query.bind(character); + } + + println!("Bind {}", opts.how_many); + query = query.bind(opts.how_many); + + println!("Query: {query_string}"); + + let result = query + .fetch_all(&db.0) + .await + .map_err(|err| err.to_string())?; + + let kanji = { + let mut new_vec: Vec = Vec::with_capacity(result.len()); + let mut last_character: Option = None; + + for row in result { + let character: String = row.get("Character"); + let most_used_rank = row.get("MostUsedRank"); + + let meaning = KanjiMeaning { + id: row.get("KanjiMeaningID"), + meaning: row.get("Meaning"), + }; + + let same_as = match last_character { + Some(ref last_character) if character == *last_character => { + Some(new_vec.last_mut().unwrap()) + } + Some(_) => None, + None => None, + }; + + last_character = Some(character.clone()); + + if let Some(kanji) = same_as { + kanji.meanings.push(meaning); + } else { + new_vec.push(Kanji { + character, + most_used_rank, + meanings: vec![meaning], + }); + } + } + + new_vec + }; + + println!("Result: {kanji:?}"); let count = sqlx::query("SELECT COUNT(*) FROM KanjiSet") .fetch_one(&db.0) .await - .map_err(|_| ())?; - let count = count.try_get(0).map_err(|_| ())?; + .map_err(|err| err.to_string())?; + let count = count.try_get(0).map_err(|err| err.to_string())?; Ok(GetKanjiResult { kanji, count }) } - -#[tauri::command] -pub async fn get_single_kanji( - db: State<'_, KanjiDb>, - character: String, -) -> Result, ()> { - let result = sqlx::query( - r#" - SELECT * FROM KanjiSet - LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID - GROUP BY KanjiSet.ID - HAVING MostUsedRank IS NOT NULL - AND KanjiSet.Character = ? - "#, - ) - .bind(character) - .fetch_one(&db.0) - .await - .map_err(|_| ())?; - - let kanji = Kanji::from_sqlite_row(result); - - Ok(Some(kanji)) -} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 062e8f2..bb165d6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -62,7 +62,6 @@ async fn main() -> Result<()> { .invoke_handler(tauri::generate_handler![ srs::get_srs_stats, kanji::get_kanji, - kanji::get_single_kanji ]) .on_window_event(|event| match event.event() { WindowEvent::CloseRequested { api, .. } => { diff --git a/src/components/DashboardReviewStats.tsx b/src/components/DashboardReviewStats.tsx index 5645c8b..a659b07 100644 --- a/src/components/DashboardReviewStats.tsx +++ b/src/components/DashboardReviewStats.tsx @@ -8,12 +8,14 @@ import { StatHelpText, StatLabel, StatNumber, + Tooltip, } from "@chakra-ui/react"; import { ArrowRightIcon } from "@chakra-ui/icons"; import styles from "./DashboardReviewStats.module.scss"; import useSWR from "swr"; import { invoke } from "@tauri-apps/api/tauri"; import { Link } from "react-router-dom"; +import ConditionalWrapper from "../lib/ConditionalWrapper"; interface SrsStats { reviews_available: number; @@ -42,9 +44,11 @@ export default function DashboardReviewStats() { ); - const averageSuccess = srsStats.num_success / (srsStats.num_success + srsStats.num_failure); + const averageSuccess = srsStats.num_success / ((srsStats.num_success + srsStats.num_failure) || 1); const averageSuccessStr = `${Math.round(averageSuccess * 10000) / 100}%`; + const canReview = srsStats.reviews_available == 0; + const generateStat = (stat) => { return ( @@ -63,11 +67,13 @@ export default function DashboardReviewStats() { reviews available {srsStats.reviews_available} - - - + {children}}> + + + + diff --git a/src/components/KanjiDisplay.tsx b/src/components/KanjiDisplay.tsx index 1dd842e..1d4d841 100644 --- a/src/components/KanjiDisplay.tsx +++ b/src/components/KanjiDisplay.tsx @@ -3,8 +3,8 @@ import { GetKanjiResult } from "../panes/KanjiPane"; import { Kanji } from "../types/Kanji"; import styles from "./KanjiDisplay.module.scss"; import useSWR from "swr"; - -type GetSingleKanjiResult = Kanji; +import { Button } from "@chakra-ui/button"; +import { AddIcon } from "@chakra-ui/icons"; interface KanjiDisplayProps { kanjiCharacter: string; @@ -12,20 +12,33 @@ interface KanjiDisplayProps { export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) { const { - data: kanji, + data: kanjiResult, error, isLoading, - } = useSWR(["get_single_kanji", kanjiCharacter], ([command, character]) => - invoke(command, { character }), + } = useSWR(["get_kanji", kanjiCharacter], ([command, character]) => + invoke(command, { options: { character } }), ); - if (!kanji) return <>Loading...; + if (!kanjiResult || !kanjiResult.kanji) return <> + {JSON.stringify([kanjiResult, error, isLoading])} + Loading... + ; + + const kanji = kanjiResult.kanji[0]; + + const addSrsItem = () => { + + }; return ( <>
{kanji.character}
- {kanji.meaning} + {kanji.meanings.map(m => m.meaning).join(", ")} + + ); } diff --git a/src/lib/ConditionalWrapper.tsx b/src/lib/ConditionalWrapper.tsx new file mode 100644 index 0000000..1c875e8 --- /dev/null +++ b/src/lib/ConditionalWrapper.tsx @@ -0,0 +1,3 @@ +export default function ConditionalWrapper({ condition, wrapper, children }) { + return condition ? wrapper(children) : children +} \ No newline at end of file diff --git a/src/panes/KanjiPane.tsx b/src/panes/KanjiPane.tsx index 5d19e0b..ed500f4 100644 --- a/src/panes/KanjiPane.tsx +++ b/src/panes/KanjiPane.tsx @@ -39,7 +39,7 @@ function KanjiList({ data, selectedCharacter }: KanjiListProps) { {kanji.character} - {kanji.meaning} + {kanji.meanings[0].meaning} #{kanji.most_used_rank} most used diff --git a/src/types/Kanji.ts b/src/types/Kanji.ts index 171d3ca..ba1f8f7 100644 --- a/src/types/Kanji.ts +++ b/src/types/Kanji.ts @@ -1,5 +1,10 @@ export interface Kanji { character: string; - meaning: string; most_used_rank: number; + meanings: KanjiMeaning[]; +} + +export interface KanjiMeaning { + id: number; + meaning: string; }