Compare commits
No commits in common. "50e99a08bf5be5be828af3e99a566c25c56b718b" and "dff64780f5b5a8b0b1253a13e792a8ac15c6bb4f" have entirely different histories.
50e99a08bf
...
dff64780f5
|
|
@ -1,8 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "pinyindictation",
|
"name": "vite-1",
|
||||||
"author": {"email": "rurik19@yandex.ru", "name": "Юрий Евдокимов"},
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
|
||||||
17
src/App.css
17
src/App.css
|
|
@ -42,23 +42,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) }
|
|
||||||
|
|
||||||
.cpr { color: gray}
|
|
||||||
115
src/App.tsx
115
src/App.tsx
|
|
@ -1,86 +1,66 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import { Button, ListGroup } from 'react-bootstrap';
|
import { Badge, Button, ListGroup } from 'react-bootstrap';
|
||||||
import { initials, finales } from './pinyin';
|
import { initials, finales } from './data';
|
||||||
import { BtnColor, Status, Tone } from './types';
|
import { BtnColor, 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 { ActionType, ToggleType } from './reducer';
|
||||||
import { ButtonSet } from './buttons';
|
import { ButtonSet } from './buttons';
|
||||||
import { RenderTones } from './rendertones';
|
|
||||||
import { getAudio } from './audio';
|
enum Status {params, plaing, plaied, showlist}
|
||||||
import { Copyright } from './copyright';
|
|
||||||
import { SelectTones } from './selecttones';
|
|
||||||
import { FormatString } from './utils';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
const { state, dispatch } = useStateContext();
|
const { state, dispatch } = useStateContext();
|
||||||
|
|
||||||
const [ plaingNo, setPlaingNo ] = useState(0)
|
//---------------- to found state
|
||||||
const [ status, setStatus ] = useState(Status.params)
|
const isFound = ():boolean => state.foundSyllables!.length > 0
|
||||||
const [ playlist, setPlaylist ] = useState( [] as HTMLAudioElement[] )
|
const [plaingNo, setPlaingNo ] = useState(0)
|
||||||
|
|
||||||
useEffect( () => {
|
const [ status, setStatus ] = useState(Status.params)
|
||||||
if (status===Status.params) {
|
|
||||||
console.log('effect for params')
|
|
||||||
dispatch({ type: ActionType.refresh })
|
|
||||||
}
|
|
||||||
if (status===Status.prepare) {
|
|
||||||
console.log('effect for preparing')
|
|
||||||
preparePlayList(state.randomTones!)
|
|
||||||
}
|
|
||||||
if (status===Status.playing) {
|
|
||||||
console.log('effect for playing')
|
|
||||||
playPlayList()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [status])
|
|
||||||
|
|
||||||
useEffect( () => {
|
const beginDictation = (): void => {
|
||||||
console.log(`effect for playList ${playlist.length}`)
|
dispatch({ type: ActionType.refreshPlayList })
|
||||||
if (playlist.length > 0) setStatus(Status.playing)
|
playDictation2(state.randomTones!)
|
||||||
} , [playlist[0]] )
|
}
|
||||||
|
|
||||||
const preparePlayList = (tones: Tone[]) => {
|
const playDictation2 = (randomTones: Tone[]) => {
|
||||||
console.log('tones for playing')
|
if ( randomTones.length == 0 ) return
|
||||||
tones!.forEach(element => console.log(element))
|
let audios:HTMLAudioElement[] = []
|
||||||
if ( tones!.length == 0 ) return
|
randomTones.forEach(element => {
|
||||||
const audios = tones!.map(element => getAudio(element.tone) )
|
audios = [...audios, new Audio(`/src/assets/audio/${element.tone}.mp3`) ]
|
||||||
|
});
|
||||||
console.debug(audios)
|
console.debug(audios)
|
||||||
if ( audios.length == 0 ) return
|
if ( audios.length == 0 ) return
|
||||||
for(let x=0; x<audios.length-1;x++)
|
for(let x=0; x<audios.length-1;x++)
|
||||||
{
|
{
|
||||||
audios[x].onended = () => setTimeout( () => {
|
audios[x].onended = () => setTimeout( () => {
|
||||||
const pno = x+2
|
let pno = x+2
|
||||||
setPlaingNo(pno)
|
setPlaingNo(pno)
|
||||||
audios[x+1].play()
|
audios[x+1].play()
|
||||||
}, 1000*state.sylPause! ) ;
|
}, 1000*state.sylPause! ) ;
|
||||||
}
|
}
|
||||||
audios[audios.length-1].onended = () => setStatus(Status.plaied)
|
audios[audios.length-1].onended = () => setStatus(Status.plaied)
|
||||||
setPlaylist(audios)
|
setStatus(Status.plaing)
|
||||||
}
|
|
||||||
|
|
||||||
const playPlayList = () => {
|
|
||||||
if (playlist.length===0) return;
|
|
||||||
setStatus(Status.playing)
|
|
||||||
setPlaingNo(1)
|
setPlaingNo(1)
|
||||||
playlist[0].play()
|
audios[0].play()
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = () => FormatString(strings.infoFormat,
|
const renderRandomTones2 = () => {
|
||||||
state.initiales!.length.toString(),
|
return state.randomTones!.map( (ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{' '}</span> })
|
||||||
state.finales!.length.toString(),
|
}
|
||||||
state.toneChecks!.length.toString(),
|
|
||||||
state.foundSyllables!.length.toString(),
|
|
||||||
state.foundTones!.length.toString())
|
|
||||||
|
|
||||||
return (
|
const refresh = () => {
|
||||||
|
dispatch({ type: ActionType.refreshPlayList })
|
||||||
|
setStatus(Status.params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>{strings.mainHeader}</h1>
|
<h1>Диктант pīnyīn</h1>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
|
|
||||||
<ListGroup.Item disabled={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}/>
|
||||||
|
|
@ -91,54 +71,41 @@ return (
|
||||||
<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={status != Status.params}>
|
|
||||||
<h2>{strings.selectTones}</h2>
|
|
||||||
<SelectTones/>
|
|
||||||
</ListGroup.Item>
|
|
||||||
|
|
||||||
<ListGroup.Item disabled={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>
|
||||||
{ status == Status.params &&
|
{ status == Status.params &&
|
||||||
<>
|
<>
|
||||||
{info()}
|
Выбрано {state.initiales!.length} инициалей, {state.finales!.length} финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов ,
|
||||||
<br/>
|
<br/>
|
||||||
<Button
|
<Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={()=>beginDictation()} disabled={!isFound()}>
|
||||||
variant={state.isFound!() ? "success" : "secondary"}
|
{isFound() ? "Начать диктант!" : "Выберите инициали и финали" }
|
||||||
size="lg"
|
|
||||||
onClick={()=>setStatus(Status.prepare)}
|
|
||||||
disabled={!state.isFound!()}>
|
|
||||||
{state.isFound!() ? strings.beginDictation : strings.selectInitFinAndTone }
|
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
status == Status.playing &&
|
status == Status.plaing &&
|
||||||
<h1>Воспроизводится...{plaingNo}</h1>
|
<h1>Воспроизводится...{plaingNo}</h1>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
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>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
status == Status.showlist &&
|
status == Status.showlist &&
|
||||||
<>
|
<>
|
||||||
<RenderTones tones={state.randomTones!}/>
|
<div>{renderRandomTones2()}</div>
|
||||||
<br/>
|
<br/>
|
||||||
<Button variant="success" onClick={()=> setStatus(Status.params)}>{strings.toBegin}</Button>
|
<Button variant="success" onClick={()=>refresh()}>В начало</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</ListGroup.Item>
|
</ListGroup.Item>
|
||||||
<ListGroup.Item><Copyright/></ListGroup.Item>
|
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,25 +1,7 @@
|
||||||
export const strings = {
|
export const strings = {
|
||||||
mainHeader: "Диктант pīnyīn",
|
|
||||||
selectInitiales: 'Выберите инициали',
|
selectInitiales: 'Выберите инициали',
|
||||||
selectFinales: 'Выберите финали',
|
selectFinales: 'Выберите финали',
|
||||||
selectAll: 'Выбрать все',
|
selectAll: 'Выбрать все',
|
||||||
unselectAll: 'Снять все',
|
unselectAll: 'Снять все',
|
||||||
params: 'Параметры',
|
params: 'Параметры'
|
||||||
sylCount: 'Количество слогов: ',
|
|
||||||
beginDictation: "Начать диктант!",
|
|
||||||
selectInitFinAndTone: "Выберите инициали, финали и тоны",
|
|
||||||
showSyllables: "Показать слоги",
|
|
||||||
playAgain: "Повторить",
|
|
||||||
toBegin: "В начало",
|
|
||||||
version: "Версия",
|
|
||||||
cpr: "©",
|
|
||||||
selectTones: "Выберите тоны",
|
|
||||||
first: "Первый",
|
|
||||||
second: "Второй",
|
|
||||||
third: "Третий",
|
|
||||||
fourth: "Четвёртый",
|
|
||||||
zeroth: "Нулевой",
|
|
||||||
infoFormat: "Выбрано {0} инициалей, {1} финалей, {2} тонов, найдено {3} слогов, {4} тонов",
|
|
||||||
pauseFormat: "Пауза между слогами, в секундах: {0}",
|
|
||||||
mail: "емайл:"
|
|
||||||
}
|
}
|
||||||
|
|
@ -15,8 +15,7 @@ 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 = {
|
||||||
|
|
@ -34,4 +33,4 @@ export enum BtnColor {
|
||||||
green = "success"
|
green = "success"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Status {params, prepare, playing, plaied, showlist}
|
export enum Status {params, plaing, plaied, showlist}
|
||||||
42
src/Utils.ts
42
src/Utils.ts
|
|
@ -1,42 +1,19 @@
|
||||||
import { syllables } from "./pinyin"
|
import { syllables } from "./data"
|
||||||
import { BtnColor, SylPart, Syllable } from "./types"
|
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 toggleSylPart = ( arr: SylPart[], part: SylPart ):SylPart[] => {
|
export const toggle = ( 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)
|
||||||
const rnd = Math.floor(Math.random() * (max - min + 1)) + min
|
let rnd = Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
return rnd
|
return rnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,23 +23,16 @@ export const getRandomArray = <T>(fromArray:T[], count:number):T[] => {
|
||||||
return _tone
|
return _tone
|
||||||
}
|
}
|
||||||
for( let x=0; x<count; x++) {
|
for( let x=0; x<count; x++) {
|
||||||
const randomN = genrateRandomNumber(0,fromArray.length-1)
|
let randomN = genrateRandomNumber(0,fromArray.length-1)
|
||||||
_tone = [..._tone, fromArray[randomN]]
|
_tone = [..._tone, fromArray[randomN]]
|
||||||
}
|
}
|
||||||
return _tone
|
return _tone
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetSyllablesByInitAndFin = (initiales:SylPart[], finales: SylPart[]):Syllable[] => {
|
export const GetSyllablesByInitAndFin = (initiales:SylPart[], finales: SylPart[]):Syllable[] => {
|
||||||
const inits = initiales.map( (i) => i.index)
|
let inits = initiales.map( (i) => i.index)
|
||||||
const fins = finales.map( (i) =>i.index )
|
let fins = finales.map( (i) =>i.index )
|
||||||
return syllables.filter( syl => inits.includes(syl.initiale) && fins.includes(syl.finale) )
|
return syllables.filter( syl => inits.includes(syl.initiale) && fins.includes(syl.finale) )
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue