tones, begin of state management

main
Yuriy Evdokimov 2023-10-04 22:43:20 +05:00
parent 5dd170683f
commit 6a3c40b36c
5 changed files with 210 additions and 86 deletions

View File

@ -1,12 +1,11 @@
import { useState } from 'react' import { useState } from 'react'
import './App.css' import './App.css'
import Accordion from 'react-bootstrap/Accordion';
import Form from 'react-bootstrap/Form'; import Form from 'react-bootstrap/Form';
import { Badge, Button, Modal } from 'react-bootstrap'; import { Badge, Button, ListGroup } from 'react-bootstrap';
import { initials, finales, syllables, tones } from './Data'; import { initials, finales, syllables, tones } from './data';
import { isEnabled, toggle } from './Utils'; import { getRandomTones, isEnabled, toggle } from './utils';
import { Found, Syllable, Tone } from './Types'; import { Found, Syllable, Tone } from './types';
import { strings } from './Strings'; import { strings } from './strings';
const defaultFoundState:Found = { const defaultFoundState:Found = {
allfinales: false, allfinales: false,
@ -14,9 +13,12 @@ const defaultFoundState:Found = {
finales: [], finales: [],
initiales: [], initiales: [],
syllables: [], syllables: [],
tones: [] toneS: [],
randomTones: []
} }
enum Status {params, plaing, plaied, showlist}
function App() { function App() {
const [ count, setCount ] = useState(10) const [ count, setCount ] = useState(10)
@ -25,71 +27,135 @@ function App() {
const [ found, setFound ] = useState(defaultFoundState) const [ found, setFound ] = useState(defaultFoundState)
const onchangepause = (e: React.ChangeEvent<HTMLInputElement> ) => setPause( Number(e.target.value) ) const onchangepause = (e: React.ChangeEvent<HTMLInputElement> ) => setPause( Number(e.target.value) )
const onchangecount = (e: React.ChangeEvent<HTMLInputElement> ) => setCount( Number(e.target.value) ) const onchangecount = (e: React.ChangeEvent<HTMLInputElement> ) => {
setCount( Number(e.target.value) )
refreshRandomTones()
}
//---------------- to found state //---------------- to found state
const toggleAllInitiales = () => { const toggleAllInitiales = () => {
let toggled = found.allInitiales ? [] : initials
let foundSyllables:Syllable[] = syllables.filter( syl =>
toggled.includes(syl.initiale) && found.finales.includes(syl.finale) )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
let foundRandomTones = getRandomTones( foundTones, count )
setFound({...found, setFound({...found,
allInitiales: !found.allInitiales, allInitiales: !found.allInitiales,
initiales: found.allInitiales ? [] : initials initiales: toggled,
syllables: foundSyllables,
toneS: foundTones,
randomTones: foundRandomTones
}) })
} }
const toggleInitialsState = (caption: String) => setFound({...found, const toggleInitialsState = (caption: string) => {
initiales: toggle(found.initiales,caption) let toggled = toggle(found.initiales,caption)
let foundSyllables:Syllable[] = syllables.filter( syl =>
toggled.includes(syl.initiale) && found.finales.includes(syl.finale) )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
let foundRandomTones = getRandomTones( foundTones, count )
setFound({...found,
initiales: toggled,
syllables: foundSyllables,
toneS: foundTones,
randomTones: foundRandomTones
}) })
const toggleInitiales = (captions: String[]) => setFound({...found, }
initiales: [...found.initiales.filter( f => !captions.includes(f)),
...captions.filter( c => !found.initiales.includes(c))]
})
// let arr3 = [...arr1, ...arr2];
//uniqueItems = [...new Set(items)]
const toggleAllFinales = () => { const toggleAllFinales = () => {
let toggled = found.allfinales ? [] : finales.map( f => f.finale)
let foundSyllables:Syllable[] = syllables.filter ( syl =>
found.initiales.includes( syl.initiale) && toggled.includes( syl.finale ) )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
let foundRandomTones = getRandomTones( foundTones, count )
setFound({...found, setFound({...found,
allfinales: !found.allfinales, allfinales: !found.allfinales,
finales: found.allfinales ? [] : finales.map( f => f.finale) finales: toggled,
syllables: foundSyllables,
toneS: foundTones,
randomTones: foundRandomTones
}) })
}
const toggleFinalsState = (caption: string) => {
let toggled = toggle(found.finales,caption)
let foundSyllables:Syllable[] = syllables.filter ( syl =>
found.initiales.includes( syl.initiale) && toggled.includes( syl.finale ) )
let foundTones = tones.filter( t => foundSyllables.some( syl => syl.tones.some( st => st===t.tone) ) )
let foundRandomTones = getRandomTones( foundTones, count )
setFound({...found,
finales: toggled,
syllables: foundSyllables,
toneS: foundTones,
randomTones: foundRandomTones
})
}
const isFound = ():boolean => found.syllables.length > 0
const [plaingNo, setPlaingNo ] = useState(0)
// ---- опять по новой
const [ status, setStatus ] = useState(Status.params)
const beginDictation = (): void => {
refreshRandomTones()
console.debug(found.randomTones)
playDictation2(found.randomTones)
} }
const toggleFinalsState = (caption: String) => setFound({...found,
finales: toggle(found.finales,caption)
})
//----------------------
const foundSyllables = ():Syllable[] => syllables.filter( (syl) => ( found.initiales.some( (i) => i == syl.initiale ) ) const playDictation2 = (randomTones: Tone[]) => {
&& ( found.finales.some( (f) => f == syl.finale ) ) ) if ( randomTones.length == 0 ) return
let audios:HTMLAudioElement[] = []
randomTones.forEach(element => {
audios = [...audios, new Audio(`/src/assets/audio/${element.tone}.mp3`) ]
});
console.debug(audios)
if ( audios.length == 0 ) return
for(let x=0; x<audios.length-1;x++)
{
audios[x].onended = () => setTimeout( () => {
let pno = x+2
setPlaingNo(pno)
audios[x+1].play()
}, 1000*pause ) ;
}
audios[audios.length-1].onended = () => setStatus(Status.plaied)
setStatus(Status.plaing)
setPlaingNo(1)
audios[0].play()
}
const isFound = ():boolean => foundSyllables().length > 0 const renderRandomTones2 = () => {
return found.randomTones.map( (ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{' '}</span> })
}
const [show, setShow] = useState(false); const refresh = () => {
refreshRandomTones()
setStatus(Status.params)
}
const handleClose = () => setShow(false); const refreshRandomTones = () => {
const handleShow = () => setShow(true); let foundRandomTones = getRandomTones( found.toneS, count )
setFound({...found,
const foundTones = ():Tone[] => tones.filter( (t) => foundSyllables().some( (syl) => syl.tones.some( (st) => st===t.tone) ) ) randomTones: foundRandomTones
// const randomTones = ( length:number, fromTones:Tone[]):Tone[] => { })
// const ftones = foundTones(); }
// return ftones
// }
return ( return (
<> <>
<h1>Диктант pīnyīn</h1> <h1>Диктант pīnyīn</h1>
<Accordion defaultActiveKey="0"> <ListGroup>
<Accordion.Item eventKey="0"> <ListGroup.Item disabled={status != Status.params}>
<Accordion.Header>{strings.selectInitiales}</Accordion.Header> <h2>{strings.selectInitiales}</h2>
<Accordion.Body>
<Button variant={found.allInitiales ? "primary" : "outline-primary"} <Button variant={found.allInitiales ? "primary" : "outline-primary"}
onClick= {()=>toggleInitiales( found.allInitiales ? [] : initials )}> onClick= {()=>toggleAllInitiales()}>
{found.allInitiales ? strings.unselectAll : strings.selectAll} {found.allInitiales ? strings.unselectAll : strings.selectAll}
</Button> </Button>
{initials.map( (text, i) => <Button variant={isEnabled(found.initiales, text) ? "primary" : "outline-primary"} {initials.map( (text, i) => <Button variant={isEnabled(found.initiales, text) ? "primary" : "outline-primary"}
key={i} onClick={()=>toggleInitiales([text])}> key={i} onClick={()=>toggleInitialsState(text)}>
{text} {text}
</Button> </Button>
)} )}
</Accordion.Body> </ListGroup.Item>
</Accordion.Item> <ListGroup.Item disabled={status != Status.params}>
<Accordion.Item eventKey="1"> <h2>{strings.selectFinales}</h2>
<Accordion.Header>{strings.selectFinales}</Accordion.Header>
<Accordion.Body>
<Button variant={found.allfinales ? "success" : "outline-success"} <Button variant={found.allfinales ? "success" : "outline-success"}
onClick= {toggleAllFinales}> onClick= {toggleAllFinales}>
{found.allfinales ? strings.unselectAll : strings.selectAll} {found.allfinales ? strings.unselectAll : strings.selectAll}
@ -99,48 +165,45 @@ const toggleFinalsState = (caption: String) => setFound({...found,
{fin.caption} {fin.caption}
</Button> </Button>
)} )}
</Accordion.Body> </ListGroup.Item>
</Accordion.Item> <ListGroup.Item disabled={status != Status.params}>
<Accordion.Item eventKey="2"> <h2>{strings.params}</h2>
<Accordion.Header>{strings.params}</Accordion.Header>
<Accordion.Body>
<Form.Label>Количество слогов {count}</Form.Label> <Form.Label>Количество слогов {count}</Form.Label>
<Form.Range value={count} min={5} max={50} step={5} onChange={onchangecount}/> <Form.Range value={count} min={5} max={50} step={5} onChange={onchangecount}/>
<Form.Label>Пауза между слогами {pause} секунд</Form.Label> <Form.Label>Пауза между слогами {pause} секунд</Form.Label>
<Form.Range value={pause} min={1} max={10} step={1} onChange={onchangepause}/> <Form.Range value={pause} min={1} max={10} step={1} onChange={onchangepause}/>
</Accordion.Body> </ListGroup.Item>
</Accordion.Item> <ListGroup.Item>
</Accordion> { status == Status.params &&
<div className="card"> <>
Выбрано {found.initiales.length} инициалей, {found.finales.length} финалей, количество слогов {count}, пауза между слогами {pause} секунд. Выбрано {found.initiales.length} инициалей, {found.finales.length} финалей, найдено {found.syllables.length} слогов, { found.toneS.length } тонов ,
<br/> <br/>
Найдено {foundSyllables().length} слогов, { foundSyllables().reduce( <Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={()=>beginDictation()} disabled={!isFound()}>
( p, c ) => p + c.tones.length , 0 {isFound() ? "Начать диктант!" : "Выберите инициали и финали" }
) } тонов </Button>
</>
}
{
status == Status.plaing &&
<h1>Воспроизводится...{plaingNo}</h1>
}
{
status == Status.plaied &&
<Button variant="success" onClick={()=>setStatus(Status.showlist)}>Показать слоги</Button>
}
{
status == Status.showlist &&
<>
<div>{renderRandomTones2()}</div>
<br/> <br/>
<Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={handleShow} disabled={!isFound()}> <Button variant="success" onClick={()=>refresh()}>В начало</Button>
{isFound() ? "Поехали!" : "Выберите инициали и финали" } </>
</Button> }
</div> </ListGroup.Item>
<Modal show={show} onHide={handleClose}> </ListGroup>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>
{foundTones().map( (t, i) => <><Badge key={i} bg="success" pill>{t.caption}</Badge>{' '}</>)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</> </>
) )
} }
export default App export default App

View File

@ -1,6 +1,6 @@
import { Finale, Syllable, Tone } from "./Types" import { Finale, Syllable, Tone } from "./types"
export const initials: String[] = [ '-', 'y', 'w', export const initials: string[] = [ '-', 'y', 'w',
'b', 'p', 'm', 'f', 'd', 't', 'n', 'l', 'g', 'k', 'b', 'p', 'm', 'f', 'd', 't', 'n', 'l', 'g', 'k',
'h', 'j', 'q', 'x', 'zh', 'ch', 'sh', 'r', 'z', 'c', 's' 'h', 'j', 'q', 'x', 'zh', 'ch', 'sh', 'r', 'z', 'c', 's'
] ]

View File

@ -1,8 +1,29 @@
export const isEnabled = (arr: String[], caption: String): Boolean => arr.some((btn) => btn==caption) import { Tone } from "./types"
export const toggle = ( arr: String[], caption: String ):String[] => { export const isEnabled = (arr: string[], caption: string): boolean => arr.some((btn) => btn==caption)
export const toggle = ( arr: string[], caption: string ):string[] => {
if ( arr.some((btn) => btn==caption) ) if ( arr.some((btn) => btn==caption) )
return arr.filter((el)=>el!==caption) return arr.filter((el)=>el!==caption)
else else
return [ ...arr, caption] return [ ...arr, caption]
} }
export const genrateRandomNumber = (min: number, max: number):number => {
min = Math.ceil(min)
max = Math.floor(max)
let rnd = Math.floor(Math.random() * (max - min + 1)) + min
return rnd
}
export const getRandomTones = (fromTomes:Tone[], counT:number):Tone[] => {
let _tone:Tone[] = []
if (fromTomes.length==0) {
return _tone
}
for( let x=0; x<counT; x++) {
let randomN = genrateRandomNumber(0,fromTomes.length-1)
_tone = [..._tone, fromTomes[randomN]]
}
return _tone
}

23
src/reducer.ts Normal file
View File

@ -0,0 +1,23 @@
import { Found } from "./types";
export enum ActionType {
toggleInitiale, toggleFinale, toggleAllInitiales, toggleAllFinales
}
export type Action = { type: ActionType } | { type: ActionType };
export const reducer = (state:Found, action:Action):Found => {
switch (action.type) {
default: return state
}
}
export const defaultState:Found = {
allfinales: false,
allInitiales: false,
finales: [],
initiales: [],
syllables: [],
toneS: [],
randomTones: []
}

17
src/store.tsx Normal file
View File

@ -0,0 +1,17 @@
import { Context, createContext, Dispatch, ReactElement, useContext, useReducer } from "react";
import { reducer, defaultState, Action } from "./reducer"
import { Found } from "./types";
export interface IStore {
state: Found
dispatch?: Dispatch<Action>
}
export const AppContext:Context<IStore> = createContext<IStore>({ state: defaultState, dispatch: () => null })
export const useStateContext = () => useContext(AppContext);
export const StateProvider = ({ children }: { children: ReactElement }) => {
const [state, dispatch] = useReducer(reducer, defaultState);
return <AppContext.Provider value={{ state, dispatch }} children={children} />
}