multiple meanings

This commit is contained in:
Michael Zhang 2023-06-11 15:08:17 -05:00
parent aae088ddb9
commit 1fe57e2bb6
7 changed files with 135 additions and 72 deletions

View file

@ -1,4 +1,4 @@
use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; use sqlx::{sqlite::SqliteRow, Encode, Row, SqlitePool, Type};
use tauri::State; use tauri::State;
pub struct KanjiDb(pub SqlitePool); pub struct KanjiDb(pub SqlitePool);
@ -7,15 +7,28 @@ pub struct KanjiDb(pub SqlitePool);
#[derivative(Default)] #[derivative(Default)]
pub struct GetKanjiOptions { pub struct GetKanjiOptions {
#[serde(default)] #[serde(default)]
character: Option<String>,
#[serde(default = "default_how_many")]
#[derivative(Default(value = "10"))] #[derivative(Default(value = "10"))]
how_many: u32, how_many: u32,
} }
fn default_how_many() -> u32 {
10
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Kanji { pub struct Kanji {
character: String, character: String,
meaning: String,
most_used_rank: u32, most_used_rank: u32,
meanings: Vec<KanjiMeaning>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KanjiMeaning {
id: u32,
meaning: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -24,73 +37,97 @@ pub struct GetKanjiResult {
kanji: Vec<Kanji>, kanji: Vec<Kanji>,
} }
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] #[tauri::command]
pub async fn get_kanji( pub async fn get_kanji(
db: State<'_, KanjiDb>, db: State<'_, KanjiDb>,
options: Option<GetKanjiOptions>, options: Option<GetKanjiOptions>,
) -> Result<GetKanjiResult, ()> { ) -> Result<GetKanjiResult, String> {
let opts = options.unwrap_or_default(); 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#" r#"
SELECT * FROM KanjiSet SELECT *, KanjiMeaningSet.ID as KanjiMeaningID
LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID FROM (
GROUP BY KanjiSet.ID SELECT *
HAVING MostUsedRank IS NOT NULL FROM KanjiSet
ORDER BY MostUsedRank WHERE MostUsedRank IS NOT NULL
LIMIT ? {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)
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) .fetch_all(&db.0)
.await .await
.map_err(|_| ())?; .map_err(|err| err.to_string())?;
let kanji = result.into_iter().map(Kanji::from_sqlite_row).collect(); let kanji = {
let mut new_vec: Vec<Kanji> = Vec::with_capacity(result.len());
let mut last_character: Option<String> = 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") let count = sqlx::query("SELECT COUNT(*) FROM KanjiSet")
.fetch_one(&db.0) .fetch_one(&db.0)
.await .await
.map_err(|_| ())?; .map_err(|err| err.to_string())?;
let count = count.try_get(0).map_err(|_| ())?; let count = count.try_get(0).map_err(|err| err.to_string())?;
Ok(GetKanjiResult { kanji, count }) Ok(GetKanjiResult { kanji, count })
} }
#[tauri::command]
pub async fn get_single_kanji(
db: State<'_, KanjiDb>,
character: String,
) -> Result<Option<Kanji>, ()> {
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))
}

View file

@ -62,7 +62,6 @@ async fn main() -> Result<()> {
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
srs::get_srs_stats, srs::get_srs_stats,
kanji::get_kanji, kanji::get_kanji,
kanji::get_single_kanji
]) ])
.on_window_event(|event| match event.event() { .on_window_event(|event| match event.event() {
WindowEvent::CloseRequested { api, .. } => { WindowEvent::CloseRequested { api, .. } => {

View file

@ -8,12 +8,14 @@ import {
StatHelpText, StatHelpText,
StatLabel, StatLabel,
StatNumber, StatNumber,
Tooltip,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { ArrowRightIcon } from "@chakra-ui/icons"; import { ArrowRightIcon } from "@chakra-ui/icons";
import styles from "./DashboardReviewStats.module.scss"; import styles from "./DashboardReviewStats.module.scss";
import useSWR from "swr"; import useSWR from "swr";
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import ConditionalWrapper from "../lib/ConditionalWrapper";
interface SrsStats { interface SrsStats {
reviews_available: number; 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 averageSuccessStr = `${Math.round(averageSuccess * 10000) / 100}%`;
const canReview = srsStats.reviews_available == 0;
const generateStat = (stat) => { const generateStat = (stat) => {
return ( return (
<GridItem> <GridItem>
@ -63,11 +67,13 @@ export default function DashboardReviewStats() {
<Stat> <Stat>
<StatLabel>reviews available</StatLabel> <StatLabel>reviews available</StatLabel>
<StatNumber>{srsStats.reviews_available}</StatNumber> <StatNumber>{srsStats.reviews_available}</StatNumber>
<ConditionalWrapper condition={canReview} wrapper={children => <Tooltip label="Add items to start reviewing">{children}</Tooltip>}>
<Link to="/srs/review"> <Link to="/srs/review">
<Button disabled={srsStats.reviews_available == 0} colorScheme="blue"> <Button isDisabled={canReview} colorScheme="blue">
Start reviewing <ArrowRightIcon marginLeft={3} /> Start reviewing <ArrowRightIcon marginLeft={3} />
</Button> </Button>
</Link> </Link>
</ConditionalWrapper>
</Stat> </Stat>
</GridItem> </GridItem>

View file

@ -3,8 +3,8 @@ import { GetKanjiResult } from "../panes/KanjiPane";
import { Kanji } from "../types/Kanji"; import { Kanji } from "../types/Kanji";
import styles from "./KanjiDisplay.module.scss"; import styles from "./KanjiDisplay.module.scss";
import useSWR from "swr"; import useSWR from "swr";
import { Button } from "@chakra-ui/button";
type GetSingleKanjiResult = Kanji; import { AddIcon } from "@chakra-ui/icons";
interface KanjiDisplayProps { interface KanjiDisplayProps {
kanjiCharacter: string; kanjiCharacter: string;
@ -12,20 +12,33 @@ interface KanjiDisplayProps {
export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) { export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
const { const {
data: kanji, data: kanjiResult,
error, error,
isLoading, isLoading,
} = useSWR(["get_single_kanji", kanjiCharacter], ([command, character]) => } = useSWR(["get_kanji", kanjiCharacter], ([command, character]) =>
invoke<GetSingleKanjiResult>(command, { character }), invoke<GetKanjiResult>(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 ( return (
<> <>
<div className={styles.display}>{kanji.character}</div> <div className={styles.display}>{kanji.character}</div>
{kanji.meaning} {kanji.meanings.map(m => m.meaning).join(", ")}
<Button onClick={addSrsItem} colorScheme="green">
<AddIcon /> Add to SRS
</Button>
</> </>
); );
} }

View file

@ -0,0 +1,3 @@
export default function ConditionalWrapper({ condition, wrapper, children }) {
return condition ? wrapper(children) : children
}

View file

@ -39,7 +39,7 @@ function KanjiList({ data, selectedCharacter }: KanjiListProps) {
<GridItem rowSpan={2} style={{ fontSize: "24px", textAlign: "center" }}> <GridItem rowSpan={2} style={{ fontSize: "24px", textAlign: "center" }}>
{kanji.character} {kanji.character}
</GridItem> </GridItem>
<GridItem>{kanji.meaning}</GridItem> <GridItem>{kanji.meanings[0].meaning}</GridItem>
<GridItem> <GridItem>
<small> <small>
#{kanji.most_used_rank} most used #{kanji.most_used_rank} most used

View file

@ -1,5 +1,10 @@
export interface Kanji { export interface Kanji {
character: string; character: string;
meaning: string;
most_used_rank: number; most_used_rank: number;
meanings: KanjiMeaning[];
}
export interface KanjiMeaning {
id: number;
meaning: string;
} }