tones, begin of state management
parent
5dd170683f
commit
6a3c40b36c
225
src/App.tsx
225
src/App.tsx
|
|
@ -1,12 +1,11 @@
|
|||
import { useState } from 'react'
|
||||
import './App.css'
|
||||
import Accordion from 'react-bootstrap/Accordion';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import { Badge, Button, Modal } from 'react-bootstrap';
|
||||
import { initials, finales, syllables, tones } from './Data';
|
||||
import { isEnabled, toggle } from './Utils';
|
||||
import { Found, Syllable, Tone } from './Types';
|
||||
import { strings } from './Strings';
|
||||
import { Badge, Button, ListGroup } from 'react-bootstrap';
|
||||
import { initials, finales, syllables, tones } from './data';
|
||||
import { getRandomTones, isEnabled, toggle } from './utils';
|
||||
import { Found, Syllable, Tone } from './types';
|
||||
import { strings } from './strings';
|
||||
|
||||
const defaultFoundState:Found = {
|
||||
allfinales: false,
|
||||
|
|
@ -14,9 +13,12 @@ const defaultFoundState:Found = {
|
|||
finales: [],
|
||||
initiales: [],
|
||||
syllables: [],
|
||||
tones: []
|
||||
toneS: [],
|
||||
randomTones: []
|
||||
}
|
||||
|
||||
enum Status {params, plaing, plaied, showlist}
|
||||
|
||||
function App() {
|
||||
|
||||
const [ count, setCount ] = useState(10)
|
||||
|
|
@ -25,71 +27,135 @@ function App() {
|
|||
const [ found, setFound ] = useState(defaultFoundState)
|
||||
|
||||
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
|
||||
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,
|
||||
allInitiales: !found.allInitiales,
|
||||
initiales: found.allInitiales ? [] : initials
|
||||
initiales: toggled,
|
||||
syllables: foundSyllables,
|
||||
toneS: foundTones,
|
||||
randomTones: foundRandomTones
|
||||
})
|
||||
}
|
||||
const toggleInitialsState = (caption: String) => setFound({...found,
|
||||
initiales: toggle(found.initiales,caption)
|
||||
})
|
||||
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 toggleInitialsState = (caption: string) => {
|
||||
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 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,
|
||||
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 ) )
|
||||
&& ( found.finales.some( (f) => f == syl.finale ) ) )
|
||||
const playDictation2 = (randomTones: Tone[]) => {
|
||||
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 handleShow = () => setShow(true);
|
||||
|
||||
const foundTones = ():Tone[] => tones.filter( (t) => foundSyllables().some( (syl) => syl.tones.some( (st) => st===t.tone) ) )
|
||||
// const randomTones = ( length:number, fromTones:Tone[]):Tone[] => {
|
||||
// const ftones = foundTones();
|
||||
// return ftones
|
||||
// }
|
||||
const refreshRandomTones = () => {
|
||||
let foundRandomTones = getRandomTones( found.toneS, count )
|
||||
setFound({...found,
|
||||
randomTones: foundRandomTones
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Диктант pīnyīn</h1>
|
||||
<Accordion defaultActiveKey="0">
|
||||
<Accordion.Item eventKey="0">
|
||||
<Accordion.Header>{strings.selectInitiales}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
<ListGroup>
|
||||
<ListGroup.Item disabled={status != Status.params}>
|
||||
<h2>{strings.selectInitiales}</h2>
|
||||
<Button variant={found.allInitiales ? "primary" : "outline-primary"}
|
||||
onClick= {()=>toggleInitiales( found.allInitiales ? [] : initials )}>
|
||||
onClick= {()=>toggleAllInitiales()}>
|
||||
{found.allInitiales ? strings.unselectAll : strings.selectAll}
|
||||
</Button>
|
||||
{initials.map( (text, i) => <Button variant={isEnabled(found.initiales, text) ? "primary" : "outline-primary"}
|
||||
key={i} onClick={()=>toggleInitiales([text])}>
|
||||
key={i} onClick={()=>toggleInitialsState(text)}>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item eventKey="1">
|
||||
<Accordion.Header>{strings.selectFinales}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item disabled={status != Status.params}>
|
||||
<h2>{strings.selectFinales}</h2>
|
||||
<Button variant={found.allfinales ? "success" : "outline-success"}
|
||||
onClick= {toggleAllFinales}>
|
||||
{found.allfinales ? strings.unselectAll : strings.selectAll}
|
||||
|
|
@ -99,48 +165,45 @@ const toggleFinalsState = (caption: String) => setFound({...found,
|
|||
{fin.caption}
|
||||
</Button>
|
||||
)}
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item eventKey="2">
|
||||
<Accordion.Header>{strings.params}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item disabled={status != Status.params}>
|
||||
<h2>{strings.params}</h2>
|
||||
<Form.Label>Количество слогов {count}</Form.Label>
|
||||
<Form.Range value={count} min={5} max={50} step={5} onChange={onchangecount}/>
|
||||
<Form.Label>Пауза между слогами {pause} секунд</Form.Label>
|
||||
<Form.Range value={pause} min={1} max={10} step={1} onChange={onchangepause}/>
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<div className="card">
|
||||
Выбрано {found.initiales.length} инициалей, {found.finales.length} финалей, количество слогов {count}, пауза между слогами {pause} секунд.
|
||||
<br/>
|
||||
Найдено {foundSyllables().length} слогов, { foundSyllables().reduce(
|
||||
( p, c ) => p + c.tones.length , 0
|
||||
) } тонов
|
||||
<br/>
|
||||
<Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={handleShow} disabled={!isFound()}>
|
||||
{isFound() ? "Поехали!" : "Выберите инициали и финали" }
|
||||
</Button>
|
||||
</div>
|
||||
<Modal show={show} onHide={handleClose}>
|
||||
<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
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item>
|
||||
{ status == Status.params &&
|
||||
<>
|
||||
Выбрано {found.initiales.length} инициалей, {found.finales.length} финалей, найдено {found.syllables.length} слогов, { found.toneS.length } тонов ,
|
||||
<br/>
|
||||
<Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={()=>beginDictation()} disabled={!isFound()}>
|
||||
{isFound() ? "Начать диктант!" : "Выберите инициали и финали" }
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleClose}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
{
|
||||
status == Status.plaing &&
|
||||
<h1>Воспроизводится...{plaingNo}</h1>
|
||||
}
|
||||
{
|
||||
status == Status.plaied &&
|
||||
<Button variant="success" onClick={()=>setStatus(Status.showlist)}>Показать слоги</Button>
|
||||
}
|
||||
{
|
||||
status == Status.showlist &&
|
||||
<>
|
||||
<div>{renderRandomTones2()}</div>
|
||||
<br/>
|
||||
<Button variant="success" onClick={()=>refresh()}>В начало</Button>
|
||||
</>
|
||||
}
|
||||
</ListGroup.Item>
|
||||
</ListGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
'h', 'j', 'q', 'x', 'zh', 'ch', 'sh', 'r', 'z', 'c', 's'
|
||||
]
|
||||
|
|
@ -41,7 +41,7 @@ export const finales: Finale[] = [
|
|||
{ caption: 'üan', finale: 'van' },
|
||||
{ caption: 'üe', finale: 've' },
|
||||
{ caption: 'ü', finale: 'v' },
|
||||
{ caption: 'ün', finale: 'vn' }
|
||||
{ caption: 'ün', finale: 'vn' }
|
||||
]
|
||||
|
||||
export const syllables: Syllable[] = [
|
||||
|
|
|
|||
25
src/Utils.ts
25
src/Utils.ts
|
|
@ -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) )
|
||||
return arr.filter((el)=>el!==caption)
|
||||
else
|
||||
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
|
||||
}
|
||||
|
|
@ -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: []
|
||||
}
|
||||
|
|
@ -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} />
|
||||
}
|
||||
Loading…
Reference in New Issue