mirror of
https://github.com/RealKai42/qwerty-learner.git
synced 2026-04-05 06:19:08 +08:00
Merge branch 'master' into refactor/default-config
merge from master
This commit is contained in:
@@ -752,145 +752,145 @@
|
||||
{
|
||||
"name": "math",
|
||||
"trans": [
|
||||
"Income is the money a person or organization earns through work or investments."
|
||||
"A subject that deals with numbers, quantities, and shapes through various operations and formulas."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "moment",
|
||||
"trans": [
|
||||
"Marriage is a legally recognized union between two people, typically involving commitment, companionship, and mutual support."
|
||||
"A brief period of time that has significance or importance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "painting",
|
||||
"trans": [
|
||||
"A user is a person or entity that utilizes a particular product or service."
|
||||
"An artistic creation made using paint on a surface, typically to convey an image or idea."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "politics",
|
||||
"trans": [
|
||||
"A combination is a mixture or blend of two or more things or people."
|
||||
"The activities associated with governing a country or area, including decision-making and the formulation of laws and policies."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "attention",
|
||||
"trans": [
|
||||
"Failure is the lack of success in achieving a desired goal or objective."
|
||||
"The act of focusing one's mental faculties on a particular object, task, or person."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "decision",
|
||||
"trans": [
|
||||
"Meaning is the significance or purpose behind something or someone."
|
||||
"The act of making a choice or coming to a conclusion after considering options and information."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "event",
|
||||
"trans": [
|
||||
"Medicine is the science and practice of diagnosing, treating, and preventing illness or injury."
|
||||
"Something that happens, especially something notable or significant."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "property",
|
||||
"trans": [
|
||||
"Philosophy is the study of fundamental questions about existence, reality, knowledge, values, reason, and ethics."
|
||||
"A thing or things that someone owns, typically a physical object or piece of land."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "shopping",
|
||||
"trans": [
|
||||
"A teacher is a person who instructs or educates others, typically in a school or academic setting."
|
||||
"The activity of browsing and purchasing goods, typically in a retail store or online."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "student",
|
||||
"trans": [
|
||||
"Communication is the exchange of information or ideas between people or groups."
|
||||
"A person who is studying or attending an educational institution."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "wood",
|
||||
"trans": [
|
||||
"Night is the period of darkness between sunset and sunrise."
|
||||
"A hard fibrous material that makes up the stems and branches of trees, used for building, fuel, and various other purposes."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "competition",
|
||||
"trans": [
|
||||
"Chemistry is the scientific study of the properties, composition, and behavior of matter."
|
||||
"A contest between individuals or groups to determine a winner, typically in a sport or game."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "distribution",
|
||||
"trans": [
|
||||
"A disease is a disorder of the body or mind that causes specific symptoms or affects certain organs or systems."
|
||||
"The act of sharing or delivering something to various people or places."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entertainment",
|
||||
"trans": [
|
||||
"A disk is a flat, circular object or storage device that can store or read digital information."
|
||||
"Activities or performances that provide amusement or enjoyment for an audience."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "office",
|
||||
"trans": [
|
||||
"Energy is the ability to do work, produce light or heat, or cause movement."
|
||||
"A place where administrative or professional work is done, typically in a building with rooms for offices and meeting spaces."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "population",
|
||||
"trans": [
|
||||
"A nation is a large group of people who share a common language, culture, history, or government."
|
||||
"The group of individuals living in a particular area or region."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "president",
|
||||
"trans": [
|
||||
"A road is a paved or unpaved pathway used for transportation, typically by vehicles or pedestrians."
|
||||
"The elected or appointed head of a country or organization."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "unit",
|
||||
"trans": [
|
||||
"A role is the function or position someone or something plays in a particular situation or setting."
|
||||
"A single entity or component of a larger group or system."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"trans": [
|
||||
"Soup is a liquid dish typically made by boiling vegetables, meat, or fish in water or stock."
|
||||
"A class or division of things that share common characteristics or features."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cigarette",
|
||||
"trans": [
|
||||
"Advertising is the practice of promoting or selling products or services through various media channels."
|
||||
"A small roll of finely cut tobacco wrapped in paper, typically smoked."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "context",
|
||||
"trans": [
|
||||
"A location is a specific place or position in space, typically with a physical address or geographical coordinates."
|
||||
"The circumstances or conditions in which something occurs or exists, influencing the meaning or interpretation of it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "introduction",
|
||||
"trans": [
|
||||
"Success is the achievement of a desired goal or objective."
|
||||
"The act of presenting or making something known to others for the first time."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "opportunity",
|
||||
"trans": [
|
||||
"Addition is the act or process of adding something to something else."
|
||||
"A chance or possibility for advancement or progress."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "performance",
|
||||
"trans": [
|
||||
"An apartment is a self-contained living space within a larger building, typically rented out to tenants."
|
||||
"The act of carrying out a task, duty, or function, often in a public setting such as a theater or sports arena."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
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.
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
@@ -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}>
|
||||
|
||||
@@ -105,7 +105,11 @@ export default function Word({ word, onFinish }: { word: string; onFinish: () =>
|
||||
|
||||
const inputChar = wordState.inputWord[inputLength - 1]
|
||||
const correctChar = wordState.displayWord[inputLength - 1]
|
||||
const isEqual = isIgnoreCase ? inputChar.toLowerCase() === correctChar.toLowerCase() : inputChar === correctChar
|
||||
|
||||
let isEqual = false
|
||||
if (inputChar != undefined && correctChar != undefined) {
|
||||
isEqual = isIgnoreCase ? inputChar.toLowerCase() === correctChar.toLowerCase() : inputChar === correctChar
|
||||
}
|
||||
|
||||
if (isEqual) {
|
||||
// 输入正确时
|
||||
|
||||
@@ -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