kana input

This commit is contained in:
Michael Zhang 2023-06-11 15:08:19 -05:00
parent 8a6dc9e339
commit 7f53fb3d27
13 changed files with 996 additions and 63 deletions

308
package-lock.json generated
View file

@ -13,7 +13,6 @@
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@tauri-apps/api": "^1.3.0", "@tauri-apps/api": "^1.3.0",
"@types/lodash-es": "^4.17.7",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"flowbite": "^1.6.5", "flowbite": "^1.6.5",
@ -28,11 +27,13 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.3.1", "@tauri-apps/cli": "^1.3.1",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.7.10", "@types/node": "^18.7.10",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-timeago": "^4.1.3", "@types/react-timeago": "^4.1.3",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.24", "postcss": "^8.4.24",
"prettier": "^2.8.8", "prettier": "^2.8.8",
@ -1829,6 +1830,200 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@swc/core": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz",
"integrity": "sha512-J58hWY+/G8vOr4J6ZH9hLg0lMSijZtqIIf4HofZezGog/pVX6sJyBJ40dZ1ploFkDIlWTWvJyqtpesBKS73gkQ==",
"dev": true,
"hasInstallScript": true,
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.3.62",
"@swc/core-darwin-x64": "1.3.62",
"@swc/core-linux-arm-gnueabihf": "1.3.62",
"@swc/core-linux-arm64-gnu": "1.3.62",
"@swc/core-linux-arm64-musl": "1.3.62",
"@swc/core-linux-x64-gnu": "1.3.62",
"@swc/core-linux-x64-musl": "1.3.62",
"@swc/core-win32-arm64-msvc": "1.3.62",
"@swc/core-win32-ia32-msvc": "1.3.62",
"@swc/core-win32-x64-msvc": "1.3.62"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz",
"integrity": "sha512-MmGilibITz68LEje6vJlKzc2gUUSgzvB3wGLSjEORikTNeM7P8jXVxE4A8fgZqDeudJUm9HVWrxCV+pHDSwXhA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.62.tgz",
"integrity": "sha512-Xl93MMB3sCWVlYWuQIB+v6EQgzoiuQYK5tNt9lsHoIEVu2zLdkQjae+5FUHZb1VYqCXIiWcULFfVz0R4Sjb7JQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.62.tgz",
"integrity": "sha512-nJsp6O7kCtAjTTMcIjVB0g5y1JNiYAa5q630eiwrnaHUusEFoANDdORI3Z9vXeikMkng+6yIv9/V8Rb093xLjQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.62.tgz",
"integrity": "sha512-XGsV93vpUAopDt5y6vPwbK1Nc/MlL55L77bAZUPIiosWD1cWWPHNtNSpriE6+I+JiMHe0pqtfS/SSTk6ZkFQVw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.62.tgz",
"integrity": "sha512-ESUmJjSlTTkoBy9dMG49opcNn8BmviqStMhwyeD1G8XRnmRVCZZgoBOKdvCXmJhw8bQXDhZumeaTUB+OFUKVXg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.62.tgz",
"integrity": "sha512-wnHJkt3ZBrax3SFnUHDcncG6mrSg9ZZjMhQV9Mc3JL1x1s1Gy9rGZCoBNnV/BUZWTemxIBcQbANRSDut/WO+9A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.62.tgz",
"integrity": "sha512-9oRbuTC/VshB66Rgwi3pTq3sPxSTIb8k9L1vJjES+dDMKa29DAjPtWCXG/pyZ00ufpFZgkGEuAHH5uqUcr1JQg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.62.tgz",
"integrity": "sha512-zv14vlF2VRrxS061XkfzGjCYnOrEo5glKJjLK5PwUKysIoVrx/L8nAbFxjkX5cObdlyoqo+ekelyBPAO+4bS0w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.62.tgz",
"integrity": "sha512-8MC/PZQSsOP2iA/81tAfNRqMWyEqTS/8zKUI67vPuLvpx6NAjRn3E9qBv7iFqH79iqZNzqSMo3awnLrKZyFbcw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.62.tgz",
"integrity": "sha512-GJSmUJ95HKHZXAxiuPUmrcm/S3ivQvEzXhOZaIqYBIwUsm02vFZkClsV7eIKzWjso1t0+I/8MjrnUNaSWqh1rQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@tauri-apps/api": { "node_modules/@tauri-apps/api": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.3.0.tgz",
@ -2023,6 +2218,7 @@
"version": "4.17.7", "version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz", "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==", "integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/lodash": "*" "@types/lodash": "*"
} }
@ -2106,6 +2302,18 @@
"vite": "^4.1.0-beta.0" "vite": "^4.1.0-beta.0"
} }
}, },
"node_modules/@vitejs/plugin-react-swc": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz",
"integrity": "sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA==",
"dev": true,
"dependencies": {
"@swc/core": "^1.3.61"
},
"peerDependencies": {
"vite": "^4"
}
},
"node_modules/@zag-js/element-size": { "node_modules/@zag-js/element-size": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.2.tgz", "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.2.tgz",
@ -5418,6 +5626,94 @@
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz",
"integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA==" "integrity": "sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA=="
}, },
"@swc/core": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz",
"integrity": "sha512-J58hWY+/G8vOr4J6ZH9hLg0lMSijZtqIIf4HofZezGog/pVX6sJyBJ40dZ1ploFkDIlWTWvJyqtpesBKS73gkQ==",
"dev": true,
"requires": {
"@swc/core-darwin-arm64": "1.3.62",
"@swc/core-darwin-x64": "1.3.62",
"@swc/core-linux-arm-gnueabihf": "1.3.62",
"@swc/core-linux-arm64-gnu": "1.3.62",
"@swc/core-linux-arm64-musl": "1.3.62",
"@swc/core-linux-x64-gnu": "1.3.62",
"@swc/core-linux-x64-musl": "1.3.62",
"@swc/core-win32-arm64-msvc": "1.3.62",
"@swc/core-win32-ia32-msvc": "1.3.62",
"@swc/core-win32-x64-msvc": "1.3.62"
}
},
"@swc/core-darwin-arm64": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz",
"integrity": "sha512-MmGilibITz68LEje6vJlKzc2gUUSgzvB3wGLSjEORikTNeM7P8jXVxE4A8fgZqDeudJUm9HVWrxCV+pHDSwXhA==",
"dev": true,
"optional": true
},
"@swc/core-darwin-x64": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.62.tgz",
"integrity": "sha512-Xl93MMB3sCWVlYWuQIB+v6EQgzoiuQYK5tNt9lsHoIEVu2zLdkQjae+5FUHZb1VYqCXIiWcULFfVz0R4Sjb7JQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm-gnueabihf": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.62.tgz",
"integrity": "sha512-nJsp6O7kCtAjTTMcIjVB0g5y1JNiYAa5q630eiwrnaHUusEFoANDdORI3Z9vXeikMkng+6yIv9/V8Rb093xLjQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm64-gnu": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.62.tgz",
"integrity": "sha512-XGsV93vpUAopDt5y6vPwbK1Nc/MlL55L77bAZUPIiosWD1cWWPHNtNSpriE6+I+JiMHe0pqtfS/SSTk6ZkFQVw==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm64-musl": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.62.tgz",
"integrity": "sha512-ESUmJjSlTTkoBy9dMG49opcNn8BmviqStMhwyeD1G8XRnmRVCZZgoBOKdvCXmJhw8bQXDhZumeaTUB+OFUKVXg==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-gnu": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.62.tgz",
"integrity": "sha512-wnHJkt3ZBrax3SFnUHDcncG6mrSg9ZZjMhQV9Mc3JL1x1s1Gy9rGZCoBNnV/BUZWTemxIBcQbANRSDut/WO+9A==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-musl": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.62.tgz",
"integrity": "sha512-9oRbuTC/VshB66Rgwi3pTq3sPxSTIb8k9L1vJjES+dDMKa29DAjPtWCXG/pyZ00ufpFZgkGEuAHH5uqUcr1JQg==",
"dev": true,
"optional": true
},
"@swc/core-win32-arm64-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.62.tgz",
"integrity": "sha512-zv14vlF2VRrxS061XkfzGjCYnOrEo5glKJjLK5PwUKysIoVrx/L8nAbFxjkX5cObdlyoqo+ekelyBPAO+4bS0w==",
"dev": true,
"optional": true
},
"@swc/core-win32-ia32-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.62.tgz",
"integrity": "sha512-8MC/PZQSsOP2iA/81tAfNRqMWyEqTS/8zKUI67vPuLvpx6NAjRn3E9qBv7iFqH79iqZNzqSMo3awnLrKZyFbcw==",
"dev": true,
"optional": true
},
"@swc/core-win32-x64-msvc": {
"version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.62.tgz",
"integrity": "sha512-GJSmUJ95HKHZXAxiuPUmrcm/S3ivQvEzXhOZaIqYBIwUsm02vFZkClsV7eIKzWjso1t0+I/8MjrnUNaSWqh1rQ==",
"dev": true,
"optional": true
},
"@tauri-apps/api": { "@tauri-apps/api": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.3.0.tgz",
@ -5512,6 +5808,7 @@
"version": "4.17.7", "version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz", "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==", "integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"dev": true,
"requires": { "requires": {
"@types/lodash": "*" "@types/lodash": "*"
} }
@ -5589,6 +5886,15 @@
"react-refresh": "^0.14.0" "react-refresh": "^0.14.0"
} }
}, },
"@vitejs/plugin-react-swc": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz",
"integrity": "sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA==",
"dev": true,
"requires": {
"@swc/core": "^1.3.61"
}
},
"@zag-js/element-size": { "@zag-js/element-size": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.2.tgz", "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.2.tgz",

View file

@ -29,12 +29,13 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.3.1", "@tauri-apps/cli": "^1.3.1",
"@types/node": "^18.7.10",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
"@types/node": "^18.7.10",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-timeago": "^4.1.3", "@types/react-timeago": "^4.1.3",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.24", "postcss": "^8.4.24",
"prettier": "^2.8.8", "prettier": "^2.8.8",

View file

@ -1,21 +1,10 @@
import { import { Button, Grid, GridItem, Stat, StatLabel, StatNumber, Tooltip } from "@chakra-ui/react";
Button,
Grid,
GridItem,
Stat,
StatArrow,
StatGroup,
StatHelpText,
StatLabel,
StatNumber,
Tooltip,
} 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"; import ConditionalWrapper from "./utils/ConditionalWrapper";
interface SrsStats { interface SrsStats {
reviews_available: number; reviews_available: number;

View file

@ -1,12 +1,11 @@
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { GetKanjiResult } from "../panes/KanjiPane"; import { GetKanjiResult } from "../panes/KanjiPane";
import { Kanji } from "../types/Kanji";
import TimeAgo from "react-timeago"; import TimeAgo from "react-timeago";
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"; import { Button } from "@chakra-ui/button";
import { AddIcon } from "@chakra-ui/icons"; import { AddIcon } from "@chakra-ui/icons";
import SelectOnClick from "../lib/SelectOnClick"; import SelectOnClick from "./utils/SelectOnClick";
import { Alert, AlertIcon } from "@chakra-ui/alert"; import { Alert, AlertIcon } from "@chakra-ui/alert";
import { isValid } from "date-fns"; import { isValid } from "date-fns";

View file

@ -0,0 +1,32 @@
import { Input as BaseInput, InputProps as BaseInputProps } from "@chakra-ui/react";
import { romajiToKana } from "../../lib/kanaHelper";
import { ChangeEvent } from "react";
export interface InputProps extends BaseInputProps {
kanaInput?: boolean;
setValue?: (_: string) => void;
defaultValue?: string;
}
export default function InputBox({
kanaInput,
value,
setValue,
onChange: baseOnChange,
...props
}: InputProps) {
const onChange = (evt: ChangeEvent<HTMLInputElement>) => {
let newValue = evt.target.value;
console.log("hellosu", kanaInput, newValue, romajiToKana(newValue));
if (kanaInput == true) newValue = romajiToKana(newValue) ?? newValue;
setValue?.(newValue);
if (baseOnChange) baseOnChange(evt);
};
return <BaseInput value={value} onChange={onChange} {...props} />;
}

562
src/data/kanadata.json Normal file
View file

@ -0,0 +1,562 @@
{
"kanaToHiragana": [
["ッ", "っ"],
["チ", "ち"],
["シ", "し"],
["ツ", "つ"],
["ヅ", "づ"],
["ヂ", "ぢ"],
["ヮ", "ゎ"],
["ャ", "ゃ"],
["ィ", "ぃ"],
["ュ", "ゅ"],
["ェ", "ぇ"],
["ョ", "ょ"],
["カ", "か"],
["キ", "き"],
["ク", "く"],
["ケ", "け"],
["コ", "こ"],
["サ", "さ"],
["ス", "す"],
["セ", "せ"],
["ソ", "そ"],
["タ", "た"],
["テ", "て"],
["ト", "と"],
["ナ", "な"],
["ニ", "に"],
["ヌ", "ぬ"],
["ネ", "ね"],
["", "の"],
["ハ", "は"],
["ヒ", "ひ"],
["フ", "ふ"],
["ヘ", "へ"],
["ホ", "ほ"],
["マ", "ま"],
["ミ", "み"],
["ム", "む"],
["メ", "め"],
["モ", "も"],
["ヤ", "や"],
["ユ", "ゆ"],
["ヨ", "よ"],
["ラ", "ら"],
["リ", "り"],
["ル", "る"],
["レ", "れ"],
["ロ", "ろ"],
["ワ", "わ"],
["ヲ", "を"],
["ガ", "が"],
["ギ", "ぎ"],
["グ", "ぐ"],
["ゲ", "げ"],
["ゴ", "ご"],
["ジ", "じ"],
["ダ", "だ"],
["デ", "で"],
["ド", "ど"],
["バ", "ば"],
["ビ", "び"],
["ブ", "ぶ"],
["ベ", "べ"],
["ボ", "ぼ"],
["パ", "ぱ"],
["ピ", "ぴ"],
["プ", "ぷ"],
["ペ", "ぺ"],
["ポ", "ぽ"],
["ザ", "ざ"],
["ズ", "ず"],
["ゼ", "ぜ"],
["ゾ", "ぞ"],
["ァ", "ぁ"],
["ィ", "ぃ"],
["ゥ", "ぅ"],
["ェ", "ぇ"],
["ォ", "ぉ"],
["ン", "ん"],
["ア", "あ"],
["イ", "い"],
["ウ", "う"],
["エ", "え"],
["オ", "お"]
],
"katakanaToHiragana": [
["っ", "ッ"],
["ち", "チ"],
["し", "シ"],
["つ", "ツ"],
["づ", "ヅ"],
["ぢ", "ヂ"],
["ゎ", "ヮ"],
["ゃ", "ャ"],
["ぃ", "ィ"],
["ゅ", "ュ"],
["ぇ", "ェ"],
["ょ", "ョ"],
["か", "カ"],
["き", "キ"],
["く", "ク"],
["け", "ケ"],
["こ", "コ"],
["さ", "サ"],
["す", "ス"],
["せ", "セ"],
["そ", "ソ"],
["た", "タ"],
["て", "テ"],
["と", "ト"],
["な", "ナ"],
["に", "ニ"],
["ぬ", "ヌ"],
["ね", "ネ"],
["の", ""],
["は", "ハ"],
["ひ", "ヒ"],
["ふ", "フ"],
["へ", "ヘ"],
["ほ", "ホ"],
["ま", "マ"],
["み", "ミ"],
["む", "ム"],
["め", "メ"],
["も", "モ"],
["や", "ヤ"],
["ゆ", "ユ"],
["よ", "ヨ"],
["ら", "ラ"],
["り", "リ"],
["る", "ル"],
["れ", "レ"],
["ろ", "ロ"],
["わ", "ワ"],
["を", "ヲ"],
["が", "ガ"],
["ぎ", "ギ"],
["ぐ", "グ"],
["げ", "ゲ"],
["ご", "ゴ"],
["じ", "ジ"],
["だ", "ダ"],
["で", "デ"],
["ど", "ド"],
["ば", "バ"],
["び", "ビ"],
["ぶ", "ブ"],
["べ", "ベ"],
["ぼ", "ボ"],
["ぱ", "パ"],
["ぴ", "ピ"],
["ぷ", "プ"],
["ぺ", "ペ"],
["ぽ", "ポ"],
["ざ", "ザ"],
["ず", "ズ"],
["ぜ", "ゼ"],
["ぞ", "ゾ"],
["ぁ", "ァ"],
["ぃ", "ィ"],
["ぅ", "ゥ"],
["ぇ", "ェ"],
["ぉ", "ォ"],
["ん", "ン"],
["あ", "ア"],
["い", "イ"],
["う", "ウ"],
["え", "エ"],
["お", "オ"]
],
"romajiToKana": {
"4letter": [
["XTSU", "ッ"],
["xtsu", "っ"],
["LTSU", "ッ"],
["ltsu", "っ"]
],
"3letter": [
["CHA", "チャ"],
["cha", "ちゃ"],
["CHI", "チ"],
["chi", "ち"],
["CHU", "チュ"],
["chu", "ちゅ"],
["CHE", "チェ"],
["che", "ちぇ"],
["CHO", "チョ"],
["cho", "ちょ"],
["SHA", "シャ"],
["sha", "しゃ"],
["SHI", "シ"],
["shi", "し"],
["SHU", "シュ"],
["shu", "しゅ"],
["SHE", "シェ"],
["she", "しぇ"],
["SHO", "ショ"],
["sho", "しょ"],
["RYA", "リャ"],
["rya", "りゃ"],
["RYI", "リィ"],
["ryi", "りぃ"],
["RYU", "リュ"],
["ryu", "りゅ"],
["RYE", "リェ"],
["rye", "りぇ"],
["RYO", "リョ"],
["ryo", "りょ"],
["HYA", "ヒャ"],
["hya", "ひゃ"],
["HYI", "ヒィ"],
["hyi", "ひぃ"],
["HYU", "ヒュ"],
["hyu", "ひゅ"],
["HYE", "ヒェ"],
["hye", "ひぇ"],
["HYO", "ヒョ"],
["hyo", "ひょ"],
["BYA", "ビャ"],
["bya", "びゃ"],
["BYI", "ビィ"],
["byi", "びぃ"],
["BYU", "ビュ"],
["byu", "びゅ"],
["BYE", "ビェ"],
["bye", "びぇ"],
["BYO", "ビョ"],
["byo", "びょ"],
["PYA", "ピャ"],
["pya", "ぴゃ"],
["PYI", "ピィ"],
["pyi", "ぴぃ"],
["PYU", "ピュ"],
["pyu", "ぴゅ"],
["PYE", "ピェ"],
["pye", "ぴぇ"],
["PYO", "ピョ"],
["pyo", "ぴょ"],
["MYA", "ミャ"],
["mya", "みゃ"],
["MYI", "ミィ"],
["myi", "みぃ"],
["MYU", "ミュ"],
["myu", "みゅ"],
["MYE", "ミェ"],
["mye", "みぇ"],
["MYO", "ミョ"],
["myo", "みょ"],
["KYA", "キャ"],
["kya", "きゃ"],
["KYI", "キィ"],
["kyi", "きぃ"],
["KYU", "キュ"],
["kyu", "きゅ"],
["KYE", "キェ"],
["kye", "きぇ"],
["KYO", "キョ"],
["kyo", "きょ"],
["GYA", "ギャ"],
["gya", "ぎゃ"],
["GYI", "ギィ"],
["gyi", "ぎぃ"],
["GYU", "ギュ"],
["gyu", "ぎゅ"],
["GYE", "ギェ"],
["gye", "ぎぇ"],
["GYO", "ギョ"],
["gyo", "ぎょ"],
["NYA", "ニャ"],
["nya", "にゃ"],
["NYI", "ニィ"],
["nyi", "にぃ"],
["NYU", "ニュ"],
["nyu", "にゅ"],
["NYE", "ニェ"],
["nye", "にぇ"],
["NYO", "ニョ"],
["nyo", "にょ"],
["JYA", "ジャ"],
["jya", "じゃ"],
["JYI", "ジィ"],
["jyi", "じぃ"],
["JYU", "ジュ"],
["jyu", "じゅ"],
["JYE", "ジェ"],
["jye", "じぇ"],
["JYO", "ジョ"],
["jyo", "じょ"],
["TSU", "ツ"],
["tsu", "つ"],
["DZU", "ヅ"],
["dzu", "づ"],
["DZI", "ヂ"],
["dzi", "ぢ"],
["DYA", "ヂャ"],
["dya", "ぢゃ"],
["DYI", "ヂィ"],
["dyi", "ぢぃ"],
["DYU", "ヂュ"],
["dyu", "ぢゅ"],
["DYE", "ヂェ"],
["dye", "ぢぇ"],
["DYO", "ヂョ"],
["dyo", "ぢょ"],
["XTU", "ッ"],
["xtu", "っ"],
["XWA", "ヮ"],
["xwa", "ゎ"],
["XKA", "ヵ"],
["xka", "ヵ"],
["XKE", "ヶ"],
["xke", "ヶ"],
["XYA", "ャ"],
["xya", "ゃ"],
["XYI", "ィ"],
["xyi", "ぃ"],
["XYU", "ュ"],
["xyu", "ゅ"],
["XYE", "ェ"],
["xye", "ぇ"],
["XYO", "ョ"],
["xyo", "ょ"],
["cya", "ちゃ"],
["cyu", "ちゅ"],
["cyo", "ちょ"],
["sya", "しゃ"],
["syu", "しゅ"],
["syo", "しょ"],
["tya", "ちゃ"],
["tyu", "ちゅ"],
["tyo", "ちょ"],
["TYA", "チャ"],
["TYU", "チュ"],
["TYO", "チョ"]
],
"2letter": [
["KA", "カ"],
["ka", "か"],
["KI", "キ"],
["ki", "き"],
["KU", "ク"],
["ku", "く"],
["KE", "ケ"],
["ke", "け"],
["KO", "コ"],
["ko", "こ"],
["CA", "カ"],
["ca", "か"],
["CI", "キ"],
["ci", "き"],
["CU", "ク"],
["cu", "く"],
["CE", "ケ"],
["ce", "け"],
["CO", "コ"],
["co", "こ"],
["SA", "サ"],
["sa", "さ"],
["SI", "シ"],
["si", "し"],
["SU", "ス"],
["su", "す"],
["SE", "セ"],
["se", "せ"],
["SO", "ソ"],
["so", "そ"],
["TA", "タ"],
["ta", "た"],
["TE", "テ"],
["ti", "ち"],
["TI", "チ"],
["tu", "つ"],
["TU", "ツ"],
["te", "て"],
["TO", "ト"],
["to", "と"],
["NA", "ナ"],
["na", "な"],
["NI", "ニ"],
["ni", "に"],
["NU", "ヌ"],
["nu", "ぬ"],
["NE", "ネ"],
["ne", "ね"],
["NO", ""],
["no", "の"],
["HA", "ハ"],
["ha", "は"],
["HI", "ヒ"],
["hi", "ひ"],
["HU", "フ"],
["hu", "ふ"],
["HE", "ヘ"],
["he", "へ"],
["HO", "ホ"],
["ho", "ほ"],
["MA", "マ"],
["ma", "ま"],
["MI", "ミ"],
["mi", "み"],
["MU", "ム"],
["mu", "む"],
["ME", "メ"],
["me", "め"],
["MO", "モ"],
["mo", "も"],
["YA", "ヤ"],
["ya", "や"],
["YU", "ユ"],
["yu", "ゆ"],
["YE", "イェ"],
["ye", "いぇ"],
["YO", "ヨ"],
["yo", "よ"],
["RA", "ラ"],
["ra", "ら"],
["RI", "リ"],
["ri", "り"],
["RU", "ル"],
["ru", "る"],
["RE", "レ"],
["re", "れ"],
["RO", "ロ"],
["ro", "ろ"],
["LA", "ラ"],
["la", "ら"],
["LI", "リ"],
["li", "り"],
["LU", "ル"],
["lu", "る"],
["LE", "レ"],
["le", "れ"],
["LO", "ロ"],
["lo", "ろ"],
["WA", "ワ"],
["wa", "わ"],
["WI", "ウィ"],
["wi", "うぃ"],
["WU", "ウ"],
["wu", "う"],
["WE", "ウェ"],
["we", "うぇ"],
["WO", "ヲ"],
["wo", "を"],
["GA", "ガ"],
["ga", "が"],
["GI", "ギ"],
["gi", "ぎ"],
["GU", "グ"],
["gu", "ぐ"],
["GE", "ゲ"],
["ge", "げ"],
["GO", "ゴ"],
["go", "ご"],
["JA", "ジャ"],
["ja", "じゃ"],
["JI", "ジ"],
["ji", "じ"],
["JU", "ジュ"],
["ju", "じゅ"],
["JE", "ジェ"],
["je", "じぇ"],
["JO", "ジョ"],
["jo", "じょ"],
["DA", "ダ"],
["da", "だ"],
["DI", "ヂ"],
["di", "ぢ"],
["DU", "ヅ"],
["du", "づ"],
["DE", "デ"],
["de", "で"],
["DO", "ド"],
["do", "ど"],
["BA", "バ"],
["ba", "ば"],
["BI", "ビ"],
["bi", "び"],
["BU", "ブ"],
["bu", "ぶ"],
["BE", "ベ"],
["be", "べ"],
["BO", "ボ"],
["bo", "ぼ"],
["VA", "ヴァ"],
["va", "ヴぁ"],
["VI", "ヴィ"],
["vi", "ヴぃ"],
["VU", "ヴ"],
["vu", "ヴ"],
["VE", "ヴェ"],
["ve", "ヴぇ"],
["VO", "ヴォ"],
["vo", "ヴぉ"],
["PA", "パ"],
["pa", "ぱ"],
["PI", "ピ"],
["pi", "ぴ"],
["PU", "プ"],
["pu", "ぷ"],
["PE", "ペ"],
["pe", "ぺ"],
["PO", "ポ"],
["po", "ぽ"],
["ZA", "ザ"],
["za", "ざ"],
["ZI", "ジ"],
["zi", "じ"],
["ZU", "ズ"],
["zu", "ず"],
["ZE", "ゼ"],
["ze", "ぜ"],
["ZO", "ゾ"],
["zo", "ぞ"],
["FA", "ファ"],
["fa", "ふぁ"],
["FI", "フィ"],
["fi", "ふぃ"],
["FU", "フ"],
["fu", "ふ"],
["FE", "フェ"],
["fe", "ふぇ"],
["FO", "フォ"],
["fo", "ふぉ"],
["XA", "ァ"],
["xa", "ぁ"],
["XI", "ィ"],
["xi", "ぃ"],
["XU", "ゥ"],
["xu", "ぅ"],
["XE", "ェ"],
["xe", "ぇ"],
["XO", "ォ"],
["xo", "ぉ"],
["XN", "ン"],
["xn", "ん"],
["NN", "ン"],
["N'", "ン"],
["nn", "ん"],
["n'", "ん"]
],
"1letter": [
["A", "ア"],
["a", "あ"],
["I", "イ"],
["i", "い"],
["U", "ウ"],
["u", "う"],
["E", "エ"],
["e", "え"],
["O", "オ"],
["o", "お"]
],
"replaceN": [
["N", "ン"],
["n", "ん"]
]
}
}

42
src/lib/kanaHelper.ts Normal file
View file

@ -0,0 +1,42 @@
import kanadata from "../data/kanadata.json";
/**
* Specifically converts a romaji string to kana.
* The result may be hiragana, katakana or mixed, depending on the case
* of the input romaji.
*
* @param romaji Input romaji string.
* @param isLive Set to true to specify that the conversion is done in live (while the user is writing). Disables certain functions.
* @returns Output kana string.
*/
export function romajiToKana(romaji: string, isLive = false): string | null {
let s = romaji.trim();
if (s == "") return null;
// Replace the double vowels for katakana.
const doubleVowelRegex = /([AEIOU])\1/g;
s = s.replaceAll(doubleVowelRegex, "$1ー");
// Replace the double consonants.
// Then, replace - by ー.
s = s.replaceAll("-", "ー");
s = s.replaceAll("_", "ー");
// Then, replace 4 letter characters:
for (const [find, repl] of kanadata.romajiToKana["4letter"]) s = s.replaceAll(find, repl);
// Then, replace 3 letter characters:
for (const [find, repl] of kanadata.romajiToKana["3letter"]) s = s.replaceAll(find, repl);
// Then, replace 2 letter characters:
for (const [find, repl] of kanadata.romajiToKana["2letter"]) s = s.replaceAll(find, repl);
// Then, replace 1 letter characters:
for (const [find, repl] of kanadata.romajiToKana["1letter"]) s = s.replaceAll(find, repl);
if (!isLive)
for (const [find, repl] of kanadata.romajiToKana["replaceN"]) s = s.replaceAll(find, repl);
return s;
}

View file

@ -1,17 +0,0 @@
export default function HomePane() {
return (
<main>
<h1 className="text-4xl">Houhou</h1>
<a
href="https://git.mzhang.io/michael/houhou"
rel="noopener"
target="_blank"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
style={{ display: "inline" }}
>
Source
</a>
</main>
);
}

View file

@ -3,6 +3,7 @@ import {
Container, Container,
Input, Input,
InputGroup, InputGroup,
InputLeftElement,
InputRightElement, InputRightElement,
Progress, Progress,
Spinner, Spinner,
@ -14,8 +15,9 @@ import { invoke } from "@tauri-apps/api/tauri";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons"; import { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons";
import { FormEvent } from "react"; import { FormEvent } from "react";
import ConfirmQuitModal from "../lib/ConfirmQuitModal"; import ConfirmQuitModal from "../components/utils/ConfirmQuitModal";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import InputBox from "../components/utils/InputBox";
export interface SrsEntry { export interface SrsEntry {
associated_kanji: string; associated_kanji: string;
@ -25,8 +27,8 @@ export interface SrsEntry {
} }
export enum ReviewItemType { export enum ReviewItemType {
MEANING, MEANING = "MEANING",
READING, READING = "READING",
} }
export interface ReviewItem { export interface ReviewItem {
@ -100,15 +102,38 @@ export function Component() {
setReviewQueue(rest); setReviewQueue(rest);
}; };
if (!reviewQueue) return <Spinner />;
if (reviewQueue.length == 0) return <Done />;
const nextItem = reviewQueue[0];
const inputBox = (kanaInput: boolean) => {
return (
<InputGroup>
<InputLeftElement>{kanaInput ? "あ" : "A"}</InputLeftElement>
<InputBox
kanaInput={kanaInput}
value={currentAnswer}
setValue={setCurrentAnswer}
autoFocus
className={styles["input-box"]}
placeholder="Enter your answer..."
spellCheck={false}
backgroundColor={"white"}
/>
<InputRightElement>
<CheckIcon color="green.500" />
</InputRightElement>
</InputGroup>
);
};
const renderInside = () => { const renderInside = () => {
if (!reviewQueue) return <Spinner />;
if (reviewQueue.length == 0) return <Done />;
const nextItem = reviewQueue[0];
console.log("next item", nextItem); console.log("next item", nextItem);
const kanaInput = nextItem.type == ReviewItemType.READING;
return ( return (
<> <>
{startingSize && ( {startingSize && (
@ -123,27 +148,15 @@ export function Component() {
<h1 className={styles["test-word"]}>{nextItem.challenge}</h1> <h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
<details>
<summary>Debug</summary>
<pre>{JSON.stringify(nextItem, null, 2)}</pre>
</details>
<form onSubmit={formSubmit}> <form onSubmit={formSubmit}>
<InputGroup> {
<Input {
autoFocus [ReviewItemType.MEANING]: "What is the meaning?",
className={styles["input-box"]} [ReviewItemType.READING]: "What is the reading?",
placeholder="Enter your answer..." }[nextItem.type]
value={currentAnswer} }
spellCheck={false}
backgroundColor={"white"}
onChange={(evt) => setCurrentAnswer(evt.target.value)}
/>
<InputRightElement> {inputBox(kanaInput)}
<CheckIcon color="green.500" />
</InputRightElement>
</InputGroup>
</form> </form>
</> </>
); );
@ -164,6 +177,12 @@ export function Component() {
<ArrowBackIcon /> <ArrowBackIcon />
Back Back
</Button> </Button>
<details>
<summary>Debug</summary>
<pre>{JSON.stringify(nextItem, null, 2)}</pre>
</details>
<div className={styles.container}>{renderInside()}</div> <div className={styles.container}>{renderInside()}</div>
</Container> </Container>

View file

@ -1,5 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react-swc";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({