@@ -2,6 +2,9 @@ import { ContentBox } from "@src/common/context-box/ContextBox";
22import { KanaUtils } from "@src/japanese/utils/kana-utils" ;
33import { makeAutoObservable } from "mobx" ;
44import { SyntheticEvent , useEffect } from "react" ;
5+ import { Dropdown } from "@src/common/windows/dropdown/Dropdown" ;
6+ import { CheckBox } from "@src/common/input/checkbox/CheckBox" ;
7+ import { Flex } from "@src/common/flex/flex" ;
58import "./KanaTyper.scss" ;
69
710type TypedKana = {
@@ -24,10 +27,15 @@ const game = makeAutoObservable({
2427 | { handle : number ; startedAtMs : number ; timeLeftMs : number } ,
2528 timeLimitMs : 0 ,
2629 finished : false ,
30+ enabledKanas : [ ] as string [ ] ,
2731
2832 reset ( ) : void {
2933 this . currentInput = "" ;
3034 this . currentIdx = 0 ;
35+ if ( ! this . enabledKanas . length )
36+ this . enabledKanas = Object . values ( kanaTables )
37+ . flatMap ( ( it ) => Object . values ( it ) )
38+ . flatMap ( ( it ) => it ) ;
3139 this . kanasPrev = [ ] ;
3240 this . kanas = this . generateKanas ( 0 ) ;
3341 this . kanasNext = this . generateKanas ( this . kanas . last ( ) ! . idx + 1 ) ;
@@ -66,8 +74,6 @@ const game = makeAutoObservable({
6674 startedAtMs : Date . now ( ) ,
6775 timeLeftMs : this . timeLimitMs ,
6876 } ;
69-
70- console . log ( "started timer" , this . timer ) ;
7177 }
7278
7379 const inputElem = evt . target as HTMLInputElement ;
@@ -92,9 +98,12 @@ const game = makeAutoObservable({
9298 generateKanas ( startingIdx : number ) : TypedKana [ ] {
9399 const result : TypedKana [ ] = [ ] ;
94100
101+ if ( ! this . enabledKanas . length ) return result ;
102+
95103 for ( let i = 0 ; i < 10 ; i ++ ) {
96- const table = hiraganas ;
97- const selectedKana = table [ Math . floor ( Math . random ( ) * table . length ) ] ;
104+ const selectedKana =
105+ this . enabledKanas [ Math . floor ( Math . random ( ) * this . enabledKanas . length ) ] ;
106+
98107 result . push ( {
99108 idx : startingIdx + i ,
100109 kana : selectedKana ,
@@ -113,7 +122,12 @@ export function KanaTyper() {
113122
114123 return (
115124 < ContentBox >
116- < h1 > Kana typer</ h1 >
125+ < Flex itemsPlacement = "center" gap = { 12 } >
126+ < h1 > Kana typer</ h1 >
127+ < Dropdown trigger = "click" content = { < KanaTyperSettings /> } >
128+ < span className = "clickable" > & #128736 ; </ span >
129+ </ Dropdown >
130+ </ Flex >
117131 < div >
118132 { ! game . timer
119133 ? "Type to start"
@@ -122,21 +136,35 @@ export function KanaTyper() {
122136 < br />
123137 < div className = "kanas-to-type" >
124138 { [ game . kanasPrev , game . kanas , game . kanasNext ] . map ( ( row , idx ) => (
125- < div key = { idx } className = { { 0 : "prev" , 1 : "cur" , 2 : "next" } [ idx ] } >
126- < span style = { { opacity : 0 } } > |</ span >
139+ < Flex
140+ key = { idx }
141+ className = { { 0 : "prev" , 1 : "cur" , 2 : "next" } [ idx ] }
142+ justify = "space-between"
143+ >
144+ < span style = { { opacity : 0 , width : 0 , height : "2em" } } > |</ span >
127145 { row . map ( ( it ) => (
128- < span
146+ < Flex
147+ down
148+ itemsPlacement = "center"
129149 key = { it . idx }
130150 className = {
131151 { true : "correct" , false : "incorrect" , undefined : "" } [
132152 it . correct as any as string
133153 ] + ( it . idx === game . currentIdx ? " current" : "" )
134154 }
135155 >
136- { it . kana }
137- </ span >
156+ < span > { it . kana } </ span >
157+ < span
158+ style = { {
159+ fontSize : "0.3em" ,
160+ opacity : it . correct != null ? 1 : 0 ,
161+ } }
162+ >
163+ { it . expected }
164+ </ span >
165+ </ Flex >
138166 ) ) }
139- </ div >
167+ </ Flex >
140168 ) ) }
141169 </ div >
142170 < br />
@@ -177,76 +205,88 @@ export function KanaTyper() {
177205 ) ;
178206}
179207
180- const hiraganas = [
181- "あ" ,
182- "い" ,
183- "う" ,
184- "え" ,
185- "お" ,
186- "か" ,
187- "き" ,
188- "く" ,
189- "け" ,
190- "こ" ,
191- "さ" ,
192- "し" ,
193- "す" ,
194- "せ" ,
195- "そ" ,
196- "た" ,
197- "ち" ,
198- "つ" ,
199- "て" ,
200- "と" ,
201- "な" ,
202- "に" ,
203- "ぬ" ,
204- "ね" ,
205- "の" ,
206- "は" ,
207- "ひ" ,
208- "ふ" ,
209- "へ" ,
210- "ほ" ,
211- "ま" ,
212- "み" ,
213- "む" ,
214- "め" ,
215- "も" ,
216- "や" ,
217- "ゆ" ,
218- "よ" ,
219- "ら" ,
220- "り" ,
221- "る" ,
222- "れ" ,
223- "ろ" ,
224- "わ" ,
225- "を" ,
226- "ん" ,
227- "が" ,
228- "ぎ" ,
229- "ぐ" ,
230- "げ" ,
231- "ご" ,
232- "ざ" ,
233- "じ" ,
234- "ず" ,
235- "ぜ" ,
236- "ぞ" ,
237- "だ" ,
238- "ぢ" ,
239- "づ" ,
240- "で" ,
241- "ど" ,
242- "ば" ,
243- "び" ,
244- "ぶ" ,
245- "べ" ,
246- "ぼ" ,
247- "ぱ" ,
248- "ぴ" ,
249- "ぷ" ,
250- "ぺ" ,
251- "ぽ" ,
252- ] ;
208+ function KanaTyperSettings ( ) {
209+ return (
210+ < Flex
211+ down
212+ bg = "white"
213+ pad = { 8 }
214+ border = "1px solid black"
215+ style = { { minWidth : 350 } }
216+ >
217+ < h3 > Selected Kana</ h3 >
218+ < Flex gap = { 12 } >
219+ < Flex down >
220+ { Object . entries ( kanaTables . hiragana ) . map ( ( [ rowName , kanas ] ) => (
221+ < CheckBox
222+ key = { rowName }
223+ onChange = { ( ) => {
224+ const arr = game . enabledKanas as any as string [ ] & {
225+ remove : ( value : string ) => boolean ;
226+ } ;
227+
228+ if ( ! kanas . every ( ( it ) => arr . remove ( it ) ) ) {
229+ arr . push ( ...kanas ) ;
230+ }
231+
232+ game . reset ( ) ;
233+ } }
234+ checked = { kanas . every ( ( k ) => game . enabledKanas . includes ( k ) ) }
235+ >
236+ < div > { kanas . join ( ", " ) } </ div >
237+ </ CheckBox >
238+ ) ) }
239+ </ Flex >
240+
241+ < Flex down >
242+ { Object . entries ( kanaTables . katakana ) . map ( ( [ rowName , kanas ] ) => (
243+ < CheckBox
244+ key = { rowName }
245+ onChange = { ( ) => {
246+ const arr = game . enabledKanas as any as string [ ] & {
247+ remove : ( value : string ) => boolean ;
248+ } ;
249+
250+ if ( ! kanas . every ( ( it ) => arr . remove ( it ) ) ) {
251+ arr . push ( ...kanas ) ;
252+ }
253+
254+ game . reset ( ) ;
255+ } }
256+ checked = { kanas . every ( ( k ) => game . enabledKanas . includes ( k ) ) }
257+ >
258+ < div > { kanas . join ( ", " ) } </ div >
259+ </ CheckBox >
260+ ) ) }
261+ </ Flex >
262+ </ Flex >
263+ </ Flex >
264+ ) ;
265+ }
266+
267+ const kanaTables = {
268+ hiragana : {
269+ row1 : [ "あ" , "い" , "う" , "え" , "お" ] ,
270+ row2 : [ "か" , "き" , "く" , "け" , "こ" ] ,
271+ row3 : [ "さ" , "し" , "す" , "せ" , "そ" ] ,
272+ row4 : [ "た" , "ち" , "つ" , "て" , "と" ] ,
273+ row5 : [ "な" , "に" , "ぬ" , "ね" , "の" ] ,
274+ row6 : [ "は" , "ひ" , "ふ" , "へ" , "ほ" ] ,
275+ row7 : [ "ま" , "み" , "む" , "め" , "も" ] ,
276+ row8 : [ "や" , "ゆ" , "よ" ] ,
277+ row9 : [ "ら" , "り" , "る" , "れ" , "ろ" ] ,
278+ row10 : [ "わ" , "を" , "ん" ] ,
279+ } ,
280+ katakana : {
281+ row1 : [ "ア" , "イ" , "ウ" , "エ" , "オ" ] ,
282+ row2 : [ "カ" , "キ" , "ク" , "ケ" , "コ" ] ,
283+ row3 : [ "サ" , "シ" , "ス" , "セ" , "ソ" ] ,
284+ row4 : [ "タ" , "チ" , "ツ" , "テ" , "ト" ] ,
285+ row5 : [ "ナ" , "ニ" , "ヌ" , "ネ" , "ノ" ] ,
286+ row6 : [ "ハ" , "ヒ" , "フ" , "ヘ" , "ホ" ] ,
287+ row7 : [ "マ" , "ミ" , "ム" , "メ" , "モ" ] ,
288+ row8 : [ "ヤ" , "ユ" , "ヨ" ] ,
289+ row9 : [ "ラ" , "リ" , "ル" , "レ" , "ロ" ] ,
290+ row10 : [ "ワ" , "ヲ" , "ン" ] ,
291+ } ,
292+ } ;
0 commit comments