Skip to content

Commit

Permalink
HSK vocabulary tester 1, 2 and 3
Browse files Browse the repository at this point in the history
  • Loading branch information
bandinopla committed Dec 3, 2024
1 parent 4c550ad commit 097580c
Show file tree
Hide file tree
Showing 7 changed files with 4,342 additions and 15 deletions.
5 changes: 3 additions & 2 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,16 @@ a { color: var(--FLAG_RED) }
display:flex;
gap:40px;
justify-content:center;
flex-direction: column;
flex-direction: column;
align-items: center;
}
.menu > div {
display: flex;
flex-direction: column;
}
.menu button {
white-space:nowrap;
font-size: 14px;
font-size: 2em;
}
.smUp .menu {
flex-direction: row;
Expand Down
48 changes: 35 additions & 13 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { AudiosFilter } from './components/AudiosFilter'
import { Flag } from './components/flag'
import { TypeCharacter } from './components/TypeCharacter'
import { CharacterUseStats } from './components/CharacterUseStats'
import hsk from "./data/hsk.json"
import { HSK } from './components/HSK'

//const props = ["audio", "ch", "en"];

Expand Down Expand Up @@ -41,6 +43,16 @@ function App() {
useEffect(() => {

const onKeyDown = (ev: KeyboardEvent) => {

if( ev.code == 'Escape') {
setQuestion(null);
setAvailableLines([]);
setMyLines([]);
setMode(0);
return;
}

if( mode>1 ) return;

if( myLines.length == 0 )
{
Expand All @@ -53,12 +65,7 @@ function App() {
return;
}

if( ev.code == 'Escape') {
setQuestion(null);
setAvailableLines([]);
setMyLines([]);
return;
}


if (ev.code == 'ArrowRight' && mode==0 ) {

Expand All @@ -85,7 +92,7 @@ function App() {
window.removeEventListener("keydown", onKeyDown);
}

}, [myLines, showDetails]);
}, [myLines, showDetails, mode]);

useEffect(()=>{

Expand Down Expand Up @@ -142,6 +149,10 @@ function App() {
setAvailableLines( lines );
}

const startHSK = (v:number) => {
setMode(1+v);
}

const pickRandomQuestion = () => {
// const x = typeIndex.current % props.length;
// const y = Math.floor(typeIndex.current / props.length) % props.length;
Expand Down Expand Up @@ -222,25 +233,36 @@ function App() {
}

{
myLines.length == 0 && <>
mode>1 && <div><HSK level={mode-1}/></div>
}

{
(mode<2 && myLines.length == 0) && <>

<Flag/>

<div className='menu'>
<div>
Write...<br/><br/>
<button onClick={()=>startQuiz(1)} style={{ fontSize: "2em"}}>
<button onClick={()=>startQuiz(1)}>
← 字符
</button>
</div>
<div>
Hear...<br/><br/>
<button onClick={()=>startQuiz(0)} style={{ fontSize: "2em"}}>
<button onClick={()=>startQuiz(0)}>
听到 →
</button>
</div>


</div>
</div>

<div className='menu'>
<div>
Test your vocabulary<br/><a href={hsk.source} target='_blank'>Source</a>
</div>
<button onClick={()=>startHSK(1)}>HSK 1</button>
<button onClick={()=>startHSK(2)}>HSK 2</button>
<button onClick={()=>startHSK(3)}>HSK 3</button>
</div>


Expand Down
16 changes: 16 additions & 0 deletions src/components/GoldenSymbol.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.gold {
text-transform: uppercase;
line-height:1;
text-align: center;
background: linear-gradient(90deg, rgba(186,148,62,1) 0%, rgba(236,172,32,1) 20%, rgba(186,148,62,1) 39%, rgba(249,244,180,1) 50%, rgba(186,148,62,1) 60%, rgba(236,172,32,1) 80%, rgba(186,148,62,1) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shine 3s infinite;
background-size: 200%;
background-position: left;

}
@keyframes shine {
to{background-position: right}

}
6 changes: 6 additions & 0 deletions src/components/GoldenSymbol.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FC, PropsWithChildren } from "react";
import classes from "./GoldenSymbol.module.css";

export const GoldenSymbol :FC<PropsWithChildren<{ size?:number}>> = ({ children, size=200 })=>{
return <div className={classes.gold} style={{ fontSize:size+"px"}}>{children}</div>
}
34 changes: 34 additions & 0 deletions src/components/HSK.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
:root {
--OKCOLOR : green;
}
.chars {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 5px;
color:#555;
}
.chars > div {
padding:2px 3px;
border-radius: 4px;
}

.resfalse {
background-color: red;
color:white;
}
.restrue {
background-color: var(--OKCOLOR);
color: white;;
}

.progress {
background-color: red;
margin: 20px 0;
border-radius: 5px;
}
.progress > div {
height: 8px;
background-color: var(--OKCOLOR);
border-radius: 5px;
}
109 changes: 109 additions & 0 deletions src/components/HSK.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { FC, useEffect, useMemo, useState } from "react"
import hskData from "../data/hsk.json";
import classes from "./HSK.module.css";
import { GoldenSymbol } from "./GoldenSymbol";

type Done = {
ch:string
knewit:boolean // if the user claim to knew it...
}

export const HSK : FC<{ level:number }> = ({ level })=>{
const chars = useMemo(()=>{
const dicc = [ hskData.hsk1, hskData.hsk2, hskData.hsk3][level-1];
return Object.entries(dicc).map( entry=>{
let key = entry[0];
let en = entry[1].en;

if( key.indexOf("(") ) {
key = key.replace(/\s*.*?\s*/g, '');
en = en.replace(/\s*.*?\s*/g, '');
}

return { ch:key, ...entry[1], en }
})
}, [level]);

const [check, setCheck] = useState(false);
const [ pool, setPool ] = useState( chars.slice(1) );
const [ char, setChar ] = useState( chars[0] );
const [ dones, setDones] = useState<Done[]>([]);

useEffect(()=>{

const onKey = (ev:KeyboardEvent) => {
if( check )
{
//chekc up or down arrows
if( ev.key=='ArrowUp') gotIt(true);
else if( ev.key=="ArrowDown") gotIt(false);
}
else
{
if( ev.key=="ArrowRight") {
setCheck(true);
}
}

}

window.addEventListener("keydown", onKey );
return ()=>window.removeEventListener("keydown", onKey);

},[check]);

const gotIt = (correct:boolean) => {

const avail = pool.length? pool : chars.slice(0);
const randomIndex = Math.floor( avail.length*Math.random() );
const pick = avail.splice(randomIndex,1)[0];

setChar(pick);
setPool(avail);
setCheck(false);


setDones([
...dones.filter(d=>d.ch!=char.ch),
{
ch: char.ch,
knewit: correct
}
])
}

return <div>

<div className='instructions'>
<strong>ESC</strong> to quit
{ !check && <><br/><strong>RIGHT →</strong> to continue </>}
</div>
<br/>
<br/>
<br/>


{
check? <div style={{ display:"flex", justifyContent:"center", alignItems:"center", gap:20 }}>
<div>
<GoldenSymbol size={180}>{char.ch}</GoldenSymbol>
<GoldenSymbol size={40}>{ char.pinzi }</GoldenSymbol>
<GoldenSymbol size={30}>{ char.en }</GoldenSymbol>
</div>
<div style={{ display:"flex", flexDirection:"column", gap:10 }}>
<h3>Did you got it?</h3>
<button onClick={()=>gotIt(true)}>↑ YES</button>
<button onClick={()=>gotIt(false)}>↓ NO</button>
</div>
</div>
: <GoldenSymbol>{char.ch}</GoldenSymbol>
}
<br/>
<div className={classes.progress}>
<div style={{ width:`${ (dones.filter(d=>d.knewit).length / dones.length)*100 }%`}}></div>
</div>
<div className={ classes.chars }>
{ chars.map( char=><div key={char.ch} className={ classes["res"+dones.find(d=>d.ch==char.ch)?.knewit] }>{char.ch}</div> )}
</div>
</div>
}
Loading

0 comments on commit 097580c

Please sign in to comment.