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;
pub struct KanjiDb(pub SqlitePool);
@ -7,15 +7,28 @@ pub struct KanjiDb(pub SqlitePool);
#[derivative(Default)]
pub struct GetKanjiOptions {
#[serde(default)]
character: Option<String>,
#[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<KanjiMeaning>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KanjiMeaning {
id: u32,
meaning: String,
}
#[derive(Debug, Serialize, Deserialize)]
@ -24,73 +37,97 @@ pub struct GetKanjiResult {
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]
pub async fn get_kanji(
db: State<'_, KanjiDb>,
options: Option<GetKanjiOptions>,
) -> Result<GetKanjiResult, ()> {
) -> Result<GetKanjiResult, String> {
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)
);
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(|_| ())?;
.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")
.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<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![
srs::get_srs_stats,
kanji::get_kanji,
kanji::get_single_kanji
])
.on_window_event(|event| match event.event() {
WindowEvent::CloseRequested { api, .. } => {

View file

@ -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 (
<GridItem>
@ -63,11 +67,13 @@ export default function DashboardReviewStats() {
<Stat>
<StatLabel>reviews available</StatLabel>
<StatNumber>{srsStats.reviews_available}</StatNumber>
<ConditionalWrapper condition={canReview} wrapper={children => <Tooltip label="Add items to start reviewing">{children}</Tooltip>}>
<Link to="/srs/review">
<Button disabled={srsStats.reviews_available == 0} colorScheme="blue">
<Button isDisabled={canReview} colorScheme="blue">
Start reviewing <ArrowRightIcon marginLeft={3} />
</Button>
</Link>
</ConditionalWrapper>
</Stat>
</GridItem>

View file

@ -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<GetSingleKanjiResult>(command, { character }),
} = useSWR(["get_kanji", kanjiCharacter], ([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 (
<>
<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" }}>
{kanji.character}
</GridItem>
<GridItem>{kanji.meaning}</GridItem>
<GridItem>{kanji.meanings[0].meaning}</GridItem>
<GridItem>
<small>
#{kanji.most_used_rank} most used

View file

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