mirror of
https://github.com/RealKai42/qwerty-learner.git
synced 2026-04-05 14:29:04 +08:00
@@ -18,17 +18,23 @@ function generateWordSoundSrc(word: string, pronunciation: Exclude<Pronunciation
|
||||
}
|
||||
|
||||
export default function usePronunciationSound(word: string) {
|
||||
const { pronunciation } = useAppState()
|
||||
const { pronunciation, soundLoop } = useAppState()
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
|
||||
const [play, { stop, sound }] = useSound(generateWordSoundSrc(word, pronunciation as Exclude<PronunciationType, false>), {
|
||||
html5: true,
|
||||
format: ['mp3'],
|
||||
loop: soundLoop,
|
||||
} as HookOptions)
|
||||
|
||||
useEffect(() => {
|
||||
if (!sound) return
|
||||
sound.loop(soundLoop)
|
||||
return () => {}
|
||||
}, [soundLoop])
|
||||
|
||||
useEffect(() => {
|
||||
if (!sound) return
|
||||
const unListens: Array<() => void> = []
|
||||
|
||||
unListens.push(addHowlListener(sound, 'play', () => setIsPlaying(true)))
|
||||
@@ -37,6 +43,7 @@ export default function usePronunciationSound(word: string) {
|
||||
unListens.push(addHowlListener(sound, 'playerror', () => setIsPlaying(false)))
|
||||
|
||||
return () => {
|
||||
setIsPlaying(false)
|
||||
unListens.forEach((unListen) => unListen())
|
||||
;(sound as Howl).unload()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
faExclamation,
|
||||
faRandom,
|
||||
faRepeat,
|
||||
faRotate,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
@@ -41,4 +42,5 @@ library.add(
|
||||
faExclamation,
|
||||
faRandom,
|
||||
faRepeat,
|
||||
faRotate,
|
||||
)
|
||||
|
||||
@@ -83,6 +83,17 @@ const Switcher: React.FC<SwitcherPropsType> = ({ state, dispatch }) => {
|
||||
<FontAwesomeIcon icon="repeat" fixedWidth />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="开关循环播放单词发音">
|
||||
<button
|
||||
className={`${state.soundLoop ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
|
||||
onClick={(e) => {
|
||||
dispatch('soundLoop')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={'rotate'} fixedWidth />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="开关键盘声音(Ctrl + M)">
|
||||
<button
|
||||
className={`${state.sound ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { useSetSoundState, useDarkMode, useRandomState, useSetLoopState, usePhoneticState } from 'store/AppState'
|
||||
import { useSetSoundState, useDarkMode, useRandomState, useSetLoopState, usePhoneticState, useSetSoundLoopState } from 'store/AppState'
|
||||
|
||||
export type SwitcherStateType = {
|
||||
phonetic: boolean
|
||||
@@ -8,6 +8,7 @@ export type SwitcherStateType = {
|
||||
random: boolean
|
||||
darkMode: boolean
|
||||
loop: boolean
|
||||
soundLoop: boolean
|
||||
}
|
||||
|
||||
export type SwitcherDispatchType = (type: string, newStatus?: boolean) => void
|
||||
@@ -24,6 +25,7 @@ const useSwitcherState = (initialState: {
|
||||
const [sound, setSound] = useSetSoundState()
|
||||
const [random, setRandom] = useRandomState()
|
||||
const [darkMode, setDarkMode] = useDarkMode()
|
||||
const [soundLoop, setSoundLoop] = useSetSoundLoopState()
|
||||
|
||||
const dispatch: SwitcherDispatchType = (type, newStatus) => {
|
||||
switch (type) {
|
||||
@@ -44,9 +46,13 @@ const useSwitcherState = (initialState: {
|
||||
break
|
||||
case 'loop':
|
||||
setLoop(newStatus ?? !loop)
|
||||
break
|
||||
case 'soundLoop':
|
||||
setSoundLoop(newStatus ?? !soundLoop)
|
||||
break
|
||||
}
|
||||
}
|
||||
return [{ phonetic, wordVisible, sound, random, darkMode, loop }, dispatch]
|
||||
return [{ phonetic, wordVisible, sound, random, darkMode, loop, soundLoop }, dispatch]
|
||||
}
|
||||
|
||||
export default useSwitcherState
|
||||
|
||||
@@ -44,6 +44,10 @@ export type AppState = {
|
||||
* Whether dark mode is enabled
|
||||
*/
|
||||
darkMode: boolean
|
||||
/**
|
||||
* loop play sound until word spells right
|
||||
*/
|
||||
soundLoop: boolean
|
||||
}
|
||||
|
||||
export type AppStateData = {
|
||||
@@ -79,6 +83,12 @@ export function useSetLoopState(): [status: boolean, setLoop: (state: boolean) =
|
||||
return [state.loop, setLoop]
|
||||
}
|
||||
|
||||
export function useSetSoundLoopState(): [status: boolean, setLoop: (state: boolean) => void] {
|
||||
const { state, dispatch } = useContext(AppStateContext)
|
||||
const setSoundLoop = useCallback((soundLoop: boolean) => dispatch({ ...state, soundLoop }), [state, dispatch])
|
||||
return [state.soundLoop, setSoundLoop]
|
||||
}
|
||||
|
||||
export function usePhoneticState(): [status: boolean, setPhonetic: (state: boolean) => void] {
|
||||
const { state, dispatch } = useContext(AppStateContext)
|
||||
const setPhonetic = useCallback((phonetic: boolean) => dispatch({ ...state, phonetic }), [state, dispatch])
|
||||
@@ -151,6 +161,7 @@ const defaultState: AppState = {
|
||||
loop: false,
|
||||
phonetic: true,
|
||||
darkMode: window.matchMedia('(prefers-color-scheme: dark)').matches,
|
||||
soundLoop: false,
|
||||
}
|
||||
|
||||
export const AppStateProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
|
||||
|
||||
Reference in New Issue
Block a user