tones selection, v1.1.0

main
Yuriy Evdokimov 2023-10-26 14:54:01 +05:00
parent dacecb3d9e
commit 50e99a08bf
9 changed files with 122 additions and 39 deletions

View File

@ -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",

View File

@ -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>
</> </>
} }

View File

@ -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: "емайл:"
} }

View File

@ -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;
}

View File

@ -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>

View File

@ -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}/>
</> </>

View File

@ -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
} }
} }

View File

@ -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>
</> </>

View File

@ -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 })