mirror of
https://github.com/RealKai42/qwerty-learner.git
synced 2026-04-05 14:29:04 +08:00
增加了选择自己的音效 (#447)
Co-authored-by: 邓亮 <787615673@qq.com> Co-authored-by: KaiyiWing <Zhang.kaiyi42@gmail.com>
This commit is contained in:
BIN
public/sounds/key-sound/Alpacas.mp3
Normal file
BIN
public/sounds/key-sound/Alpacas.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Buckling Spring.mp3
Normal file
BIN
public/sounds/key-sound/Buckling Spring.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Cherry MX Blacks.mp3
Normal file
BIN
public/sounds/key-sound/Cherry MX Blacks.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Cherry MX Blues.mp3
Normal file
BIN
public/sounds/key-sound/Cherry MX Blues.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Cherry MX Browns.mp3
Normal file
BIN
public/sounds/key-sound/Cherry MX Browns.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Default.wav
Normal file
BIN
public/sounds/key-sound/Default.wav
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Gateron Black Inks.mp3
Normal file
BIN
public/sounds/key-sound/Gateron Black Inks.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Gateron Red Inks.mp3
Normal file
BIN
public/sounds/key-sound/Gateron Red Inks.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Holy Pandas.mp3
Normal file
BIN
public/sounds/key-sound/Holy Pandas.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Kailh Box Navies.mp3
Normal file
BIN
public/sounds/key-sound/Kailh Box Navies.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/NovelKeys Creams.mp3
Normal file
BIN
public/sounds/key-sound/NovelKeys Creams.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/SKCM Blue Alps.mp3
Normal file
BIN
public/sounds/key-sound/SKCM Blue Alps.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Topre.mp3
Normal file
BIN
public/sounds/key-sound/Topre.mp3
Normal file
Binary file not shown.
BIN
public/sounds/key-sound/Turquoise Tealios.mp3
Normal file
BIN
public/sounds/key-sound/Turquoise Tealios.mp3
Normal file
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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' }]
|
||||
|
||||
|
||||
13
src/utils/sounds/keySounds.ts
Normal file
13
src/utils/sounds/keySounds.ts
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user