增加了选择自己的音效 (#447)

Co-authored-by: 邓亮 <787615673@qq.com>
Co-authored-by: KaiyiWing <Zhang.kaiyi42@gmail.com>
This commit is contained in:
hemoo
2023-05-13 10:58:46 +08:00
committed by GitHub
parent 21549e12ee
commit 7b05c1323f
19 changed files with 124 additions and 9 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,13 +1,15 @@
import { SOUND_URL_PREFIX } from '@/resources/soundResource'
import { SOUND_URL_PREFIX, KEY_SOUND_URL_PREFIX, keySoundResources } from '@/resources/soundResource'
import { keySoundsConfigAtom, hintSoundsConfigAtom } from '@/store'
import noop from '@/utils/noop'
import { useAtomValue } from 'jotai'
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from 'react'
import useSound from 'use-sound'
export type PlayFunction = ReturnType<typeof useSound>[0]
export default function useKeySound(): [PlayFunction, PlayFunction, PlayFunction] {
const { isOpen: isKeyOpen, isOpenClickSound, volume: keyVolume, resource: keyResource } = useAtomValue(keySoundsConfigAtom)
const setKeySoundsConfig = useSetAtom(keySoundsConfigAtom)
const {
isOpen: isHintOpen,
isOpenWrongSound,
@@ -16,8 +18,18 @@ export default function useKeySound(): [PlayFunction, PlayFunction, PlayFunction
wrongResource,
correctResource,
} = useAtomValue(hintSoundsConfigAtom)
const [keySoundUrl, setKeySoundUrl] = useState(`${KEY_SOUND_URL_PREFIX}${keyResource.filename}`)
const [playClickSound] = useSound(`${SOUND_URL_PREFIX}${keyResource.filename}`, {
useEffect(() => {
if (!keySoundResources.some((item) => item.filename === keyResource.filename && item.key === keyResource.key)) {
const defaultKeySoundResource = keySoundResources.find((item) => item.key === 'Default') || keySoundResources[0]
setKeySoundUrl(`${KEY_SOUND_URL_PREFIX}${defaultKeySoundResource.filename}`)
setKeySoundsConfig((prev) => ({ ...prev, resource: defaultKeySoundResource }))
}
}, [keyResource, setKeySoundsConfig])
const [playClickSound] = useSound(keySoundUrl, {
volume: keyVolume,
interrupt: true,
})
@@ -30,8 +42,6 @@ export default function useKeySound(): [PlayFunction, PlayFunction, PlayFunction
interrupt: true,
})
// todo: add volume control
return [
isKeyOpen && isOpenClickSound ? playClickSound : noop,
isHintOpen && isOpenWrongSound ? playWrongSound : noop,

View File

@@ -182,7 +182,7 @@ const PronunciationSwitcher = () => {
<>
<span>{item.name}</span>
{selected ? (
<span className="listbox-options-icon ">
<span className="listbox-options-icon">
<IconCheck className="focus:outline-none" />
</span>
) : null}

View File

@@ -1,10 +1,18 @@
import styles from './index.module.css'
import { keySoundResources } from '@/resources/soundResource'
import { hintSoundsConfigAtom, keySoundsConfigAtom, pronunciationConfigAtom } from '@/store'
import { SoundResource } from '@/typings'
import { toFixedNumber } from '@/utils'
import { Switch } from '@headlessui/react'
import { playKeySoundResource } from '@/utils/sounds/keySounds'
import { Switch, Transition } from '@headlessui/react'
import { Listbox } from '@headlessui/react'
import * as Slider from '@radix-ui/react-slider'
import { useAtom } from 'jotai'
import { useCallback } from 'react'
import { Fragment } from 'react'
import IconCheck from '~icons/tabler/check'
import IconChevronDown from '~icons/tabler/chevron-down'
import IconEar from '~icons/tabler/ear'
export default function SoundSetting() {
const [pronunciationConfig, setPronunciationConfig] = useAtom(pronunciationConfigAtom)
@@ -29,7 +37,6 @@ export default function SoundSetting() {
},
[setPronunciationConfig],
)
const onChangePronunciationRate = useCallback(
(value: [number]) => {
setPronunciationConfig((prev) => ({
@@ -59,6 +66,23 @@ export default function SoundSetting() {
[setKeySoundsConfig],
)
const onChangeKeySoundsResource = useCallback(
(key: string) => {
const soundResource = keySoundResources.find((item: SoundResource) => item.key === key) as SoundResource
if (!soundResource) return
setKeySoundsConfig((prev) => ({
...prev,
resource: soundResource,
}))
},
[setKeySoundsConfig],
)
const onPlayKeySound = useCallback((soundResource: SoundResource) => {
playKeySoundResource(soundResource)
}, [])
const onToggleHintSounds = useCallback(
(checked: boolean) => {
setHintSoundsConfig((prev) => ({
@@ -162,6 +186,46 @@ export default function SoundSetting() {
<span className="ml-4 w-10 text-xs font-normal text-gray-600">{`${Math.floor(keySoundsConfig.volume * 100)}%`}</span>
</div>
</div>
<div className={`${styles.block}`}>
<span className={styles.blockLabel}></span>
<Listbox value={keySoundsConfig.resource.key} onChange={onChangeKeySoundsResource}>
<div className="relative">
<Listbox.Button className="listbox-button w-60">
<span>{keySoundsConfig.resource.name}</span>
<span>
<IconChevronDown className="focus:outline-none" />
</span>
</Listbox.Button>
<Transition as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0">
<Listbox.Options className="listbox-options z-10">
{keySoundResources.map((keySoundResource) => (
<Listbox.Option key={keySoundResource.key} value={keySoundResource.key}>
{({ selected }) => (
<>
<div className="group flex cursor-pointer items-center justify-between">
<span>{keySoundResource.name}</span>
{selected ? (
<span className="listbox-options-icon">
<IconCheck className="focus:outline-none" />
</span>
) : null}
<IconEar
onClick={(e) => {
e.stopPropagation()
onPlayKeySound(keySoundResource)
}}
className="mr-2 hidden cursor-pointer text-neutral-500 hover:text-indigo-400 group-hover:block dark:text-neutral-300"
/>
</div>
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
</div>
</div>
<div className={styles.section}>

View File

@@ -1,9 +1,37 @@
import { SoundResource, LanguagePronunciationMap } from '@/typings'
export const SOUND_URL_PREFIX = REACT_APP_DEPLOY_ENV === 'pages' ? '/qwerty-learner/sounds/' : './sounds/'
export const KEY_SOUND_URL_PREFIX = SOUND_URL_PREFIX + 'key-sound/'
// will add more sound resource and add config ui in the future
export const keySoundResources: SoundResource[] = [{ key: '1', name: '声音1', filename: 'click.wav' }]
const videoList = import.meta.glob(['../../public/sounds/key-sound/*.(wav|mp3)'], {
eager: false,
})
/**
* the Mechanical keyboard sound from https://github.com/tplai/kbsim
*/
export const keySoundResources: SoundResource[] = Object.keys(videoList)
.map((k) => {
const name = k.replace(/(.*\/)*([^.]+).*/gi, '$2')
const suffix = k.substring(k.lastIndexOf('.'))
return {
key: name,
name: `${name}`,
filename: `${name}${suffix}`,
}
})
.sort((a, b) => {
// default key should be the first one
if (a.key === 'Default') {
return -1
}
if (b.key === 'Default') {
return 1
}
return a.key.localeCompare(b.key)
})
export const wrongSoundResources: SoundResource[] = [{ key: '1', name: '声音1', filename: 'beep.wav' }]

View File

@@ -0,0 +1,13 @@
import { KEY_SOUND_URL_PREFIX } from '@/resources/soundResource'
import { SoundResource } from '@/typings'
import { Howl, Howler } from 'howler'
export function playKeySoundResource(soundResource: SoundResource) {
const path = KEY_SOUND_URL_PREFIX + soundResource.filename
const sound = new Howl({
src: path,
format: ['wav'],
})
Howler.volume(1)
sound.play()
}