Compare commits
	
		
			No commits in common. "50e99a08bf5be5be828af3e99a566c25c56b718b" and "dff64780f5b5a8b0b1253a13e792a8ac15c6bb4f" have entirely different histories. 
		
	
	
		
			50e99a08bf
			...
			dff64780f5
		
	
		|  | @ -1,8 +1,7 @@ | |||
| { | ||||
|   "name": "pinyindictation", | ||||
|   "author": {"email": "rurik19@yandex.ru", "name": "Юрий Евдокимов"}, | ||||
|   "name": "vite-1", | ||||
|   "private": true, | ||||
|   "version": "1.1.0", | ||||
|   "version": "0.0.0", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|  |  | |||
							
								
								
									
										17
									
								
								src/App.css
								
								
								
								
							
							
						
						
									
										17
									
								
								src/App.css
								
								
								
								
							|  | @ -42,23 +42,6 @@ | |||
| } | ||||
| 
 | ||||
| .btn { | ||||
|   margin-top: 5px; | ||||
|   margin-bottom: 5px; | ||||
|   margin-right: 5px; | ||||
| } | ||||
| 
 | ||||
| .syllable { | ||||
|   font-weight: bold; | ||||
|   font-size: xx-large; | ||||
| } | ||||
| .syllable:hover { | ||||
|   cursor:pointer | ||||
| } | ||||
| 
 | ||||
| .tone1 { color: rgb(255, 0, 0) } | ||||
| .tone2 { color: rgb(255, 170, 0) } | ||||
| .tone3 { color: rgb(85, 170, 0) } | ||||
| .tone4 { color: rgb(0, 0, 255) } | ||||
| .tone5 { color: rgb(50, 50, 50) } | ||||
| 
 | ||||
| .cpr { color: gray} | ||||
							
								
								
									
										115
									
								
								src/App.tsx
								
								
								
								
							
							
						
						
									
										115
									
								
								src/App.tsx
								
								
								
								
							|  | @ -1,86 +1,66 @@ | |||
| import { useEffect, useState } from 'react' | ||||
| import { useState } from 'react' | ||||
| import './App.css' | ||||
| import { Button, ListGroup } from 'react-bootstrap'; | ||||
| import { initials, finales } from './pinyin'; | ||||
| import { BtnColor, Status, Tone } from './types'; | ||||
| import { Badge, Button, ListGroup } from 'react-bootstrap'; | ||||
| import { initials, finales } from './data'; | ||||
| import { BtnColor, Tone } from './types'; | ||||
| import { strings } from './strings'; | ||||
| import { useStateContext } from './store'; | ||||
| import { Params } from './params'; | ||||
| import { ActionType, ToggleType } from './reducer'; | ||||
| import { ButtonSet } from './buttons'; | ||||
| import { RenderTones } from './rendertones'; | ||||
| import { getAudio } from './audio'; | ||||
| import { Copyright } from './copyright'; | ||||
| import { SelectTones } from './selecttones'; | ||||
| import { FormatString } from './utils'; | ||||
| 
 | ||||
| enum Status {params, plaing, plaied, showlist} | ||||
| 
 | ||||
| function App() { | ||||
|    | ||||
|   const { state, dispatch } = useStateContext(); | ||||
| 
 | ||||
|   const [ plaingNo, setPlaingNo ] = useState(0) | ||||
|   const [ status, setStatus ] = useState(Status.params) | ||||
|   const [ playlist, setPlaylist ] = useState( [] as HTMLAudioElement[] ) | ||||
| //---------------- to found state
 | ||||
|   const isFound = ():boolean => state.foundSyllables!.length > 0 | ||||
|   const [plaingNo, setPlaingNo ] = useState(0) | ||||
| 
 | ||||
|   useEffect( () => { | ||||
|     if (status===Status.params) { | ||||
|      console.log('effect for params') | ||||
|      dispatch({ type: ActionType.refresh }) | ||||
|     } | ||||
|     if (status===Status.prepare) { | ||||
|       console.log('effect for preparing') | ||||
|       preparePlayList(state.randomTones!) | ||||
|     } | ||||
|     if (status===Status.playing) { | ||||
|       console.log('effect for playing') | ||||
|       playPlayList() | ||||
|     } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, [status]) | ||||
| const [ status, setStatus ] = useState(Status.params) | ||||
| 
 | ||||
|   useEffect( () => { | ||||
|     console.log(`effect for playList ${playlist.length}`) | ||||
|     if (playlist.length > 0) setStatus(Status.playing) | ||||
|   } , [playlist[0]] ) | ||||
| const beginDictation = (): void => { | ||||
|   dispatch({ type: ActionType.refreshPlayList }) | ||||
|   playDictation2(state.randomTones!) | ||||
| } | ||||
| 
 | ||||
| const preparePlayList = (tones: Tone[]) => { | ||||
|   console.log('tones for playing') | ||||
|   tones!.forEach(element => console.log(element)) | ||||
|   if ( tones!.length == 0 ) return | ||||
|   const audios = tones!.map(element => getAudio(element.tone) ) | ||||
| const playDictation2 = (randomTones: Tone[]) => { | ||||
|   if ( randomTones.length == 0 ) return | ||||
|   let audios:HTMLAudioElement[] = [] | ||||
|   randomTones.forEach(element => { | ||||
|      audios = [...audios, new Audio(`/src/assets/audio/${element.tone}.mp3`) ] | ||||
|   }); | ||||
|   console.debug(audios) | ||||
|   if ( audios.length == 0 ) return | ||||
|   for(let x=0; x<audios.length-1;x++) | ||||
|   { | ||||
|     audios[x].onended = () =>  setTimeout( () => {  | ||||
|       const pno = x+2 | ||||
|       let pno = x+2 | ||||
|       setPlaingNo(pno) | ||||
|       audios[x+1].play() | ||||
|     }, 1000*state.sylPause! ) ; | ||||
|   } | ||||
|   audios[audios.length-1].onended = () => setStatus(Status.plaied) | ||||
|   setPlaylist(audios) | ||||
| } | ||||
| 
 | ||||
| const playPlayList = () => { | ||||
|   if (playlist.length===0) return; | ||||
|   setStatus(Status.playing) | ||||
|   setStatus(Status.plaing) | ||||
|   setPlaingNo(1) | ||||
|   playlist[0].play() | ||||
|   audios[0].play() | ||||
| } | ||||
| 
 | ||||
| const info = () => FormatString(strings.infoFormat, | ||||
|   state.initiales!.length.toString(), | ||||
|   state.finales!.length.toString(), | ||||
|   state.toneChecks!.length.toString(), | ||||
|   state.foundSyllables!.length.toString(), | ||||
|    state.foundTones!.length.toString()) | ||||
| const renderRandomTones2 = () => { | ||||
|   return state.randomTones!.map( (ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{'  '}</span> }) | ||||
| } | ||||
| 
 | ||||
| return ( | ||||
| const refresh = () => { | ||||
|   dispatch({ type: ActionType.refreshPlayList }) | ||||
|   setStatus(Status.params) | ||||
| } | ||||
| 
 | ||||
|   return ( | ||||
|       <> | ||||
|       <h1>{strings.mainHeader}</h1> | ||||
|       <h1>Диктант pīnyīn</h1> | ||||
|       <ListGroup> | ||||
| 
 | ||||
|       <ListGroup.Item disabled={status != Status.params}> | ||||
|         <h2>{strings.selectInitiales}</h2> | ||||
|         <ButtonSet source={initials} color={BtnColor.blue} toggle={ToggleType.init}/> | ||||
|  | @ -91,54 +71,41 @@ return ( | |||
|         <ButtonSet source={finales} color={BtnColor.green} toggle={ToggleType.fin}/> | ||||
|       </ListGroup.Item> | ||||
| 
 | ||||
|       <ListGroup.Item disabled={status != Status.params}> | ||||
|         <h2>{strings.selectTones}</h2> | ||||
|           <SelectTones/> | ||||
|       </ListGroup.Item> | ||||
| 
 | ||||
|       <ListGroup.Item disabled={status != Status.params}> | ||||
|         <h2>{strings.params}</h2> | ||||
|           <Params/> | ||||
|        </ListGroup.Item> | ||||
| 
 | ||||
|       <ListGroup.Item> | ||||
|         { status == Status.params && | ||||
|           <> | ||||
|           {info()} | ||||
|         Выбрано {state.initiales!.length} инициалей,  {state.finales!.length}  финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов ,  | ||||
|         <br/> | ||||
|         <Button | ||||
|           variant={state.isFound!() ? "success" : "secondary"} | ||||
|           size="lg" | ||||
|           onClick={()=>setStatus(Status.prepare)} | ||||
|           disabled={!state.isFound!()}> | ||||
|             {state.isFound!() ?  strings.beginDictation : strings.selectInitFinAndTone } | ||||
|         <Button variant={isFound() ? "success" : "secondary"} size="lg"  onClick={()=>beginDictation()} disabled={!isFound()}> | ||||
|             {isFound() ? "Начать диктант!" : "Выберите инициали и финали" } | ||||
|           </Button> | ||||
|           </> | ||||
|         } | ||||
|         { | ||||
|           status == Status.playing && | ||||
|           status == Status.plaing && | ||||
|           <h1>Воспроизводится...{plaingNo}</h1> | ||||
|         } | ||||
|         { | ||||
|           status == Status.plaied && | ||||
|           <> | ||||
|           <Button variant="success" onClick={()=>setStatus(Status.showlist)}>{strings.showSyllables}</Button> | ||||
|           <Button variant="success" onClick={()=>{setStatus(Status.playing)}}>{strings.playAgain}</Button> | ||||
|           </> | ||||
|           <Button variant="success" onClick={()=>setStatus(Status.showlist)}>Показать слоги</Button> | ||||
|         } | ||||
|         { | ||||
|           status == Status.showlist && | ||||
|           <> | ||||
|             <RenderTones tones={state.randomTones!}/> | ||||
|             <div>{renderRandomTones2()}</div> | ||||
|             <br/> | ||||
|             <Button variant="success" onClick={()=>  setStatus(Status.params)}>{strings.toBegin}</Button> | ||||
|             <Button variant="success" onClick={()=>refresh()}>В начало</Button> | ||||
|           </> | ||||
|         } | ||||
|       </ListGroup.Item> | ||||
|       <ListGroup.Item><Copyright/></ListGroup.Item> | ||||
|       </ListGroup> | ||||
|       </> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export default App | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,25 +1,7 @@ | |||
| export const strings = { | ||||
|   mainHeader: "Диктант pīnyīn", | ||||
|   selectInitiales: 'Выберите инициали', | ||||
|   selectFinales: 'Выберите финали', | ||||
|   selectAll:  'Выбрать все', | ||||
|   unselectAll: 'Снять все', | ||||
|   params: 'Параметры', | ||||
|   sylCount: 'Количество слогов: ', | ||||
|   beginDictation: "Начать диктант!", | ||||
|   selectInitFinAndTone: "Выберите инициали, финали и тоны", | ||||
|   showSyllables: "Показать слоги", | ||||
|   playAgain: "Повторить", | ||||
|   toBegin: "В начало", | ||||
|   version: "Версия", | ||||
|   cpr: "©", | ||||
|   selectTones: "Выберите тоны", | ||||
|   first: "Первый", | ||||
|   second: "Второй", | ||||
|   third: "Третий", | ||||
|   fourth: "Четвёртый", | ||||
|   zeroth: "Нулевой", | ||||
|   infoFormat: "Выбрано {0} инициалей,  {1}  финалей, {2} тонов, найдено {3} слогов, {4} тонов", | ||||
|   pauseFormat: "Пауза между слогами, в секундах: {0}", | ||||
|   mail: "емайл:" | ||||
|   params: 'Параметры' | ||||
| } | ||||
|  | @ -15,8 +15,7 @@ export type SylPart = { | |||
| 
 | ||||
| export type Tone = { | ||||
|     tone: string, | ||||
|     caption: string, | ||||
|     num: number | ||||
|     caption: string | ||||
| } | ||||
| 
 | ||||
| export type Found = { | ||||
|  | @ -34,4 +33,4 @@ export enum BtnColor { | |||
|     green = "success" | ||||
| } | ||||
| 
 | ||||
| export enum Status {params, prepare, playing, plaied, showlist} | ||||
| export enum Status {params, plaing, plaied, showlist} | ||||
							
								
								
									
										42
									
								
								src/Utils.ts
								
								
								
								
							
							
						
						
									
										42
									
								
								src/Utils.ts
								
								
								
								
							|  | @ -1,42 +1,19 @@ | |||
| import { syllables } from "./pinyin" | ||||
| import { syllables } from "./data" | ||||
| import { BtnColor, SylPart, Syllable } from "./types" | ||||
| 
 | ||||
| export const isEnabled = (arr: SylPart[], find: string): boolean => arr.some((str) => str.index==find) | ||||
| 
 | ||||
| export const toggleSylPart = ( arr: SylPart[], part: SylPart ):SylPart[] => { | ||||
| export const toggle = ( arr: SylPart[], part: SylPart ):SylPart[] => { | ||||
|     if ( arr.some((btn) => btn.index==part.index) ) | ||||
|       return arr.filter((el)=>el.index!==part.index) | ||||
|     else | ||||
|       return [ ...arr, part] | ||||
| } | ||||
| 
 | ||||
| type TComparer = (index: any) => (el: any) => boolean | ||||
| const SylPartComparer:TComparer = (index: string) => (el: SylPart) => el.index === index | ||||
| const SylPartFilter:TComparer = (index: SylPart) => (el: SylPart) => el.index !== index.index | ||||
| 
 | ||||
| const defToggleComparer:TComparer = (index: any) => (el: any) => el === index | ||||
| const defToggleFilter:TComparer = (index: any) => (el: any) => el !== index | ||||
| 
 | ||||
| export const toggleSylPartG = ( arr: SylPart[], part: SylPart ):SylPart[] => | ||||
|   toggle<SylPart>(arr, part, SylPartComparer, SylPartFilter) | ||||
| 
 | ||||
| export const included = <T>( arr: T[], part: T, comparer:TComparer = defToggleComparer) => | ||||
|   arr.some( comparer(part) ) | ||||
| 
 | ||||
| const exclude = <T>( arr: T[], part: T, | ||||
|   filter:TComparer = defToggleFilter) => arr.filter( filter(part) ) | ||||
| 
 | ||||
| const include = <T>( arr: T[], part: T):T[] => [ ...arr, part] | ||||
| 
 | ||||
| export const toggle = <T>( arr: T[], part: T, | ||||
|                            comparer:TComparer = defToggleComparer, | ||||
|                            filter:TComparer = defToggleFilter):T[] => | ||||
|   included<T>(arr,part, comparer) ? exclude<T>(arr,part, filter ) :  include<T>(arr, part) | ||||
| 
 | ||||
| export const genrateRandomNumber = (min: number, max: number):number => { | ||||
|   min = Math.ceil(min) | ||||
|   max = Math.floor(max) | ||||
|   const rnd =  Math.floor(Math.random() * (max - min + 1)) + min | ||||
|   let rnd =  Math.floor(Math.random() * (max - min + 1)) + min | ||||
|   return rnd | ||||
| } | ||||
| 
 | ||||
|  | @ -46,23 +23,16 @@ export const getRandomArray = <T>(fromArray:T[], count:number):T[] => { | |||
|     return _tone | ||||
|   } | ||||
|   for( let x=0; x<count; x++) { | ||||
|     const randomN = genrateRandomNumber(0,fromArray.length-1) | ||||
|     let randomN = genrateRandomNumber(0,fromArray.length-1) | ||||
|     _tone = [..._tone, fromArray[randomN]] | ||||
|   } | ||||
|   return _tone | ||||
| } | ||||
| 
 | ||||
| export const GetSyllablesByInitAndFin = (initiales:SylPart[], finales: SylPart[]):Syllable[] => { | ||||
|   const inits = initiales.map( (i) => i.index) | ||||
|   const fins = finales.map( (i) =>i.index ) | ||||
|   let inits = initiales.map( (i) => i.index) | ||||
|   let fins = finales.map( (i) =>i.index ) | ||||
|   return syllables.filter( syl => inits.includes(syl.initiale) && fins.includes(syl.finale) ) | ||||
|   } | ||||
| 
 | ||||
| export const Outlined = (color: BtnColor):string => `outline-${color}`   | ||||
| 
 | ||||
| export function FormatString(str: string, ...val: string[]) { | ||||
|   for (let index = 0; index < val.length; index++) { | ||||
|     str = str.replace(`{${index}}`, val[index]); | ||||
|   } | ||||
|   return str; | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue