globalstate + params + btnsets

main
Rurik19 2023-10-12 17:09:49 +05:00
parent e1bf83fd58
commit 5665eb4955
9 changed files with 299 additions and 200 deletions

View File

@ -1,103 +1,29 @@
import { useState } from 'react'
import './App.css'
import Form from 'react-bootstrap/Form';
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 { initials, finales } from './data';
import { BtnColor, Tone } from './types';
import { strings } from './strings';
const defaultFoundState:Found = {
allfinales: false,
allInitiales: false,
finales: [],
initiales: [],
syllables: [],
toneS: [],
randomTones: []
}
import { useStateContext } from './store';
import { Params } from './params';
import { ActionType, ToggleType } from './reducer';
import { ButtonSet } from './buttons';
enum Status {params, plaing, plaied, showlist}
function App() {
const { state, dispatch } = useStateContext();
const [ count, setCount ] = useState(10)
const [ pause, setPause ] = useState(3)
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) )
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: toggled,
syllables: foundSyllables,
toneS: foundTones,
randomTones: foundRandomTones
})
}
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: 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 isFound = ():boolean => state.foundSyllables!.length > 0
const [plaingNo, setPlaingNo ] = useState(0)
// ---- опять по новой
const [ status, setStatus ] = useState(Status.params)
const beginDictation = (): void => {
refreshRandomTones()
console.debug(found.randomTones)
playDictation2(found.randomTones)
dispatch({ type: ActionType.refreshPlayList })
playDictation2(state.randomTones!)
}
const playDictation2 = (randomTones: Tone[]) => {
@ -114,7 +40,7 @@ const playDictation2 = (randomTones: Tone[]) => {
let pno = x+2
setPlaingNo(pno)
audios[x+1].play()
}, 1000*pause ) ;
}, 1000*state.sylPause! ) ;
}
audios[audios.length-1].onended = () => setStatus(Status.plaied)
setStatus(Status.plaing)
@ -123,60 +49,36 @@ const playDictation2 = (randomTones: Tone[]) => {
}
const renderRandomTones2 = () => {
return found.randomTones.map( (ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{' '}</span> })
return state.randomTones!.map( (ton, i) => { return <span key={i}><Badge bg="success" pill>{ton.caption}</Badge>{' '}</span> })
}
const refresh = () => {
refreshRandomTones()
dispatch({ type: ActionType.refreshPlayList })
setStatus(Status.params)
}
const refreshRandomTones = () => {
let foundRandomTones = getRandomTones( found.toneS, count )
setFound({...found,
randomTones: foundRandomTones
})
}
return (
<>
<>
<h1>Диктант pīnyīn</h1>
<ListGroup>
<ListGroup.Item disabled={status != Status.params}>
<h2>{strings.selectInitiales}</h2>
<Button variant={found.allInitiales ? "primary" : "outline-primary"}
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={()=>toggleInitialsState(text)}>
{text}
</Button>
)}
<ButtonSet source={initials} color={BtnColor.blue} toggle={ToggleType.init}/>
</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}
</Button>
{finales.map( (fin, i) => <Button variant={isEnabled(found.finales, fin.finale) ? "success" : "outline-success"}
key={i} onClick={()=>toggleFinalsState(fin.finale)}>
{fin.caption}
</Button>
)}
<ButtonSet source={finales} color={BtnColor.green} toggle={ToggleType.fin}/>
</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}/>
</ListGroup.Item>
<Params/>
</ListGroup.Item>
<ListGroup.Item>
{ status == Status.params &&
<>
Выбрано {found.initiales.length} инициалей, {found.finales.length} финалей, найдено {found.syllables.length} слогов, { found.toneS.length } тонов ,
Выбрано {state.initiales!.length} инициалей, {state.finales!.length} финалей, найдено {state.foundSyllables!.length} слогов, { state.foundTones!.length } тонов ,
<br/>
<Button variant={isFound() ? "success" : "secondary"} size="lg" onClick={()=>beginDictation()} disabled={!isFound()}>
{isFound() ? "Начать диктант!" : "Выберите инициали и финали" }
@ -201,7 +103,7 @@ const refreshRandomTones = () => {
}
</ListGroup.Item>
</ListGroup>
</>
</>
)
}

View File

@ -1,47 +1,69 @@
import { Finale, Syllable, Tone } from "./types"
import { SylPart, Syllable, Tone } from "./types"
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'
export const initials: SylPart[] = [
{ caption: '-', index: '-' },
{ caption: 'y', index: 'y' },
{ caption: 'w', index: 'w' },
{ caption: 'b', index: 'b' },
{ caption: 'p', index: 'p' },
{ caption: 'm', index: 'm' },
{ caption: 'f', index: 'f' },
{ caption: 'd', index: 'd' },
{ caption: 't', index: 't' },
{ caption: 'n', index: 'n' },
{ caption: 'l', index: 'l' },
{ caption: 'g', index: 'g' },
{ caption: 'k', index: 'k' },
{ caption: 'h', index: 'h' },
{ caption: 'j', index: 'j' },
{ caption: 'q', index: 'q' },
{ caption: 'x', index: 'x' },
{ caption: 'zh', index: 'zh' },
{ caption: 'ch', index: 'ch' },
{ caption: 'sh', index: 'sh' },
{ caption: 'r', index: 'r' },
{ caption: 'z', index: 'z' },
{ caption: 'c', index: 'c' },
{ caption: 's', index: 's' },
]
export const finales: Finale[] = [
{ caption: 'ai', finale: 'ai' },
{ caption: 'a', finale: 'a' },
{ caption: 'ang', finale: 'ang' },
{ caption: 'an', finale: 'an' },
{ caption: 'ao', finale: 'ao' },
{ caption: 'ei', finale: 'ei' },
{ caption: 'e', finale: 'e' },
{ caption: 'eng', finale: 'eng' },
{ caption: 'en', finale: 'en' },
{ caption: 'er', finale: 'er' },
{ caption: 'ia', finale: 'ia' },
{ caption: 'iang', finale: 'iang' },
{ caption: 'ian', finale: 'ian' },
{ caption: 'iao', finale: 'iao' },
{ caption: 'ie', finale: 'ie' },
{ caption: 'i', finale: 'i' },
{ caption: 'ing', finale: 'ing' },
{ caption: 'in', finale: 'in' },
{ caption: 'io', finale: 'io' },
{ caption: 'iong', finale: 'iong' },
{ caption: 'iu', finale: 'iu' },
{ caption: 'o', finale: 'o' },
{ caption: 'ong', finale: 'ong' },
{ caption: 'ou', finale: 'ou' },
{ caption: 'uai', finale: 'uai' },
{ caption: 'ua', finale: 'ua' },
{ caption: 'uang', finale: 'uang' },
{ caption: 'uan', finale: 'uan' },
{ caption: 'ui', finale: 'ui' },
{ caption: 'u', finale: 'u' },
{ caption: 'un', finale: 'un' },
{ caption: 'uo', finale: 'uo' },
{ caption: 'üan', finale: 'van' },
{ caption: 'üe', finale: 've' },
{ caption: 'ü', finale: 'v' },
{ caption: 'ün', finale: 'vn' }
export const finales: SylPart[] = [
{ caption: 'ai', index: 'ai' },
{ caption: 'a', index: 'a' },
{ caption: 'ang', index: 'ang' },
{ caption: 'an', index: 'an' },
{ caption: 'ao', index: 'ao' },
{ caption: 'ei', index: 'ei' },
{ caption: 'e', index: 'e' },
{ caption: 'eng', index: 'eng' },
{ caption: 'en', index: 'en' },
{ caption: 'er', index: 'er' },
{ caption: 'ia', index: 'ia' },
{ caption: 'iang', index: 'iang' },
{ caption: 'ian', index: 'ian' },
{ caption: 'iao', index: 'iao' },
{ caption: 'ie', index: 'ie' },
{ caption: 'i', index: 'i' },
{ caption: 'ing', index: 'ing' },
{ caption: 'in', index: 'in' },
{ caption: 'io', index: 'io' },
{ caption: 'iong', index: 'iong' },
{ caption: 'iu', index: 'iu' },
{ caption: 'o', index: 'o' },
{ caption: 'ong', index: 'ong' },
{ caption: 'ou', index: 'ou' },
{ caption: 'uai', index: 'uai' },
{ caption: 'ua', index: 'ua' },
{ caption: 'uang', index: 'uang' },
{ caption: 'uan', index: 'uan' },
{ caption: 'ui', index: 'ui' },
{ caption: 'u', index: 'u' },
{ caption: 'un', index: 'un' },
{ caption: 'uo', index: 'uo' },
{ caption: 'üan', index: 'van' },
{ caption: 'üe', index: 've' },
{ caption: 'ü', index: 'v' },
{ caption: 'ün', index: 'vn' }
]
export const syllables: Syllable[] = [

View File

@ -8,9 +8,9 @@ export type Syllable = {
tones: string[]
}
export type Finale = {
export type SylPart = {
caption: string,
finale: string
index: string
}
export type Tone = {
@ -26,4 +26,9 @@ export type Found = {
syllables: Syllable[],
toneS: Tone[],
randomTones: Tone[]
}
export enum BtnColor {
blue = "primary",
green = "success"
}

View File

@ -1,12 +1,13 @@
import { Tone } from "./types"
import { syllables } from "./data"
import { BtnColor, SylPart, Syllable } from "./types"
export const isEnabled = (arr: string[], caption: string): boolean => arr.some((btn) => btn==caption)
export const isEnabled = (arr: SylPart[], find: string): boolean => arr.some((str) => str.index==find)
export const toggle = ( arr: string[], caption: string ):string[] => {
if ( arr.some((btn) => btn==caption) )
return arr.filter((el)=>el!==caption)
export const toggle = ( arr: SylPart[], part: SylPart ):SylPart[] => {
if ( arr.some((btn) => btn.index==part.index) )
return arr.filter((el)=>el.index!==part.index)
else
return [ ...arr, caption]
return [ ...arr, part]
}
export const genrateRandomNumber = (min: number, max: number):number => {
@ -16,14 +17,22 @@ export const genrateRandomNumber = (min: number, max: number):number => {
return rnd
}
export const getRandomTones = (fromTomes:Tone[], counT:number):Tone[] => {
let _tone:Tone[] = []
if (fromTomes.length==0) {
export const getRandomArray = <T>(fromArray:T[], count:number):T[] => {
let _tone:T[] = []
if (fromArray.length==0) {
return _tone
}
for( let x=0; x<counT; x++) {
let randomN = genrateRandomNumber(0,fromTomes.length-1)
_tone = [..._tone, fromTomes[randomN]]
for( let x=0; x<count; x++) {
let randomN = genrateRandomNumber(0,fromArray.length-1)
_tone = [..._tone, fromArray[randomN]]
}
return _tone
}
}
export const GetSyllablesByInitAndFin = (initiales:SylPart[], finales: SylPart[]):Syllable[] => {
let inits = initiales.map( (i) => i.index)
let fins = finales.map( (i) =>i.index )
return syllables.filter( syl => inits.includes(syl.initiale) && fins.includes(syl.finale) )
}
export const Outlined = (color: BtnColor):string => `outline-${color}`

32
src/buttons.tsx Normal file
View File

@ -0,0 +1,32 @@
import { Button } from "react-bootstrap";
import { useStateContext } from "./store";
import { Outlined } from "./utils";
import { ActionType, ToggleType } from "./reducer";
import { strings } from "./strings";
import { BtnColor, SylPart } from "./types";
export interface IBtnSerProps {
source: SylPart[],
color: BtnColor,
toggle: ToggleType
}
export const ButtonSet = (props: IBtnSerProps) => {
const { state, dispatch } = useStateContext();
const { source, color, toggle } = props;
return <>
<Button variant={ state.allEnabled!(toggle) ? color : Outlined(color) }
onClick= {()=>dispatch({type: ActionType.toggleAll, payload: toggle})}>
{state.allInitiales ? strings.unselectAll : strings.selectAll}
</Button>
{ source.map( (part, i) => <Button
variant={ state.isEnabled!(toggle, part.index) ? color : Outlined(color) }
key={i} onClick={()=>dispatch({
type: ActionType.toggleOne,
payload: { type: toggle, part: part }
})}>
{part.caption}
</Button>
)}
</>
}

View File

@ -3,9 +3,12 @@ import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import 'bootstrap/dist/css/bootstrap.min.css';
import { StateProvider } from './store.js';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
<StateProvider>
<App />
</StateProvider>
</React.StrictMode>,
)

24
src/params.tsx Normal file
View File

@ -0,0 +1,24 @@
import { ReactElement } from "react";
import { useStateContext } from "./store";
import { Form } from "react-bootstrap";
import { ActionType } from "./reducer";
import { strings } from "./strings";
export const Params = (): ReactElement => {
const { state, dispatch } = useStateContext();
const pausedispatcher = (e: React.ChangeEvent<HTMLInputElement>) =>
dispatch({ type: ActionType.setPause, payload: Number(e.target.value)})
const countdispatcher = (e: React.ChangeEvent<HTMLInputElement>) =>
dispatch({ type: ActionType.setCount, payload: Number(e.target.value)})
return <>
<h2>{strings.params}</h2>
<Form.Label>Количество слогов {state.sylCount}</Form.Label>
<Form.Range value={state.sylCount} min={5} max={50} step={5} onChange={countdispatcher}/>
<Form.Label>Пауза между слогами {state.sylPause} секунд</Form.Label>
<Form.Range value={state.sylPause} min={1} max={10} step={1} onChange={pausedispatcher}/>
</>
}

View File

@ -1,23 +1,88 @@
import { Found } from "./types";
import { finales, initials, tones } from "./data";
import { IState } from "./store";
import { SylPart, Syllable, Tone } from "./types";
import { GetSyllablesByInitAndFin, getRandomArray, toggle } from "./utils";
export enum ActionType {
toggleInitiale, toggleFinale, toggleAllInitiales, toggleAllFinales
toggleOne, toggleAll, refreshPlayList, setPause, setCount
}
export type Action = { type: ActionType } | { type: ActionType };
export enum ToggleType { init, fin }
export type TogglePayload = { type: ToggleType, part: SylPart }
export const reducer = (state:Found, action:Action):Found => {
export type Action = { type: ActionType, payload?: any };
const ProceedAllInitials = (state: IState):{ allInitiales:boolean, initiales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} =>
{
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 {
allInitiales: !state.allInitiales,
initiales: toggled,
foundSyllables: foundSyllables,
foundTones: foundTones
}
}
const ProceedAllFinales = (state: IState):{ allfinales:boolean, finales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} =>
{
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 {
allfinales: !state.allfinales,
finales: toggled,
foundSyllables: foundSyllables,
foundTones: foundTones
}
}
const ProceedInitiale = (state: IState, index: SylPart):{ initiales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} =>
{
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 {
initiales: toggled,
foundSyllables: foundSyllables,
foundTones: foundTones
}
}
const ProceedFinale = (state: IState, index: SylPart):{ finales: SylPart[], foundSyllables:Syllable[], foundTones: Tone[]} =>
{
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 {
finales: toggled,
foundSyllables: foundSyllables,
foundTones: foundTones
}
}
export const reducer = (state:IState, action:Action):IState => {
switch (action.type) {
case ActionType.setPause: return { ...state, sylPause: action.payload as number }
case ActionType.setCount: return { ...state, sylCount: action.payload as number }
case ActionType.refreshPlayList: return { ...state, randomTones: getRandomArray( state.foundTones, state.sylCount! ) }
case ActionType.toggleAll: {
if (action.payload as ToggleType === ToggleType.init) return { ...state, ...ProceedAllInitials(state) }
if (action.payload as ToggleType === ToggleType.fin) return { ...state, ...ProceedAllFinales(state) }
return state
}
case ActionType.toggleOne: {
if ( (action.payload as TogglePayload).type === ToggleType.init)
return { ...state, ...ProceedInitiale(state, (action.payload as TogglePayload).part ) }
if ( (action.payload as TogglePayload).type === ToggleType.fin)
return { ...state, ...ProceedFinale(state, (action.payload as TogglePayload).part ) }
return state
}
default: return state
}
}
export const defaultState:Found = {
allfinales: false,
allInitiales: false,
finales: [],
initiales: [],
syllables: [],
toneS: [],
randomTones: []
}
}

View File

@ -1,17 +1,54 @@
import { Context, createContext, Dispatch, ReactElement, useContext, useReducer } from "react";
import { reducer, defaultState, Action } from "./reducer"
import { Found } from "./types";
import { reducer, Action, ToggleType } from "./reducer"
import { Syllable, SylPart, Tone } from "./types";
import { isEnabled } from "./utils";
export interface IStore {
state: Found
dispatch?: Dispatch<Action>
export interface IState {
sylCount: number,
sylPause: number,
initiales: SylPart[],
finales: SylPart[],
allInitiales: boolean,
allfinales: boolean,
foundSyllables: Syllable[],
foundTones: Tone[],
randomTones: Tone[],
allEnabled: (type: ToggleType) => false,
isEnabled: (type: ToggleType, index: string) => false
}
export const AppContext:Context<IStore> = createContext<IStore>({ state: defaultState, dispatch: () => null })
export interface IStore {
state: Partial<IState>
dispatch: Dispatch<Action>
}
export const defaultState:Object = {
sylCount: 10,
sylPause: 3,
allfinales: false,
allInitiales: false,
finales: [] as SylPart[],
initiales: [] as SylPart[],
foundSyllables: [],
foundTones: [],
randomTones: [],
allEnabled: function(type: ToggleType) {
if ( type === ToggleType.init ) return (this as IState).allInitiales
if ( type === ToggleType.fin ) return (this as IState).allfinales
return false
},
isEnabled: function(type: ToggleType, index: string) {
if ( type === ToggleType.init ) return isEnabled((this as IState).initiales!, index)
if ( type === ToggleType.fin ) return isEnabled((this as IState).finales!, index)
return false
}
}
export const AppContext:Context<IStore> = createContext<IStore>({ state: defaultState as IState, dispatch: () => null })
export const useStateContext = () => useContext(AppContext);
export const StateProvider = ({ children }: { children: ReactElement }) => {
const [state, dispatch] = useReducer(reducer, defaultState);
const [state, dispatch] = useReducer(reducer, defaultState as IState);
return <AppContext.Provider value={{ state, dispatch }} children={children} />
}