eslint + more vocab
This commit is contained in:
parent
5987e746db
commit
00ec52a9a4
30 changed files with 3846 additions and 660 deletions
14
eslint.config.js
Normal file
14
eslint.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import js from "@eslint/js";
|
||||
import reactRecommended from "eslint-plugin-react/configs/recommended.js";
|
||||
import reactJsxRuntime from "eslint-plugin-react/configs/jsx-runtime.js";
|
||||
|
||||
export default [
|
||||
{ ignores: ["src-tauri/**/*", "dist/**/*"] },
|
||||
{ settings: { react: { version: "detect" } } },
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
rules: js.configs.recommended.rules,
|
||||
},
|
||||
reactRecommended,
|
||||
reactJsxRuntime,
|
||||
];
|
4218
package-lock.json
generated
4218
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -18,7 +18,6 @@
|
|||
"chakra-react-select": "^4.6.0",
|
||||
"classnames": "^2.3.2",
|
||||
"date-fns": "^2.30.0",
|
||||
"flowbite": "^1.6.5",
|
||||
"framer-motion": "^10.12.16",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
|
@ -29,19 +28,23 @@
|
|||
"swr": "^2.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^8.42.0",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-timeago": "^4.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||
"@typescript-eslint/parser": "^5.59.11",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"sass": "^1.62.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.2.1"
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
|
@ -1643,6 +1643,7 @@ dependencies = [
|
|||
"derivative",
|
||||
"dirs",
|
||||
"flate2",
|
||||
"itertools",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
|
|
|
@ -26,6 +26,7 @@ derivative = "2.2.0"
|
|||
flate2 = "1.0.26"
|
||||
base64 = "0.21.2"
|
||||
tantivy = { version = "0.20.2", default-features = false, features = ["memmap2"] }
|
||||
itertools = "0.10.5"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
|
|
@ -50,6 +50,7 @@ fn default_how_many() -> u32 {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Kanji {
|
||||
id: u32,
|
||||
character: String,
|
||||
meanings: Vec<KanjiMeaning>,
|
||||
srs_info: Option<KanjiSrsInfo>,
|
||||
|
@ -270,6 +271,7 @@ pub async fn get_kanji(
|
|||
}
|
||||
|
||||
new_vec.push(Kanji {
|
||||
id,
|
||||
character,
|
||||
meanings: vec![meaning],
|
||||
|
||||
|
|
|
@ -12,13 +12,12 @@ pub mod utils;
|
|||
pub mod vocab;
|
||||
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
use tauri::{
|
||||
CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
SystemTrayMenuItem, WindowEvent,
|
||||
WindowEvent,
|
||||
};
|
||||
use tokio::fs;
|
||||
|
||||
|
@ -78,6 +77,7 @@ async fn main() -> Result<()> {
|
|||
srs::generate_review_batch,
|
||||
srs::update_srs_item,
|
||||
kanji::get_kanji,
|
||||
vocab::get_vocab,
|
||||
utils::application_info,
|
||||
])
|
||||
.on_window_event(|event| match event.event() {
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
use sqlx::Row;
|
||||
use tauri::State;
|
||||
|
||||
use crate::kanji::KanjiDb;
|
||||
|
||||
#[derive(Debug, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Default)]
|
||||
pub struct GetVocabOptions {
|
||||
#[serde(default)]
|
||||
kanji_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Vocab {
|
||||
kanji_writing: String,
|
||||
|
@ -21,8 +15,16 @@ pub struct Vocab {
|
|||
frequency_rank: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Default)]
|
||||
pub struct GetVocabOptions {
|
||||
#[serde(default)]
|
||||
kanji_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GetVocabResult {
|
||||
count: u32,
|
||||
vocab: Vec<Vocab>,
|
||||
}
|
||||
|
||||
|
@ -33,5 +35,46 @@ pub async fn get_vocab(
|
|||
) -> Result<GetVocabResult, String> {
|
||||
let opts = options.unwrap_or_default();
|
||||
|
||||
Ok(GetVocabResult { vocab: vec![] })
|
||||
let query_string = format!(
|
||||
r#"
|
||||
"#
|
||||
);
|
||||
|
||||
// Count query
|
||||
let count = {
|
||||
let kanji_filter_clause = match opts.kanji_id {
|
||||
Some(_) => format!(
|
||||
r#"
|
||||
JOIN KanjiEntityVocabEntity ON KanjiEntityVocabEntity.Vocabs_ID = VocabSet.ID
|
||||
WHERE KanjiEntityVocabEntity.Kanji_ID = ?
|
||||
"#
|
||||
),
|
||||
None => String::new(),
|
||||
};
|
||||
let count_query_string = format!(
|
||||
r#"
|
||||
SELECT COUNT(*) AS Count
|
||||
FROM VocabSet
|
||||
{kanji_filter_clause}
|
||||
"#
|
||||
);
|
||||
println!(
|
||||
"count query: {count_query_string}\n bind {:?}",
|
||||
opts.kanji_id
|
||||
);
|
||||
let mut count_query = sqlx::query(&count_query_string);
|
||||
if let Some(kanji_id) = opts.kanji_id {
|
||||
count_query = count_query.bind(kanji_id);
|
||||
}
|
||||
let row = count_query
|
||||
.fetch_one(&kanji_db.0)
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
row.get("Count")
|
||||
};
|
||||
|
||||
Ok(GetVocabResult {
|
||||
count,
|
||||
vocab: vec![],
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Link, RouterProvider, createHashRouter } from "react-router-dom";
|
||||
import { Link, RouterProvider } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { ChakraProvider, Flex } from "@chakra-ui/react";
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { Button, Grid, GridItem, Stat, 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 "./utils/ConditionalWrapper";
|
||||
import ReactTimeago, { Formatter } from "react-timeago";
|
||||
import { isValid } from "date-fns";
|
||||
import { SrsStats } from "../panes/SrsPane";
|
||||
|
||||
export interface DashboardReviewStatsProps {
|
||||
|
@ -15,7 +11,7 @@ export interface DashboardReviewStatsProps {
|
|||
|
||||
interface Stat {
|
||||
label: string;
|
||||
value: any;
|
||||
value: string | number | JSX.Element;
|
||||
}
|
||||
|
||||
export default function DashboardReviewStats({ srsStats }: DashboardReviewStatsProps) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
$kanjiDisplaySize: 120px;
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
@ -79,8 +82,15 @@ $kanjiDisplaySize: 120px;
|
|||
|
||||
.vocabSection {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.vocabList {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import SelectOnClick from "./utils/SelectOnClick";
|
|||
import classNames from "classnames";
|
||||
import Strokes from "./Strokes";
|
||||
import SrsPart from "./SrsPart";
|
||||
import VocabList from "./VocabList";
|
||||
|
||||
interface KanjiDisplayProps {
|
||||
kanjiCharacter: string;
|
||||
|
@ -44,11 +45,6 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<details>
|
||||
<summary>Debug</summary>
|
||||
<pre>{JSON.stringify(kanji, null, 2)}</pre>
|
||||
</details>
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.topRow}>
|
||||
<SelectOnClick className={styles.display}>{kanji.character}</SelectOnClick>
|
||||
|
@ -89,6 +85,8 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
|||
|
||||
<div className={styles.vocabSection}>
|
||||
<h2>Related Vocab</h2>
|
||||
|
||||
<VocabList kanjiId={kanji.id} className={styles.vocabList} />
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { GetKanjiResult } from "../panes/KanjiPane";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Badge, Grid, GridItem } from "@chakra-ui/layout";
|
||||
import styles from "./KanjiList.module.scss";
|
||||
import { Kanji } from "../lib/kanji";
|
||||
import { Input, Spinner } from "@chakra-ui/react";
|
||||
import { Spinner } from "@chakra-ui/react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import SearchBar from "./SearchBar";
|
||||
import LevelBadge from "./utils/LevelBadge";
|
||||
|
||||
export interface KanjiListProps {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SearchIcon } from "@chakra-ui/icons";
|
||||
import { Input, InputGroup, InputRightElement, Spinner } from "@chakra-ui/react";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
export interface SearchBarProps {
|
||||
isLoading: boolean;
|
||||
|
|
15
src/components/VocabList.module.scss
Normal file
15
src/components/VocabList.module.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
.main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vocabList {
|
||||
flex-grow: 1;
|
||||
border: 1px solid lightgray;
|
||||
padding: 12px;
|
||||
overflow-y: scroll;
|
||||
}
|
36
src/components/VocabList.tsx
Normal file
36
src/components/VocabList.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Input, StylesProvider } from "@chakra-ui/react";
|
||||
import { GetVocabResult } from "../lib/vocab";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import useSWR from "swr";
|
||||
import styles from "./VocabList.module.scss";
|
||||
import classNames from "classnames";
|
||||
|
||||
export interface VocabListProps {
|
||||
kanjiId?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function VocabList({ className, kanjiId }: VocabListProps) {
|
||||
const { data, isLoading, error } = useSWR(["get_vocab", kanjiId], () =>
|
||||
invoke<GetVocabResult>("get_vocab", {
|
||||
options: { kanji_id: kanjiId },
|
||||
}),
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={classNames(styles.main, className)}>
|
||||
<Input placeholder="Filter..." autoFocus />
|
||||
|
||||
<div>
|
||||
Displaying {data.vocab.length} of {data.count} results. ({kanjiId})
|
||||
</div>
|
||||
|
||||
<div className={styles.vocabList}></div>
|
||||
</main>
|
||||
);
|
||||
}
|
3
src/components/utils/LevelBadge.module.scss
Normal file
3
src/components/utils/LevelBadge.module.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.badge {
|
||||
user-select: none;
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
import { Badge, BadgeProps } from "@chakra-ui/react";
|
||||
import { srsLevels } from "../../lib/srs";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./LevelBadge.module.scss";
|
||||
|
||||
export interface LevelBadgeProps extends BadgeProps {
|
||||
grade?: number;
|
||||
}
|
||||
|
||||
export default function LevelBadge({ grade, ...props }: LevelBadgeProps) {
|
||||
export default function LevelBadge({ grade, className, ...props }: LevelBadgeProps) {
|
||||
if (grade == undefined) return null;
|
||||
|
||||
const levelInfo = srsLevels.get(grade);
|
||||
|
@ -14,7 +17,12 @@ export default function LevelBadge({ grade, ...props }: LevelBadgeProps) {
|
|||
const { color, name } = levelInfo;
|
||||
|
||||
return (
|
||||
<Badge backgroundColor={color} color="white" {...props}>
|
||||
<Badge
|
||||
backgroundColor={color}
|
||||
color="white"
|
||||
className={classNames(styles.badge, className)}
|
||||
{...props}
|
||||
>
|
||||
{name}
|
||||
</Badge>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export interface Kanji {
|
||||
id: number;
|
||||
character: string;
|
||||
meanings: KanjiMeaning[];
|
||||
|
||||
|
|
6
src/lib/vocab.ts
Normal file
6
src/lib/vocab.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface GetVocabResult {
|
||||
count: number;
|
||||
vocab: Vocab[];
|
||||
}
|
||||
|
||||
export interface Vocab {}
|
|
@ -8,9 +8,10 @@
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
.kanji-pane-list {
|
||||
.kanjiPaneList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
min-height: 0;
|
||||
min-width: 280px;
|
||||
|
@ -19,3 +20,13 @@
|
|||
.right-side {
|
||||
flex-grow: 5;
|
||||
}
|
||||
|
||||
.searchContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.advancedSearch {
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import useSWR from "swr";
|
||||
import { Box, Flex, Grid, GridItem, LinkBox, Stack } from "@chakra-ui/layout";
|
||||
import { Box, Flex } from "@chakra-ui/layout";
|
||||
|
||||
import styles from "./KanjiPane.module.scss";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
@ -9,6 +9,7 @@ import { Kanji } from "../lib/kanji";
|
|||
import { KanjiList } from "../components/KanjiList";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchBar from "../components/SearchBar";
|
||||
import { QuestionIcon, UpDownIcon } from "@chakra-ui/icons";
|
||||
|
||||
export interface GetKanjiResult {
|
||||
count: number;
|
||||
|
@ -55,9 +56,17 @@ export function Component() {
|
|||
|
||||
return (
|
||||
<Flex className={styles["kanji-pane-container"]}>
|
||||
<Box className={styles["kanji-pane-list"]}>
|
||||
<div className={styles["search-container"]}>
|
||||
<Box className={styles.kanjiPaneList}>
|
||||
<div className={styles.searchContainer}>
|
||||
<SearchBar isLoading={isLoading} setSearchQuery={setSearchQuery} />
|
||||
|
||||
<details>
|
||||
<summary className={styles.advancedSearch}>Advanced</summary>
|
||||
|
||||
<a href="">
|
||||
Help <QuestionIcon />
|
||||
</a>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
{kanjiList && (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import useSWR from "swr";
|
||||
import SelectOnClick from "../components/utils/SelectOnClick";
|
||||
|
||||
interface ApplicationInfo {
|
||||
|
|
|
@ -20,11 +20,11 @@ export interface SrsStats {
|
|||
}
|
||||
|
||||
export function Component() {
|
||||
const {
|
||||
data: srsStats,
|
||||
error,
|
||||
isLoading,
|
||||
} = useSWR(["get_srs_stats"], ([command]) => invoke<SrsStats>(command));
|
||||
const { data: srsStats, error } = useSWR(["get_srs_stats"], ([command]) =>
|
||||
invoke<SrsStats>(command),
|
||||
);
|
||||
|
||||
if (error) console.error(error);
|
||||
|
||||
if (!srsStats) return <>Loading...</>;
|
||||
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
import {
|
||||
Button,
|
||||
Container,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
InputRightElement,
|
||||
Progress,
|
||||
Spinner,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { Button, Container, Progress, Spinner, useDisclosure } from "@chakra-ui/react";
|
||||
import styles from "./SrsReviewPane.module.scss";
|
||||
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { Link, ScrollRestoration, useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ArrowBackIcon } from "@chakra-ui/icons";
|
||||
import ConfirmQuitModal from "../components/utils/ConfirmQuitModal";
|
||||
import * as _ from "lodash-es";
|
||||
import { romajiToKana } from "../lib/kanaHelper";
|
||||
import {
|
||||
ReviewItem,
|
||||
ReviewItemType,
|
||||
|
@ -26,12 +15,9 @@ import {
|
|||
groupUpdatedLevel,
|
||||
isGroupCorrect,
|
||||
} from "../lib/srs";
|
||||
import classNames from "classnames";
|
||||
import InputBox from "../components/srsReview/InputBox";
|
||||
import SelectOnClick from "../components/utils/SelectOnClick";
|
||||
|
||||
const batchSize = 10;
|
||||
|
||||
export function Component() {
|
||||
// null = has not started, (.length == 0) = finished
|
||||
const [reviewQueue, setReviewQueue] = useState<ReviewItem[] | null>(null);
|
||||
|
@ -124,7 +110,7 @@ export function Component() {
|
|||
delay: newLevel.delay,
|
||||
};
|
||||
|
||||
const result = await invoke("update_srs_item", params);
|
||||
await invoke("update_srs_item", params);
|
||||
}
|
||||
|
||||
// If it's wrong this time
|
||||
|
|
4
src/panes/VocabPane.module.scss
Normal file
4
src/panes/VocabPane.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.main {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
}
|
|
@ -1,5 +1,12 @@
|
|||
import VocabList from "../components/VocabList";
|
||||
import styles from "./VocabPane.module.scss";
|
||||
|
||||
export function Component() {
|
||||
return <></>;
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<VocabList />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Component.displayName = "VocabPane";
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./src/**/*.{html,js,jsx,ts,tsx}", "./node_modules/flowbite/**/*.js"],
|
||||
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
plugins: [require("flowbite/plugin")],
|
||||
};
|
Loading…
Reference in a new issue