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