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