multiple changes in states, useEffect for chaining

main
Yuriy Evdokimov 2023-10-17 22:59:47 +05:00
parent 4546eb9140
commit f118066b9b
7 changed files with 1394 additions and 1358 deletions

View File

@ -42,6 +42,21 @@
} }
.btn { .btn {
margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
margin-right: 5px; margin-right: 5px;
} }
.syllable {
font-weight: bold;
font-size: xx-large;
}
.syllable:hover {
cursor:pointer
}
.tone1 { color: rgb(255, 0, 0) }
.tone2 { color: rgb(255, 170, 0) }
.tone3 { color: rgb(85, 170, 0) }
.tone4 { color: rgb(0, 0, 255) }
.tone5 { color: rgb(50, 50, 50) }

View File

@ -1,12 +1,12 @@
import { useState } from 'react' import { useEffect, useState } from 'react'
import './App.css' import './App.css'
import { Badge, Button, ListGroup } from 'react-bootstrap'; import { Button, ListGroup } from 'react-bootstrap';
import { initials, finales } from './data'; import { initials, finales } from './data';
import { BtnColor, Status } from './types'; import { BtnColor, Status, Tone } from './types';
import { strings } from './strings'; import { strings } from './strings';
import { useStateContext } from './store'; import { useStateContext } from './store';
import { Params } from './params'; import { Params } from './params';
import { ActionType, ToggleType } from './reducer'; import { ToggleType } from './reducer';
import { ButtonSet } from './buttons'; import { ButtonSet } from './buttons';
function App() { function App() {
@ -14,20 +14,31 @@ function App() {
const { state, dispatch } = useStateContext(); const { state, dispatch } = useStateContext();
const [ plaingNo, setPlaingNo ] = useState(0) const [ plaingNo, setPlaingNo ] = useState(0)
const [ status, setStatus ] = useState(Status.params)
const [ playlist, setPlaylist ] = useState( [] as HTMLAudioElement[] )
const setStatus = (status: Status) => { dispatch({ type: ActionType.setStatus, payload: status}) } useEffect( () => {
if (status===Status.prepare) {
const beginDictation = (): void => { console.log('effect for preparing')
dispatch({ type: ActionType.prepare }) preparePlayList(state.randomTones!)
playDictation2()
} }
if (status===Status.playing) {
console.log('effect for playing')
playPlayList()
}
}, [status])
const playDictation2 = () => { useEffect( () => {
console.log('effect for playList')
setStatus(Status.playing)
} , [playlist] )
const preparePlayList = (tones: Tone[]) => {
console.log('tones for playing') console.log('tones for playing')
state.randomTones!.forEach(element => console.log(element)) tones!.forEach(element => console.log(element))
if ( state.randomTones!.length == 0 ) return if ( tones!.length == 0 ) return
let audios:HTMLAudioElement[] = [] let audios:HTMLAudioElement[] = []
state.randomTones!.forEach(element => { tones!.forEach(element => {
audios = [...audios, new Audio(`/assets/audio/${element.tone}.mp3`) ] audios = [...audios, new Audio(`/assets/audio/${element.tone}.mp3`) ]
}); });
console.debug(audios) console.debug(audios)
@ -41,14 +52,30 @@ const playDictation2 = () => {
}, 1000*state.sylPause! ) ; }, 1000*state.sylPause! ) ;
} }
audios[audios.length-1].onended = () => setStatus(Status.plaied) audios[audios.length-1].onended = () => setStatus(Status.plaied)
setStatus(Status.plaing) setPlaylist(audios)
setPlaingNo(1)
audios[0].play()
} }
const renderRandomTones2 = () => { const playPlayList = () => {
if (playlist.length===0) return;
setStatus(Status.playing)
setPlaingNo(1)
playlist[0].play()
}
const playTone = (index: string) => {
let audio:HTMLAudioElement = new Audio(`/assets/audio/${index}.mp3`)
audio.play()
}
const renderPlayList = () => {
return state.randomTones!.map( return state.randomTones!.map(
(ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{' '}</span> } (ton, i) => { return <span key={i}
className={`syllable tone${ton.num}`}
onClick = {()=>playTone(ton.tone)}
>
{i+1}{'. '}{ton.caption}{' '}
</span> }
) )
} }
@ -57,49 +84,53 @@ const renderRandomTones2 = () => {
<h1>{strings.mainHeader}</h1> <h1>{strings.mainHeader}</h1>
<ListGroup> <ListGroup>
<ListGroup.Item disabled={state.status != Status.params}> <ListGroup.Item disabled={status != Status.params}>
<h2>{strings.selectInitiales}</h2> <h2>{strings.selectInitiales}</h2>
<ButtonSet source={initials} color={BtnColor.blue} toggle={ToggleType.init}/> <ButtonSet source={initials} color={BtnColor.blue} toggle={ToggleType.init}/>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item disabled={state.status != Status.params}> <ListGroup.Item disabled={status != Status.params}>
<h2>{strings.selectFinales}</h2> <h2>{strings.selectFinales}</h2>
<ButtonSet source={finales} color={BtnColor.green} toggle={ToggleType.fin}/> <ButtonSet source={finales} color={BtnColor.green} toggle={ToggleType.fin}/>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item disabled={state.status != Status.params}> <ListGroup.Item disabled={status != Status.params}>
<h2>{strings.params}</h2> <h2>{strings.params}</h2>
<Params/> <Params/>
</ListGroup.Item> </ListGroup.Item>
<ListGroup.Item> <ListGroup.Item>
{ state.status == Status.params && { status == Status.params &&
<> <>
Выбрано {state.initiales!.length} инициалей, {state.finales!.length} финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов , Выбрано {state.initiales!.length} инициалей, {state.finales!.length} финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов ,
<br/> <br/>
<Button <Button
variant={state.isFound!() ? "success" : "secondary"} variant={state.isFound!() ? "success" : "secondary"}
size="lg" size="lg"
onClick={()=>beginDictation()} onClick={()=>setStatus(Status.prepare)}
disabled={!state.isFound!()}> disabled={!state.isFound!()}>
{state.isFound!() ? strings.beginDictation : strings.selectInitAndFin } {state.isFound!() ? strings.beginDictation : strings.selectInitAndFin }
</Button> </Button>
</> </>
} }
{ {
state.status == Status.plaing && status == Status.playing &&
<h1>Воспроизводится...{plaingNo}</h1> <h1>Воспроизводится...{plaingNo}</h1>
} }
{ {
state.status == Status.plaied && status == Status.plaied &&
<Button variant="success" onClick={()=>setStatus(Status.showlist)}>Показать слоги</Button> <>
<Button variant="success" onClick={()=>setStatus(Status.showlist)}>{strings.showSyllables}</Button>
<Button variant="success" onClick={()=>{setStatus(Status.playing)}}>{strings.playAgain}</Button>
</>
} }
{ {
state.status == Status.showlist && status == Status.showlist &&
<> <>
<div>{renderRandomTones2()}</div> <div>{renderPlayList()}</div>
<br/> <br/>
<Button variant="success" onClick={()=> setStatus(Status.params)}>В начало</Button> // here add effect to new randomTones
<Button variant="success" onClick={()=> setStatus(Status.params)}>{strings.toBegin}</Button>
</> </>
} }
</ListGroup.Item> </ListGroup.Item>

File diff suppressed because it is too large Load Diff

View File

@ -7,5 +7,8 @@ export const strings = {
params: 'Параметры', params: 'Параметры',
sylCount: 'Количество слогов', sylCount: 'Количество слогов',
beginDictation: "Начать диктант!", beginDictation: "Начать диктант!",
selectInitAndFin: "Выберите инициали и финали" selectInitAndFin: "Выберите инициали и финали",
showSyllables: "Показать слоги",
playAgain: "Повторить",
toBegin: "В начало"
} }

View File

@ -15,7 +15,8 @@ export type SylPart = {
export type Tone = { export type Tone = {
tone: string, tone: string,
caption: string caption: string,
num: number
} }
export type Found = { export type Found = {
@ -33,4 +34,4 @@ export enum BtnColor {
green = "success" green = "success"
} }
export enum Status {params, plaing, plaied, showlist} export enum Status {params, prepare, playing, plaied, showlist}

View File

@ -1,10 +1,10 @@
import { finales, initials, tones } from "./data"; import { finales, initials, tones } from "./data";
import { IState } from "./store"; import { IState } from "./store";
import { SylPart, Syllable, Tone, Status } from "./types"; import { SylPart, Syllable, Tone } from "./types";
import { GetSyllablesByInitAndFin, getRandomArray, toggle } from "./utils"; import { GetSyllablesByInitAndFin, getRandomArray, toggle } from "./utils";
export enum ActionType { export enum ActionType {
toggleOne, toggleAll, refreshPlayList, setPause, setCount, setStatus, prepare toggleOne, toggleAll, refreshPlayList, setPause, setCount, setStatus, playing
} }
export enum ToggleType { init, fin } export enum ToggleType { init, fin }
@ -12,89 +12,77 @@ export type TogglePayload = { type: ToggleType, part: SylPart }
export type Action = { type: ActionType, payload?: any }; export type Action = { type: ActionType, payload?: any };
const ProceedAllInitials = (state: IState):{ allInitiales:boolean, initiales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} => interface IFounds { foundSyllables:Syllable[], foundTones: Tone[], randomTones: Tone[] }
interface IInitResult {
allInitiales?:boolean,
initiales: SylPart[],
foundSyllables:Syllable[],
foundTones: Tone[],
randomTones: Tone[]
}
interface IFinResult {
allfinales?:boolean,
finales: SylPart[],
foundSyllables:Syllable[],
foundTones: Tone[],
randomTones: Tone[]
}
const proceedFounds = ( initiales: SylPart[], finales:SylPart[], count: number):IFounds=>
{
let foundSyllables:Syllable[] = GetSyllablesByInitAndFin( initiales, finales )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
let randomTones = getRandomArray(foundTones, count)
return { foundSyllables, foundTones, randomTones }
}
const ProceedAllInitials = (state: IState):IInitResult =>
{ {
let toggled = state.allInitiales ? [] as SylPart[] : initials let toggled = state.allInitiales ? [] as SylPart[] : initials
let foundSyllables:Syllable[] = GetSyllablesByInitAndFin( toggled, state.finales )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
return { return {
allInitiales: !state.allInitiales, allInitiales: !state.allInitiales,
initiales: toggled, initiales: toggled,
foundSyllables: foundSyllables, ...proceedFounds(toggled, state.finales, state.sylCount)
foundTones: foundTones
} }
} }
const ProceedAllFinales = (state: IState):{ allfinales:boolean, finales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} => const ProceedAllFinales = (state: IState):IFinResult =>
{ {
let toggled = state.allfinales ? [] as SylPart[] : finales let toggled = state.allfinales ? [] as SylPart[] : finales
let foundSyllables:Syllable[] = GetSyllablesByInitAndFin( state.initiales, toggled )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
return { return {
allfinales: !state.allfinales, allfinales: !state.allfinales,
finales: toggled, finales: toggled,
foundSyllables: foundSyllables, ...proceedFounds(state.initiales, toggled, state.sylCount)
foundTones: foundTones
} }
} }
const ProceedInitiale = (state: IState, index: SylPart):{ initiales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} => const ProceedInitiale = (state: IState, index: SylPart):IInitResult =>
{ {
let toggled = toggle(state.initiales,index) let toggled = toggle(state.initiales,index)
let foundSyllables:Syllable[] = GetSyllablesByInitAndFin( toggled, state.finales )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
return { return {
initiales: toggled, initiales: toggled,
foundSyllables: foundSyllables, ...proceedFounds(toggled, state.finales, state.sylCount)
foundTones: foundTones
} }
} }
const ProceedFinale = (state: IState, index: SylPart):{ finales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} => const ProceedFinale = (state: IState, index: SylPart):IFinResult =>
{ {
let toggled = toggle(state.finales,index) let toggled = toggle(state.finales,index)
let foundSyllables:Syllable[] = GetSyllablesByInitAndFin( state.initiales , toggled )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
return { return {
finales: toggled, finales: toggled,
foundSyllables: foundSyllables, ...proceedFounds(state.initiales, toggled, state.sylCount)
foundTones: foundTones
} }
} }
const prepareDictation = (state: IState): { randomTones: Tone[] } => {
var newRandom = getRandomArray( state.foundTones, state.sylCount! )
if (newRandom.length > 0) {
let audios:HTMLAudioElement[] = []
newRandom!.forEach(element => {
audios = [...audios, new Audio(`/assets/audio/${element.tone}.mp3`) ]
});
if ( audios.length > 0 ) {
for(let x=0; x<audios.length-1;x++)
{
audios[x].onended = () => setTimeout( () => {
// let pno = x+2
// setPlaingNo(pno)
audios[x+1].play()
}, 1000*state.sylPause! ) ;
}
}
}
console.log('tones for refresh')
newRandom!.forEach(element => console.log(element))
return { randomTones: newRandom }
}
// see https://stackoverflow.com/questions/36730793/can-i-dispatch-an-action-in-reducer
export const reducer = (state:IState, action:Action):IState => { export const reducer = (state:IState, action:Action):IState => {
switch (action.type) { switch (action.type) {
case ActionType.setPause: return { ...state, sylPause: action.payload as number } case ActionType.setPause: return { ...state, sylPause: action.payload as number }
case ActionType.setCount: return { ...state, sylCount: action.payload as number } case ActionType.setCount: return { ...state,
// case ActionType.refreshPlayList: return { ...state, randomTones: getRandomArray( state.foundTones, state.sylCount! ) } sylCount: action.payload as number,
case ActionType.setStatus: return { ...state, status: action.payload as Status} randomTones: getRandomArray(state.foundTones, action.payload as number) }
// case ActionType.setStatus: return { ...state, status: action.payload as Status}
case ActionType.prepare: return { ...state, ...prepareDictation(state) }
case ActionType.toggleAll: { case ActionType.toggleAll: {
if (action.payload as ToggleType === ToggleType.init) return { ...state, ...ProceedAllInitials(state) } if (action.payload as ToggleType === ToggleType.init) return { ...state, ...ProceedAllInitials(state) }

View File

@ -1,6 +1,6 @@
import { Context, createContext, Dispatch, ReactElement, useContext, useReducer } from "react"; import { Context, createContext, Dispatch, ReactElement, useContext, useReducer } from "react";
import { reducer, Action, ToggleType } from "./reducer" import { reducer, Action, ToggleType } from "./reducer"
import { Status, Syllable, SylPart, Tone } from "./types"; import { Syllable, SylPart, Tone } from "./types";
import { isEnabled } from "./utils"; import { isEnabled } from "./utils";
export interface IState { export interface IState {
@ -15,8 +15,7 @@ export interface IState {
randomTones: Tone[], randomTones: Tone[],
allEnabled: (type: ToggleType) => false, allEnabled: (type: ToggleType) => false,
isEnabled: (type: ToggleType, index: string) => false, isEnabled: (type: ToggleType, index: string) => false,
isFound: () => false, isFound: () => false
status: Status
} }
export interface IStore { export interface IStore {
@ -44,8 +43,7 @@ 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 }
status: Status.params
} }
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 })