tones selection, v1.1.0
							parent
							
								
									dacecb3d9e
								
							
						
					
					
						commit
						50e99a08bf
					
				|  | @ -1,8 +1,8 @@ | |||
| { | ||||
|   "name": "pinyindictation", | ||||
|   "author": {"email": "rurik19@yandex.ru", "name": "Yuriy Evdokimov"}, | ||||
|   "author": {"email": "rurik19@yandex.ru", "name": "Юрий Евдокимов"}, | ||||
|   "private": true, | ||||
|   "version": "1.0.2", | ||||
|   "version": "1.1.0", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/App.tsx
								
								
								
								
							
							
						
						
									
										12
									
								
								src/App.tsx
								
								
								
								
							|  | @ -12,6 +12,7 @@ import { RenderTones } from './rendertones'; | |||
| import { getAudio } from './audio'; | ||||
| import { Copyright } from './copyright'; | ||||
| import { SelectTones } from './selecttones'; | ||||
| import { FormatString } from './utils'; | ||||
| 
 | ||||
| function App() { | ||||
| 
 | ||||
|  | @ -68,6 +69,13 @@ const playPlayList = () => { | |||
|   playlist[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()) | ||||
| 
 | ||||
| return ( | ||||
|       <> | ||||
|       <h1>{strings.mainHeader}</h1> | ||||
|  | @ -96,14 +104,14 @@ return ( | |||
|       <ListGroup.Item> | ||||
|         { status == Status.params && | ||||
|           <> | ||||
|         Выбрано {state.initiales!.length} инициалей,  {state.finales!.length}  финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов ,  | ||||
|           {info()} | ||||
|         <br/> | ||||
|         <Button | ||||
|           variant={state.isFound!() ? "success" : "secondary"} | ||||
|           size="lg" | ||||
|           onClick={()=>setStatus(Status.prepare)} | ||||
|           disabled={!state.isFound!()}> | ||||
|             {state.isFound!() ?  strings.beginDictation : strings.selectInitAndFin } | ||||
|             {state.isFound!() ?  strings.beginDictation : strings.selectInitFinAndTone } | ||||
|           </Button> | ||||
|           </> | ||||
|         } | ||||
|  |  | |||
|  | @ -5,13 +5,21 @@ export const strings = { | |||
|   selectAll:  'Выбрать все', | ||||
|   unselectAll: 'Снять все', | ||||
|   params: 'Параметры', | ||||
|   sylCount: 'Количество слогов', | ||||
|   sylCount: 'Количество слогов: ', | ||||
|   beginDictation: "Начать диктант!", | ||||
|   selectInitAndFin: "Выберите инициали и финали", | ||||
|   selectInitFinAndTone: "Выберите инициали, финали и тоны", | ||||
|   showSyllables: "Показать слоги", | ||||
|   playAgain: "Повторить", | ||||
|   toBegin: "В начало", | ||||
|   version: "Версия", | ||||
|   cpr: "©", | ||||
|   selectTones: "Выберите тоны" | ||||
|   selectTones: "Выберите тоны", | ||||
|   first: "Первый", | ||||
|   second: "Второй", | ||||
|   third: "Третий", | ||||
|   fourth: "Четвёртый", | ||||
|   zeroth: "Нулевой", | ||||
|   infoFormat: "Выбрано {0} инициалей,  {1}  финалей, {2} тонов, найдено {3} слогов, {4} тонов", | ||||
|   pauseFormat: "Пауза между слогами, в секундах: {0}", | ||||
|   mail: "емайл:" | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/Utils.ts
								
								
								
								
							
							
						
						
									
										32
									
								
								src/Utils.ts
								
								
								
								
							|  | @ -3,13 +3,36 @@ import { BtnColor, SylPart, Syllable } from "./types" | |||
| 
 | ||||
| export const isEnabled = (arr: SylPart[], find: string): boolean => arr.some((str) => str.index==find) | ||||
| 
 | ||||
| export const toggle = ( arr: SylPart[], part: SylPart ):SylPart[] => { | ||||
| export const toggleSylPart = ( 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) | ||||
|  | @ -36,3 +59,10 @@ export const GetSyllablesByInitAndFin = (initiales:SylPart[], finales: SylPart[] | |||
|   } | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
|  | @ -2,4 +2,5 @@ import { ReactElement } from "react"; | |||
| import { strings } from "./strings"; | ||||
| import { author } from '../package.json' | ||||
| 
 | ||||
| export const Copyright = ():ReactElement => <div className="cpr">{strings.version} {import.meta.env.VITE_REACT_APP_VERSION} {strings.cpr}{author.name}</div> | ||||
| export const Copyright = ():ReactElement =>  | ||||
| <div className="cpr">{strings.version} {import.meta.env.VITE_REACT_APP_VERSION} {strings.cpr}{author.name} {strings.mail}{author.email}</div> | ||||
|  | @ -3,6 +3,7 @@ import { useStateContext } from "./store"; | |||
| import { Form } from "react-bootstrap"; | ||||
| import { ActionType } from "./reducer"; | ||||
| import { strings } from "./strings"; | ||||
| import { FormatString } from "./utils"; | ||||
| 
 | ||||
| export const Params = (): ReactElement => { | ||||
| 
 | ||||
|  | @ -16,7 +17,7 @@ export const Params = (): ReactElement => { | |||
|     return <> | ||||
|          <Form.Label>{strings.sylCount} {state.sylCount}</Form.Label> | ||||
|           <Form.Range value={state.sylCount} min={5} max={50} step={5} onChange={countdispatcher}/> | ||||
|           <Form.Label>Пауза между слогами {state.sylPause} секунд</Form.Label> | ||||
|           <Form.Label>{ FormatString( strings.pauseFormat , state.sylPause!.toString() )}</Form.Label> | ||||
|           <Form.Range value={state.sylPause} min={1} max={10} step={1} onChange={pausedispatcher}/> | ||||
|     </> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| import { finales, initials, tones } from "./pinyin"; | ||||
| import { IState } from "./store"; | ||||
| import { SylPart, Syllable, Tone } from "./types"; | ||||
| import { GetSyllablesByInitAndFin, getRandomArray, toggle } from "./utils"; | ||||
| import { GetSyllablesByInitAndFin, getRandomArray, toggleSylPart, toggle, included } from "./utils"; | ||||
| 
 | ||||
| export enum ActionType { | ||||
|     toggleOne, toggleAll, refresh, setPause, setCount, setStatus, playing | ||||
|     toggleOne, toggleAll, refresh, setPause, setCount, setStatus, playing, toneSelect | ||||
| } | ||||
| 
 | ||||
| export enum ToggleType { init, fin } | ||||
|  | @ -12,28 +12,30 @@ export type TogglePayload = { type: ToggleType, part: SylPart } | |||
| 
 | ||||
| export type Action = { type: ActionType, payload?: number | ToggleType | TogglePayload }; | ||||
| 
 | ||||
| interface IFounds { foundSyllables:Syllable[], foundTones: Tone[], randomTones: Tone[] } | ||||
| interface IFounds { | ||||
|   foundSyllables:Syllable[], | ||||
|   foundTones: Tone[], | ||||
|   randomTones: Tone[] | ||||
| } | ||||
| 
 | ||||
| interface IInitResult { | ||||
| interface IInitResult extends IFounds { | ||||
|   allInitiales?:boolean, | ||||
|   initiales: SylPart[], | ||||
|   foundSyllables:Syllable[], | ||||
|   foundTones: Tone[], | ||||
|   randomTones: Tone[] | ||||
|   initiales: SylPart[] | ||||
| } | ||||
| 
 | ||||
| interface IFinResult { | ||||
| interface IFinResult extends IFounds { | ||||
|   allfinales?:boolean, | ||||
|   finales: SylPart[], | ||||
|   foundSyllables:Syllable[], | ||||
|   foundTones: Tone[], | ||||
|   randomTones: Tone[] | ||||
|   finales: SylPart[] | ||||
| } | ||||
| 
 | ||||
| const proceedFounds = ( initiales: SylPart[], finales:SylPart[], count: number):IFounds=> | ||||
| interface IToneSelectResult extends IFounds { | ||||
|   toneChecks: number[] | ||||
| } | ||||
| 
 | ||||
| const proceedFounds = ( initiales: SylPart[], finales:SylPart[], tonesCheck:number[], count: number):IFounds=> | ||||
| { | ||||
|   const foundSyllables:Syllable[] = GetSyllablesByInitAndFin( initiales, finales ) | ||||
|   const foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) ) | ||||
|   const foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone && included<number>(tonesCheck, t.num) ) ) ) | ||||
|   const randomTones = getRandomArray(foundTones, count) | ||||
|   return { foundSyllables, foundTones, randomTones } | ||||
| } | ||||
|  | @ -44,7 +46,7 @@ const ProceedAllInitials = (state: IState):IInitResult => | |||
|   return { | ||||
|     allInitiales: !state.allInitiales, | ||||
|     initiales: toggled, | ||||
|     ...proceedFounds(toggled, state.finales, state.sylCount) | ||||
|     ...proceedFounds(toggled, state.finales, state.toneChecks, state.sylCount) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -54,25 +56,32 @@ const ProceedAllFinales = (state: IState):IFinResult => | |||
|   return { | ||||
|     allfinales: !state.allfinales, | ||||
|     finales: toggled, | ||||
|     ...proceedFounds(state.initiales, toggled, state.sylCount) | ||||
|     ...proceedFounds(state.initiales, toggled, state.toneChecks, state.sylCount) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const ProceedInitiale = (state: IState, index: SylPart):IInitResult => | ||||
| { | ||||
|   const toggled = toggle(state.initiales,index) | ||||
|   const toggled = toggleSylPart(state.initiales,index) | ||||
|   return { | ||||
|     initiales: toggled, | ||||
|     ...proceedFounds(toggled, state.finales, state.sylCount) | ||||
|     ...proceedFounds(toggled, state.finales, state.toneChecks, state.sylCount) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const ProceedFinale = (state: IState, index: SylPart):IFinResult => | ||||
| { | ||||
|   const toggled = toggle(state.finales,index) | ||||
|   const toggled = toggleSylPart(state.finales,index) | ||||
|   return { | ||||
|     finales: toggled, | ||||
|     ...proceedFounds(state.initiales, toggled, state.sylCount) | ||||
|     ...proceedFounds(state.initiales, toggled, state.toneChecks, state.sylCount) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const ProceedToneSelect = (state: IState, index: number):IToneSelectResult => { | ||||
|   const toggled = toggle<number>(state.toneChecks, index) | ||||
|   return { toneChecks: toggled, | ||||
|     ...proceedFounds(state.initiales, state.finales, toggled, state.sylCount) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -95,6 +104,8 @@ export const reducer = (state:IState, action:Action):IState => { | |||
|         return {  ...state, ...ProceedFinale(state, (action.payload as TogglePayload).part )  } | ||||
|       return state | ||||
|     } | ||||
|     case ActionType.toneSelect:  return { ...state, ...ProceedToneSelect(state, (action.payload as number) ) } | ||||
| 
 | ||||
|     default: return state | ||||
|   } | ||||
| } | ||||
|  | @ -1,34 +1,53 @@ | |||
| import { useStateContext } from "./store" | ||||
| import { Form } from "react-bootstrap"; | ||||
| import { strings } from "./strings"; | ||||
| import { ActionType } from "./reducer"; | ||||
| 
 | ||||
| export const SelectTones = () => { | ||||
|  //   const { state, dispatch } = useStateContext()
 | ||||
|     const { state, dispatch } = useStateContext() | ||||
|     const onToneChange = ( toneNo: number ) => | ||||
|          dispatch({ type:ActionType.toneSelect, payload: toneNo }) | ||||
|     return <> | ||||
|     <Form> | ||||
|       <Form.Check // prettier-ignore | ||||
|         inline | ||||
|         type="switch" | ||||
|         id="first-switch" | ||||
|         label="Первый" | ||||
|         label={strings.first} | ||||
|         checked = {state.isToneChecked!(1)} | ||||
|         onChange = { () => onToneChange(1) } | ||||
|       /> | ||||
|       <Form.Check // prettier-ignore | ||||
|         inline | ||||
|         type="switch" | ||||
|         label="Второй" | ||||
|         label={strings.second} | ||||
|         id="second-switch" | ||||
|         checked = {state.isToneChecked!(2)} | ||||
|         onChange = { () => onToneChange(2) } | ||||
|       /> | ||||
|     </Form><Form> | ||||
|       <Form.Check // prettier-ignore | ||||
|         inline | ||||
|         type="switch" | ||||
|         id="third-switch" | ||||
|         label="Третий" | ||||
|         label={strings.third} | ||||
|         checked = {state.isToneChecked!(3)} | ||||
|         onChange = { () => onToneChange(3) } | ||||
|       /> | ||||
|       <Form.Check // prettier-ignore | ||||
|         inline | ||||
|             type="switch" | ||||
|         label="Четвёртый" | ||||
|         label={strings.fourth} | ||||
|         id="fourth-switch" | ||||
|         checked = {state.isToneChecked!(4)} | ||||
|         onChange = { () => onToneChange(4) } | ||||
|       /> | ||||
|       <Form.Check // prettier-ignore | ||||
|         inline | ||||
|             type="switch" | ||||
|         label={strings.zeroth} | ||||
|         id="zeroth-switch" | ||||
|         checked = {state.isToneChecked!(5)} | ||||
|         onChange = { () => onToneChange(5) } | ||||
|       /> | ||||
|     </Form> | ||||
|     </> | ||||
|  |  | |||
|  | @ -13,9 +13,11 @@ export interface IState { | |||
|     foundSyllables: Syllable[], | ||||
|     foundTones: Tone[], | ||||
|     randomTones: Tone[], | ||||
|     toneChecks: number[], | ||||
|     allEnabled: (type: ToggleType) => false, | ||||
|     isEnabled: (type: ToggleType, index: string) => false, | ||||
|     isFound: () => false | ||||
|     isFound: () => false, | ||||
|     isToneChecked: (toneNo: number) => false | ||||
| } | ||||
| 
 | ||||
| export interface IStore { | ||||
|  | @ -33,6 +35,7 @@ export const defaultState:object = { | |||
|      foundSyllables: [], | ||||
|      foundTones: [], | ||||
|      randomTones: [], | ||||
|      toneChecks: [ 1, 2, 3, 4, 5 ], | ||||
|      allEnabled: function(type: ToggleType) { | ||||
|         if ( type === ToggleType.init ) return (this as IState).allInitiales | ||||
|         if ( type === ToggleType.fin )  return (this as IState).allfinales | ||||
|  | @ -43,7 +46,9 @@ export const defaultState:object = { | |||
|         if ( type === ToggleType.fin )  return isEnabled((this as IState).finales!, index) | ||||
|         return false | ||||
|       }, | ||||
|       isFound: function():boolean { return (this as IState).foundSyllables!.length > 0 } | ||||
|       isFound: function():boolean { return (this as IState).foundSyllables!.length > 0 && (this as IState).toneChecks!.length > 0} | ||||
|       , | ||||
|       isToneChecked: function(toneNo: number):boolean{ return (this as IState).toneChecks.includes(toneNo) } | ||||
|     } | ||||
| 
 | ||||
| export const AppContext:Context<IStore> = createContext<IStore>({ state: defaultState as IState, dispatch: () => null }) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue