tones, begin of state management
parent
5dd170683f
commit
6a3c40b36c
221
src/App.tsx
221
src/App.tsx
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
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) )
|
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
|
||||||
|
}
|
||||||
|
|
@ -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