Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

尝试增加单词发音功能 #50

Merged
merged 6 commits into from
Feb 8, 2021
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/components/AppSettings.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import React, { useCallback, useContext, useState } from 'react'
*/
export type AppSettings = {
sound: boolean
pronunciation: boolean
}

export type AppSettingsContextData = {
@@ -25,8 +26,15 @@ export function useSetSoundState(): [status: boolean, setSound: (state: boolean)
return [settings.sound, setSound]
}

export function useSetPronunciationState(): [status: boolean, setpronunciation: (state: boolean) => void] {
const { settings, dispatch } = useContext(AppSettingsContext)
const setpronunciation = useCallback((state: boolean) => dispatch({ ...settings, pronunciation: state }), [settings, dispatch])
return [settings.pronunciation, setpronunciation]
}

const defaultSettings: AppSettings = {
sound: true,
pronunciation: true,
}

export const AppSettingsProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
10 changes: 10 additions & 0 deletions src/components/Word/index.tsx
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import Letter, { LetterState } from './Letter'
import { isLegal, isChineseSymbol } from '../../utils/utils'
import useSounds from 'hooks/useSounds'
import style from './index.module.css'
import usePronunciationSound from 'hooks/usePronouncation'

const Word: React.FC<WordProps> = ({ word = 'defaultWord', onFinish, isStart, wordVisible = true }) => {
word = word.replace(new RegExp(' ', 'g'), '_')
@@ -12,6 +13,7 @@ const Word: React.FC<WordProps> = ({ word = 'defaultWord', onFinish, isStart, wo
const [isFinish, setIsFinish] = useState(false)
const [hasWrong, setHasWrong] = useState(false)
const [playKeySound, playBeepSound, playHintSound] = useSounds()
const playPronounce = usePronunciationSound(word)

const onKeydown = useCallback(
(e) => {
@@ -59,6 +61,14 @@ const Word: React.FC<WordProps> = ({ word = 'defaultWord', onFinish, isStart, wo
}
}, [hasWrong, playBeepSound])

useLayoutEffect(() => {
if (isStart && inputWord.length === 0) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不应该使用 useLayoutEffect,useLayoutEffect是与dom更新同步,而useEffect是在dom更新后进行。盲目的使用useLayoutEffect可能会引发其他问题。可以参考链接:https://zh-hans.reactjs.org/docs/hooks-reference.html#uselayouteffect

以及,对 useEffect类函数的依赖可能理解不对,这里 hooks 内部没有使用 hasWrong,可以在依赖项中移除。

playPronounce()
}
// SAFETY: Don't depend on `playPronounce`! It will cost audio play again and again.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isStart, hasWrong, word])

useLayoutEffect(() => {
let hasWrong = false,
wordLength = word.length,
21 changes: 21 additions & 0 deletions src/hooks/usePronouncation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useAppSettings } from 'components/AppSettings'
import { noop } from 'lodash'
import { useEffect, useState } from 'react'

declare type PronounceFunction = () => void

const pronunciationApi = 'http://dict.youdao.com/dictvoice?audio='

export default function usePronunciationSound(word: string): PronounceFunction {
const [audio, setAudio] = useState<HTMLAudioElement | null>(null)
const { pronunciation } = useAppSettings()
const pronounceFunction = () => {
audio?.pause()
setAudio(new Audio(pronunciationApi + word))
}

useEffect(() => {
audio?.play()
}, [audio])
return pronunciation ? pronounceFunction : noop
}
4 changes: 4 additions & 0 deletions src/icon.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,8 @@ import {
faEyeSlash,
faAssistiveListeningSystems,
faCoffee,
faMicrophone,
faMicrophoneSlash,
} from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'

@@ -23,4 +25,6 @@ library.add(
faEyeSlash,
faAssistiveListeningSystems,
faCoffee,
faMicrophone,
faMicrophoneSlash,
)
22 changes: 22 additions & 0 deletions src/pages/Typing/Switcher/index.tsx
Original file line number Diff line number Diff line change
@@ -33,6 +33,14 @@ const Switcher: React.FC<SwitcherPropsType> = ({ state, dispatch }) => {
},
[dispatch],
)
useHotkeys(
'ctrl+n',
(e) => {
e.preventDefault()
dispatch('pronunciation')
},
[dispatch],
)

return (
<div className="flex items-center justify-center space-x-3">
@@ -50,6 +58,20 @@ const Switcher: React.FC<SwitcherPropsType> = ({ state, dispatch }) => {
<span className="py-1 px-3 text-gray-500 text-xs">开关声音(Ctrl + M)</span>
</div>
</div>
<div className="group relative">
<button
className={`${state.pronunciation ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
onClick={(e) => {
dispatch('pronunciation')
e.currentTarget.blur()
}}
>
<FontAwesomeIcon icon={state.pronunciation ? 'microphone' : 'microphone-slash'} fixedWidth />
</button>
<div className="invisible group-hover:visible absolute top-full left-1/2 w-40 -ml-20 pt-2 flex items-center justify-center">
<span className="py-1 px-3 text-gray-500 text-xs">开关发音(Ctrl + N)</span>
</div>
</div>
<div className="group relative">
<button
className={`${state.wordVisible ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
9 changes: 7 additions & 2 deletions src/pages/Typing/hooks/useSwitcherState.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState } from 'react'
import { useSetSoundState } from 'components/AppSettings'
import { useSetPronunciationState, useSetSoundState } from 'components/AppSettings'

export type SwitcherStateType = {
phonetic: boolean
wordVisible: boolean
sound: boolean
pronunciation: boolean
}

export type SwitcherDispatchType = (type: string, newStatus?: boolean) => void
@@ -13,6 +14,7 @@ const useSwitcherState = (initialState: { phonetic: boolean; wordVisible: boolea
const [phonetic, setPhonetic] = useState(initialState.phonetic)
const [wordVisible, setWordVisible] = useState(initialState.wordVisible)
const [sound, setSound] = useSetSoundState()
const [pronunciation, setPronunciation] = useSetPronunciationState()

const dispatch: SwitcherDispatchType = (type, newStatus) => {
switch (type) {
@@ -24,10 +26,13 @@ const useSwitcherState = (initialState: { phonetic: boolean; wordVisible: boolea
break
case 'sound':
newStatus === undefined ? setSound(!sound) : setSound(newStatus)
break
case 'pronunciation':
newStatus === undefined ? setPronunciation(!pronunciation) : setPronunciation(newStatus)
}
}

return [{ phonetic, wordVisible, sound }, dispatch]
return [{ phonetic, wordVisible, sound, pronunciation }, dispatch]
}

export default useSwitcherState
5 changes: 5 additions & 0 deletions src/pages/Typing/index.tsx
Original file line number Diff line number Diff line change
@@ -162,6 +162,10 @@ const App: React.FC = () => {
(dictName: string) => {
setOrder(0)
setIsLoading(true)
// Need to stop the game, in order to prevent pronounce wrong word.
// Besides, it makes sense because users are about to stop when they change dict/chapter.
// Otherwise, introduce a new parameter to allow pronunciation begin.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种方案可能确实很是合理的。
Need more comments @chengluyu

setIsStart(false)
wordListDispatch('setDictName', dictName, () => {
setIsLoading(false)
})
@@ -172,6 +176,7 @@ const App: React.FC = () => {
const changeChapter = useCallback(
(chapter: number) => {
setOrder(0)
setIsStart(false) // Same story as above.
wordListDispatch('setChapter', chapter)
},
[wordListDispatch],