multiple meanings
This commit is contained in:
parent
aae088ddb9
commit
1fe57e2bb6
7 changed files with 135 additions and 72 deletions
|
@ -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))
|
|
||||||
}
|
|
||||||
|
|
|
@ -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, .. } => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
3
src/lib/ConditionalWrapper.tsx
Normal file
3
src/lib/ConditionalWrapper.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function ConditionalWrapper({ condition, wrapper, children }) {
|
||||||
|
return condition ? wrapper(children) : children
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue