Compare commits

...

2 commits

Author SHA1 Message Date
Michael Zhang 4cbdc7c142 still doesn't work 2023-06-07 18:03:50 -05:00
Michael Zhang db3ef039c7 upd 2023-06-07 17:49:03 -05:00
25 changed files with 4745 additions and 230 deletions

2
.prettierignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
src-tauri

7
.prettierrc.json5 Normal file
View file

@ -0,0 +1,7 @@
{
useTabs: false,
tabWidth: 2,
singleQuote: false,
trailingComma: "all",
printWidth: 100,
}

4481
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,13 @@
"tauri": "tauri"
},
"dependencies": {
"@chakra-ui/react": "^2.7.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@tauri-apps/api": "^1.3.0",
"classnames": "^2.3.2",
"flowbite": "^1.6.5",
"framer-motion": "^10.12.16",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.11.2",
@ -23,7 +29,10 @@
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"sass": "^1.62.1",
"tailwindcss": "^3.3.2",
"typescript": "^4.9.5",
"vite": "^4.2.1"
}

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

36
src-tauri/Cargo.lock generated
View file

@ -1656,12 +1656,46 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libappindicator"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8"
dependencies = [
"glib",
"gtk",
"gtk-sys",
"libappindicator-sys",
"log",
]
[[package]]
name = "libappindicator-sys"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa"
dependencies = [
"gtk-sys",
"libloading",
"once_cell",
]
[[package]]
name = "libc"
version = "0.2.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libsqlite3-sys"
version = "0.24.2"
@ -3048,6 +3082,7 @@ dependencies = [
"core-foundation",
"core-graphics",
"crossbeam-channel",
"dirs-next",
"dispatch",
"gdk",
"gdk-pixbuf",
@ -3062,6 +3097,7 @@ dependencies = [
"instant",
"jni",
"lazy_static",
"libappindicator",
"libc",
"log",
"ndk",

View file

@ -14,7 +14,7 @@ members = ["database-maker"]
tauri-build = { version = "1.3", features = [] }
[dependencies]
tauri = { version = "1.3", features = ["shell-open"] }
tauri = { version = "1.3", features = ["shell-open", "system-tray"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dirs = "5.0.1"

View file

@ -1,4 +1,4 @@
use sqlx::{Row, SqlitePool};
use sqlx::{sqlite::SqliteRow, Row, SqlitePool};
use tauri::State;
pub struct KanjiDb(pub SqlitePool);
@ -6,42 +6,86 @@ pub struct KanjiDb(pub SqlitePool);
#[derive(Debug, Derivative, Serialize, Deserialize)]
#[derivative(Default)]
pub struct GetKanjiOptions {
#[serde(default)]
#[derivative(Default(value = "10"))]
how_many: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Kanji {
character: String,
meaning: String,
most_used_rank: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetKanjiResult {
count: u32,
kanji: Vec<String>,
kanji: Vec<Kanji>,
}
fn build_kanji(row: SqliteRow) -> Kanji {
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(
state: State<'_, KanjiDb>,
db: State<'_, KanjiDb>,
options: Option<GetKanjiOptions>,
) -> Result<GetKanjiResult, ()> {
let opts = options.unwrap_or_default();
let result = sqlx::query(
r#"SELECT * FROM KanjiSet
LEFT JOIN KanjiMeaningSet ON KanjiSet.ID = KanjiMeaningSet.Kanji_ID
WHERE MostUsedRank IS NOT NULL
ORDER BY MostUsedRank
LIMIT ?"#,
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 ?
"#,
)
.bind(opts.how_many)
.fetch_all(&state.0)
.fetch_all(&db.0)
.await
.map_err(|_| ())?;
let kanji = result.into_iter().map(|row| row.get("Character")).collect();
let kanji = result.into_iter().map(build_kanji).collect();
let count = sqlx::query("SELECT COUNT(*) FROM KanjiSet")
.fetch_one(&state.0)
.fetch_one(&db.0)
.await
.map_err(|_| ())?;
let count = count.try_get(0).map_err(|_| ())?;
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 Kanji.Character = ?
"#,
)
.bind(character)
.fetch_one(&db.0)
.await
.map_err(|_| ())?;
Ok(Some(build_kanji(result)))
}

View file

@ -15,6 +15,7 @@ use sqlx::{
sqlite::{SqliteConnectOptions, SqlitePoolOptions},
SqlitePool,
};
use tauri::SystemTray;
use crate::kanji::KanjiDb;
@ -26,15 +27,25 @@ fn greet(name: &str) -> String {
#[tokio::main]
async fn main() -> Result<()> {
// Open kanji db
let kanji_db_options =
SqliteConnectOptions::from_str("./KanjiDatabase.sqlite")?.read_only(true);
let kanji_pool = SqlitePoolOptions::new()
.connect_with(kanji_db_options)
.await?;
// System tray
let tray = SystemTray::new();
// Build tauri
tauri::Builder::default()
.manage(KanjiDb(kanji_pool))
.invoke_handler(tauri::generate_handler![greet, kanji::get_kanji])
.system_tray(tray)
.invoke_handler(tauri::generate_handler![
greet,
kanji::get_kanji,
kanji::get_single_kanji
])
.run(tauri::generate_context!())
.context("error while running tauri application")?;

View file

@ -33,6 +33,10 @@
"security": {
"csp": null
},
"systemTray": {
"iconPath": "icons/icon.ico",
"iconAsTemplate": true
},
"windows": [
{
"fullscreen": false,
@ -43,4 +47,4 @@
}
]
}
}
}

View file

@ -1,11 +1,22 @@
main.main {
display: flex;
flex-direction: column;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-evenly;
list-style-type: none;
}
li {
padding-left: 0;
}
}
.link {
flex-grow: 1;
display: inline-block;
padding: 12px;
}
.link-active {
background-color: skyblue;
}

View file

@ -1,23 +1,39 @@
import { Link, RouterProvider, createHashRouter } from "react-router-dom";
import styles from "./App.module.scss";
import KanjiPane from "./panes/KanjiPane";
import classNames from "classnames";
import { ChakraProvider } from "@chakra-ui/react";
import HomePane from "./panes/HomePane";
import { createBrowserRouter } from "react-router-dom";
import { Outlet, Route, createRoutesFromElements } from "react-router";
import { Outlet, Route, createRoutesFromElements, matchPath, useLocation } from "react-router";
import SrsPane from "./panes/SrsPane";
import VocabPane from "./panes/VocabPane";
import SettingsPane from "./panes/SettingsPane";
import { StrictMode } from "react";
import styles from "./App.module.scss";
function Layout() {
const location = useLocation();
return (
<>
<main className={styles.main}>
<ul className={styles.header}>
{routes.map((route) => (
<li key={route.path}>
<Link to={route.path}>{route.title}</Link>
</li>
))}
{routes.map((route) => {
if (!route.title) return undefined;
const active = matchPath({ path: route.path }, location.pathname);
const className = classNames(styles.link, active && styles["link-active"]);
return (
<li key={route.path}>
<Link to={route.path} className={className}>
{route.title}
</Link>
</li>
);
})}
</ul>
<Outlet />
</>
</main>
);
}
@ -26,21 +42,26 @@ export default function App() {
createRoutesFromElements(
<Route path="/" element={<Layout />}>
{routes.map((route, idx) => (
<Route
key={route.path}
index={idx === 0}
path={route.path}
element={route.element}
/>
<Route key={route.path} index={idx === 0} path={route.path} element={route.element} />
))}
</Route>
)
</Route>,
),
);
return <RouterProvider router={router} />;
return (
<StrictMode>
<ChakraProvider>
<RouterProvider router={router} />
</ChakraProvider>
</StrictMode>
);
}
const routes = [
{ path: "/", title: "Home", element: <HomePane /> },
{ path: "/kanji", title: "Kanji", element: <KanjiPane /> },
{ key: "home", path: "/", title: "Home", element: <HomePane /> },
{ key: "srs", path: "/srs", title: "SRS", element: <SrsPane /> },
{ key: "kanji", path: "/kanji", title: "Kanji", element: <KanjiPane /> },
{ key: "kanjiSelected", path: "/kanji/:selectedKanji", element: <KanjiPane /> },
{ key: "vocab", path: "/vocab", title: "Vocab", element: <VocabPane /> },
{ key: "settings", path: "/settings", title: "Settings", element: <SettingsPane /> },
];

View file

@ -0,0 +1,10 @@
$kanjiDisplaySize: 80px;
.display {
border: 1px solid rgb(87, 87, 210);
width: $kanjiDisplaySize;
height: $kanjiDisplaySize;
font-size: $kanjiDisplaySize * 0.8;
text-align: center;
vertical-align: middle;
}

View file

@ -0,0 +1,31 @@
import { invoke } from "@tauri-apps/api/tauri";
import { GetKanjiResult } from "../panes/KanjiPane";
import { Kanji } from "../types/Kanji";
import styles from "./KanjiDisplay.module.scss";
import useSWR from "swr";
interface KanjiDisplayProps {
kanjiCharacter: string;
}
export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
const { data, error, isLoading } = useSWR(["get_single_kanji", kanjiCharacter], ([command, character]) => invoke<GetKanjiResult>(command, { character }));
return (
<>
<div className={styles.display}>{kanjiCharacter}</div>
{JSON.stringify(isLoading)}
<p>
data:
{JSON.stringify(data)}
</p>
<p>
error:
{JSON.stringify(error)}
</p>
</>
);
}

View file

@ -1,3 +0,0 @@
export default function KanjiSearch() {
}

View file

@ -1,10 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
</React.StrictMode>,
);

View file

@ -1,5 +1,3 @@
export default function HomePane() {
return <>
hellosu
</>
}
return <>hellosu</>;
}

View file

@ -1,10 +1,10 @@
$kanjiDisplaySize: 80px;
.kanji-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.kanjiDisplay {
border: 1px solid rgb(87, 87, 210);
width: $kanjiDisplaySize;
height: $kanjiDisplaySize;
font-size: $kanjiDisplaySize * 0.8;
text-align: center;
vertical-align: middle;
}
.kanji-link {
padding: 4px 8px;
border: 1px solid lightgray;
}

View file

@ -1,36 +1,63 @@
import { useState } from "react";
import { Kanji } from "../types/Kanji";
import { invoke } from "@tauri-apps/api/tauri";
import useSWR from "swr";
import styles from "./KanjiPane.module.scss";
import { Box, Grid, GridItem, LinkBox, Stack } from "@chakra-ui/layout";
interface GetKanjiResult {
import styles from "./KanjiPane.module.scss";
import { Link, useParams } from "react-router-dom";
import KanjiDisplay from "../components/KanjiDisplay";
import { Kanji } from "../types/Kanji";
export interface GetKanjiResult {
count: number;
kanji: string[];
kanji: Kanji[];
}
function KanjiList({ data }: { data : GetKanjiResult}) {
return <>
Displaying {data.kanji.length} of {data.count} results.
interface KanjiListProps {
data: GetKanjiResult;
selectedCharacter?: string;
}
<ul>
{data.kanji.map(kanji => <li key={kanji}>
{kanji}
</li>)}
</ul>
</>
function KanjiList({ data, selectedCharacter }: KanjiListProps) {
return (
<>
Displaying {data.kanji.length} of {data.count} results.
{data.kanji.map((kanji) => (
<Link
key={kanji.character}
className={styles["kanji-link"]}
to={`/kanji/${kanji.character}`}
>
<Grid templateRows="repeat(2, 1fr)" templateColumns="1fr 3fr">
<GridItem rowSpan={2} style={{ fontSize: "24px", textAlign: "center" }}>
{kanji.character}
</GridItem>
<GridItem>{kanji.meaning}</GridItem>
<GridItem>#{kanji.most_used_rank} most used</GridItem>
</Grid>
</Link>
))}
</>
);
}
export default function KanjiPane() {
const { selectedKanji } = useParams();
const { data, error, isLoading } = useSWR("get_kanji", invoke<GetKanjiResult>);
return (
<>
{JSON.stringify(error)}
{JSON.stringify(selectedKanji)}
<div className={styles.kanjiDisplay}></div>
<Stack spacing={7} direction="row">
<Box p={2} className={styles["kanji-list"]}>
{data && <KanjiList data={data} selectedCharacter={selectedKanji} />}
</Box>
{data && <KanjiList data={data} />}
<Box p={5}>
{selectedKanji ? <KanjiDisplay kanjiCharacter={selectedKanji} /> : "nothing selected"}
</Box>
</Stack>
</>
);
}

View file

@ -0,0 +1,3 @@
export default function SettingsPane() {
return <></>;
}

View file

@ -1,3 +1,3 @@
export default function SrsPane() {
}
return <></>;
}

3
src/panes/VocabPane.tsx Normal file
View file

@ -0,0 +1,3 @@
export default function VocabPane() {
return <></>;
}

View file

@ -1,109 +1,3 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -1,4 +1,5 @@
export interface Kanji {
character: string;
translation: string;
}
meaning: string;
most_used_rank: number;
}

10
tailwind.config.js Normal file
View file

@ -0,0 +1,10 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,js,jsx,ts,tsx}", "./node_modules/flowbite/**/*.js"],
theme: {
extend: {},
},
plugins: [require("flowbite/plugin")],
};