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