diff --git a/package-lock.json b/package-lock.json index 24a5dd1..b79b1f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "kysely-codegen": "^0.13.1", "postcss": "^8", "tailwindcss": "^3.3.0", + "type-fest": "^4.12.0", "typescript": "^5" } }, @@ -4267,6 +4268,18 @@ "node": ">=8" } }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/boxen/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6811,6 +6824,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", @@ -10967,12 +10992,12 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.12.0.tgz", + "integrity": "sha512-5Y2/pp2wtJk8o08G0CMkuFPCO354FGwk/vbidxrdhRGZfd0tFnb4Qb8anp9XxXriwBgVPjdWbKpGl4J9lJY2jQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 1e1ab3d..913381d 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "kysely-codegen": "^0.13.1", "postcss": "^8", "tailwindcss": "^3.3.0", + "type-fest": "^4.12.0", "typescript": "^5" } } diff --git a/src/components/chuni/userbox.tsx b/src/components/chuni/userbox.tsx index 7ff8ab1..053cdb6 100644 --- a/src/components/chuni/userbox.tsx +++ b/src/components/chuni/userbox.tsx @@ -12,6 +12,8 @@ import { ChuniAvatar } from '@/components/chuni/avatar'; import { CHUNI_VOICE_LINES } from '@/helpers/chuni/voice'; import { PlayIcon, StopIcon } from '@heroicons/react/24/solid'; import { SaveIcon } from '@/components/save-icon'; +import { useAudio } from '@/helpers/use-audio'; +import { useIsMounted } from 'usehooks-ts'; export type ChuniUserboxProps = { profile: ChuniUserData, @@ -44,12 +46,20 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => { .find(i => ('id' in i ? i.id : i.avatarAccessoryId) === profile?.[profileKey])])) as EquippedItem); const [equipped, setEquipped] = useState(initialEquipped.current); const [saved, setSaved] = useState(Object.fromEntries(Object.keys(ITEM_KEYS).map(k => [k, true])) as any); - const audioRef = useRef(null); const [playingVoice, setPlayingVoice] = useState(false); const [selectedLine, setSelectedLine] = useState(new Set(['0035'])); - const [playPreviews, setPlayPreviews] = useState(true); + const [playPreviews, _setPlayPreviews] = useState(true); const [selectingVoice, setSelectingVoice] = useState(null); + const setPlayPreviews = (play: boolean) => { + _setPlayPreviews(play); + localStorage.setItem('chuni-userbox-play-previews', play ? '1' : ''); + } + + useEffect(() => { + _setPlayPreviews(!!(localStorage.getItem('chuni-userbox-play-previews') ?? 1)); + }, []); + const equipItem = (k: K, item: RequiredUserbox[K][number] | undefined | null) => { if (!item || equipped[k] === item) return; @@ -63,28 +73,11 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => { .entries(initialEquipped.current).filter(([k]) => items.includes(k as any))) })) }; - useEffect(() => { - const audio = audioRef.current; - if (!audio) return; - audio.volume = 0.25; - - const setPlay = () => { - setPlayingVoice(true); - }; - const setStop = () => { - setPlayingVoice(false); - }; - audio.addEventListener('play', setPlay); - audio.addEventListener('ended', setStop); - audio.addEventListener('pause', setStop) - - return () => { - audio.removeEventListener('play', setPlay); - audio.removeEventListener('ended', setStop); - audio.removeEventListener('pause', setStop); - audio.pause(); - }; - }, []); + const audioRef = useAudio({ + play: () => setPlayingVoice(true), + ended: () => setPlayingVoice(false), + pause: () => setPlayingVoice(false) + }, audio => audio.volume = 0.25); const play = (src: string) => { if (!audioRef.current || !playPreviews) return; @@ -202,8 +195,6 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => { {/* begin system voice */}
-