diff --git a/rustfmt.toml b/rustfmt.toml index a3be6a8..86fef46 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ max_width = 80 tab_spaces = 2 wrap_comments = true +fn_single_line = true \ No newline at end of file diff --git a/src-tauri/src/kanji.rs b/src-tauri/src/kanji.rs index 72d4f93..7b5cc09 100644 --- a/src-tauri/src/kanji.rs +++ b/src-tauri/src/kanji.rs @@ -16,16 +16,23 @@ pub struct GetKanjiOptions { #[serde(default)] character: Option, + #[serde(default = "default_skip")] + #[derivative(Default(value = "0"))] + skip: u32, + #[serde(default = "default_how_many")] - #[derivative(Default(value = "10"))] + #[derivative(Default(value = "40"))] how_many: u32, #[serde(default)] include_srs_info: bool, } +fn default_skip() -> u32 { + 0 +} fn default_how_many() -> u32 { - 10 + 40 } #[derive(Debug, Serialize, Deserialize)] @@ -62,6 +69,7 @@ pub async fn get_kanji( options: Option, ) -> Result { let opts = options.unwrap_or_default(); + println!("opts: {opts:?}"); let looking_for_character_clause = match opts.character { None => String::new(), @@ -80,7 +88,7 @@ pub async fn get_kanji( FROM KanjiSet WHERE MostUsedRank IS NOT NULL {looking_for_character_clause} - ORDER BY MostUsedRank LIMIT ? + ORDER BY MostUsedRank LIMIT ?, ? ) as Kanji JOIN KanjiMeaningSet ON Kanji.ID = KanjiMeaningSet.Kanji_ID ORDER BY MostUsedRank, KanjiMeaningSet.ID @@ -94,6 +102,7 @@ pub async fn get_kanji( if let Some(character) = &opts.character { query = query.bind(character.clone()); } + query = query.bind(opts.skip); query = query.bind(opts.how_many); let result = query diff --git a/src/App.module.scss b/src/App.module.scss index 25a83d1..7533608 100644 --- a/src/App.module.scss +++ b/src/App.module.scss @@ -3,9 +3,10 @@ $navLinkColor: hsl(203, 91%, 91%); .main { min-height: 0; - display: inline-flex; + display: flex; flex-direction: column; - height: 100%; + + height: 100vh; } .header { @@ -23,9 +24,8 @@ $navLinkColor: hsl(203, 91%, 91%); .body { min-height: 0; - flex-basis: 0px; flex-grow: 1; - display: inline-flex; + display: flex; } .link { @@ -50,4 +50,4 @@ $navLinkColor: hsl(203, 91%, 91%); .link-active { border-top-color: $navLinkAccentColor; background-color: $navLinkColor; -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 0c090d2..dc77e4e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,7 +60,12 @@ export default function App() { }); } else { return ( - + ); } })} @@ -84,7 +89,7 @@ const navLinks = [ title: "SRS", element: , subPaths: [ - { key: "index", path: "/srs" }, + { key: "index", path: "/" }, { key: "review", path: "/srs/review", element: }, ], }, diff --git a/src/components/KanjiList.module.scss b/src/components/KanjiList.module.scss index 637d698..b677847 100644 --- a/src/components/KanjiList.module.scss +++ b/src/components/KanjiList.module.scss @@ -9,7 +9,15 @@ .kanji-link { padding: 4px 8px; border-left: 4px solid transparent; + transition: background-color 0.1s ease-out, border-left-color 0.1s ease-out; + user-select: none; + -webkit-user-drag: none; + + &:not(.kanji-link-active):hover { + border-top-color: transparent; + background-color: #f4f4f4; + } } .kanji-list-scroll { @@ -28,4 +36,17 @@ .kanji-link-active { border-left-color: #09c; background-color: hsl(203, 91%, 91%); -} \ No newline at end of file +} + +.loading { + padding: 4px 8px; + text-align: center; +} + +.result-count { + padding: 4px 8px; +} + +.search-container { + padding: 4px 8px; +} diff --git a/src/components/KanjiList.tsx b/src/components/KanjiList.tsx index 7c8a863..812772c 100644 --- a/src/components/KanjiList.tsx +++ b/src/components/KanjiList.tsx @@ -4,13 +4,54 @@ import { Link } from "react-router-dom"; import { Badge, Grid, GridItem } from "@chakra-ui/layout"; import styles from "./KanjiList.module.scss"; import { Kanji } from "../types/Kanji"; +import { Input, Spinner } from "@chakra-ui/react"; +import { useCallback, useEffect, useState } from "react"; export interface KanjiListProps { - data: GetKanjiResult; + kanjiList: Kanji[]; + totalCount: number; selectedCharacter?: string; + loadMoreKanji: () => void; } -export function KanjiList({ data, selectedCharacter }: KanjiListProps) { +export function KanjiList({ + kanjiList, + totalCount, + selectedCharacter, + loadMoreKanji, +}: KanjiListProps) { + // Set up intersection observer + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [loadingCanary, setLoadingCanary] = useState(null); + const loadingCanaryRef = useCallback( + (element) => { + if (element) setLoadingCanary(element); + }, + [setLoadingCanary], + ); + useEffect(() => { + if (loadingCanary && !isLoadingMore) { + const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + console.log("loading more shit"); + loadMoreKanji(); + setIsLoadingMore(true); + } + } + }); + observer.observe(loadingCanary); + + return () => { + observer.unobserve(loadingCanary); + }; + } + }, [loadingCanary, isLoadingMore]); + + useEffect(() => { + setIsLoadingMore(false); + }, [kanjiList]); + const renderKanjiItem = (kanji: Kanji, active: boolean) => { const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]); return ( @@ -21,7 +62,7 @@ export function KanjiList({ data, selectedCharacter }: KanjiListProps) { {kanji.meanings[0].meaning} - #{kanji.most_used_rank} most used + #{kanji.most_used_rank} common @@ -30,16 +71,24 @@ export function KanjiList({ data, selectedCharacter }: KanjiListProps) { return ( <> - - Displaying {data.kanji.length} of {data.count} results. +
+ +
+ + + Displaying {kanjiList.length} of {totalCount} results.
- {data.kanji.map((kanji) => { + {kanjiList.map((kanji) => { const active = kanji.character == selectedCharacter; return renderKanjiItem(kanji, active); })} + +
+ +
diff --git a/src/panes/KanjiPane.module.scss b/src/panes/KanjiPane.module.scss index cceee8c..d66e5ac 100644 --- a/src/panes/KanjiPane.module.scss +++ b/src/panes/KanjiPane.module.scss @@ -1,16 +1,18 @@ .kanji-pane-container { display: flex; + align-items: stretch; width: 100%; + height: 100%; } -.kanji-list { - // display: flex; - // flex-direction: column; +.kanji-pane-list { + display: flex; + flex-direction: column; min-height: 0; - width: 240px; + min-width: 280px; } .right-side { flex-grow: 5; -} \ No newline at end of file +} diff --git a/src/panes/KanjiPane.tsx b/src/panes/KanjiPane.tsx index 58cc762..4864e83 100644 --- a/src/panes/KanjiPane.tsx +++ b/src/panes/KanjiPane.tsx @@ -8,6 +8,7 @@ import KanjiDisplay from "../components/KanjiDisplay"; import { Kanji } from "../types/Kanji"; import classNames from "classnames"; import { KanjiList } from "../components/KanjiList"; +import { useEffect, useState } from "react"; export interface GetKanjiResult { count: number; @@ -16,19 +17,43 @@ export interface GetKanjiResult { export default function KanjiPane() { const { selectedKanji } = useParams(); - const { data, error, isLoading } = useSWR("get_kanji", invoke); + const { data: baseData, error, isLoading } = useSWR("get_kanji", invoke); + + const [totalCount, setTotalCount] = useState(0); + const [kanjiList, setKanjiList] = useState([]); + + // Set the base info + useEffect(() => { + if (baseData) { + setTotalCount(baseData.count); + setKanjiList(baseData.kanji); + } + }, [baseData]); if (error) { console.error(error); } + const loadMoreKanji = async () => { + const result = await invoke("get_kanji", { options: {skip: kanjiList.length }}); + console.log("invoked result", result); + setKanjiList([...kanjiList, ...result.kanji]) + }; + return ( - - - {data && } + + + {kanjiList && ( + + )} - + {selectedKanji ? : "nothing selected"} diff --git a/src/styles.css b/src/styles.css index b19b318..f57d265 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5,10 +5,11 @@ html, body { width: 100%; + height: 100%; padding: 0; } body { overflow: hidden; -} \ No newline at end of file +}