From 1b60a93d6bdb6fde1338a57a148d101e3094e7d0 Mon Sep 17 00:00:00 2001 From: ZaikoXander <80606136+ZaikoXander@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:27:24 -0300 Subject: [PATCH 1/5] start refactor --- bun.lockb | Bin 157915 -> 157907 bytes src/App.tsx | 162 +----------------- src/atoms/{player.ts => player/index.ts} | 15 +- src/atoms/player/remote.ts | 34 ++++ src/atoms/player/store.ts | 11 ++ src/atoms/timer/formattedTimeAtom.ts | 40 +++++ src/atoms/{timer.ts => timer/index.ts} | 19 +- .../AudioOrVideoSourceInput/index.tsx | 26 --- src/components/Button.tsx | 2 +- .../PlayerControl/HiddenMediaPlayer.tsx | 57 ++++++ .../PlayerSourceSelector}/FileInputButton.tsx | 30 ++-- .../YoutubeVideoUrlInput.tsx | 21 ++- .../PlayerSourceSelector/index.tsx | 51 ++++++ src/components/PlayerControl/Timer.tsx | 21 +++ .../StartOrPauseTimerButton.tsx | 114 ++++++++++++ .../TimerControlButtons/index.tsx | 81 +++++++++ .../{ => PlayerControl}/VolumeControl.tsx | 15 +- src/components/PlayerControl/index.tsx | 54 ++++++ src/components/StartOrPauseTimerButton.tsx | 65 ------- src/components/Timer.tsx | 43 ----- src/components/index.tsx | 13 -- src/hooks/usePlayer.ts | 48 ------ src/locales/en.json | 4 +- src/locales/pt-BR.json | 4 +- src/utils/extractYoutubeVideoId.ts | 7 - 25 files changed, 527 insertions(+), 410 deletions(-) rename src/atoms/{player.ts => player/index.ts} (60%) create mode 100644 src/atoms/player/remote.ts create mode 100644 src/atoms/player/store.ts create mode 100644 src/atoms/timer/formattedTimeAtom.ts rename src/atoms/{timer.ts => timer/index.ts} (71%) delete mode 100644 src/components/AudioOrVideoSourceInput/index.tsx create mode 100644 src/components/PlayerControl/HiddenMediaPlayer.tsx rename src/components/{AudioOrVideoSourceInput => PlayerControl/PlayerSourceSelector}/FileInputButton.tsx (52%) rename src/components/{AudioOrVideoSourceInput => PlayerControl/PlayerSourceSelector}/YoutubeVideoUrlInput.tsx (67%) create mode 100644 src/components/PlayerControl/PlayerSourceSelector/index.tsx create mode 100644 src/components/PlayerControl/Timer.tsx create mode 100644 src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx create mode 100644 src/components/PlayerControl/TimerControlButtons/index.tsx rename src/components/{ => PlayerControl}/VolumeControl.tsx (73%) create mode 100644 src/components/PlayerControl/index.tsx delete mode 100644 src/components/StartOrPauseTimerButton.tsx delete mode 100644 src/components/Timer.tsx delete mode 100644 src/components/index.tsx delete mode 100644 src/hooks/usePlayer.ts delete mode 100644 src/utils/extractYoutubeVideoId.ts diff --git a/bun.lockb b/bun.lockb index 962324800b0ba035b75ffcae569747db44b4fbe5..c644a11ec73a3b59f37bf0b95cbf59d0a3a48b0f 100755 GIT binary patch delta 2959 zcmXZXNstp&0LJlYU>d^GFbqV-C3;GgmsD}dg(7NMl~ND!77tdXJmuga#VI%A;1U_N z0y0eEGK3K&DnbjQiEG^B9vQ*7pb=C8f<{q^d-Q$(*D2obd;RZJ9FNV1j?9O)rJSn| zUhmF0&UAX_2BVK|S@#Uq(W|Irv5DdPRdU$E$O9^QY-996l>&A!_K->uyBMEUDPa#2 zJ5|cq$7EHdiUUmTQmNq(({n05x)1A~!8&@oRkGN`@FOZYY+>Y4l{~gFx<{pe9gNjf zirB^YV=5)=Vd8O>GWId~gh~|$nA)pS!y%@hRPoW>r+WtL=&A!_O?n9yBL2*rG!09ysJ{iJ|^E&sp0@r z@2k{si0Kbhd~`q5J%e@h+A3LWV)!GK9JVlWSS62bjLxeRu!FIWRf^cf_z{&7_Aqf& zrHp+{exg#v0j4@CH5_94QxzZG&vegV9lg(0ve?A%7b-byVdR)f9@`lGQl)?$jD4k2 z#4g6WDkbb;;%k*M_Az-}rHTVgolvRa5YykN_~?GCdj{+1eW#MeCWcR{ zPGmJPZ>Mc1I!-Lu>DY;_Ar|d)?Znp-OLls85@BN5PTx-Q0%Fz9z)or%v1VszCp|&* z?YI~6l(AE{<6T6|+G*MeUrfx|X_=Y6a^i~V$%#dcn=Ww<-#k`uMn=rPk>%#-Q>a|E z#Tnl?8RUf~9b3Sgk(=8z9o2dXV8|I=UanT z3|h!MA22_wpTDbeLO-!3c(u%=4d$-TRM7MXZ#QSDId=zbg}LuAN1uBt>!+OY(R=l( za_y9J>4a)2_~w|o?=;6VXKQ7%`R2xL=9T`h#?6^CN1yG%*VmZ)0drQGGaa%#?XY2G*OePo4PP*Ty^21an-1V0uB{e z)FRS4JPL{wRHz`@I$kHNbLENF;XxILGAMPfbM<`xa~sa@JNJKI$_p!wO^0TtL*@%-Baf=&v4hb)Dh2Ff ztgceT9>({ol(3J9eJT|kVDd4Q8V)h_xJn&In0`W~f$o0YGuTA$fJzp93^!DA*v80{ zDtYW+^q@)syBIs9Qp6s{pHeAd9}|aFDmcL8(<(I_V(J-{I*u^iRB52w(mjJs^qy78 zqL1O{RC3tH$nz?B>|pc-l>&A#_M%D=dl-L7rG$M<_$n0~VDe>^8V)h_ib@?vn0{5I zf$nR%XRwLh>nd6FF?>WNhi#0!p_0cAM%yX{>|*Rql_K^q{+3D!`Z;Ungy~OI8t8tidj^~6eWsE{AH$!ki%JoD82?qJgndl>rc%KHCWk6D9AfHsl{$_v{f9~e z-9L5DU=zK+RI=z}_-~aQwlVULN*+5H{a2-cU5t%XirB;We<~&H%k2qAR&XGzNr$P~ z8QMwBBG&DU?4)NC8+P0|JZ0=O?RewFtR3G@*d^xdwCzOZ67zOCcB1o$1v_0ku@JFn zr)MWVpIEZfx06^vtk@aYNiHPT>A-S6JA2h z*=gH}EG6dcbnHYYhy^=cJF#WNqMe?d_;O;&PTx)@@9o7ZS5}d^_Ql#GIYBnQf~l*R*cD$T@n`lC{p*nE5ld&>Vf%R#$Iw zCRVKr@@$i4X7OgM5VSewaZk|J2W{N6o9&N{ZK!V5S8NPk%{6&l@ap!U%`?yIgLX&I zLgx9P`C5Jboz;{2io1eW3rxDg-1R92O~3HH=FB(e?w~C)_xsGzXH)g^&CbO5X7g%J z^~%l8#gm??CG)6XeTg|c%uy>>H=3VZwKaG(VbZSP)wZB5GtURjS!|B^4=w*<%guei zIZMr{RHw~XEUgBwR%|gB^SPy5y{+s-TPwFXN5-9<)pPeaH%v~M7y7Gf=Ik;@-@C`0 zz18c=&cx(yb3G6o<$Y!QU6-_OEjv3Ggsz>k&&OMrPC19K3SD-}KA&!#=sUBW(2CRc Nc}?rgDd+5l{{ad8e6Roj diff --git a/src/App.tsx b/src/App.tsx index 16b39b6..f824941 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,140 +1,16 @@ import { useEffect } from 'react' -import { useAtom, useAtomValue, useSetAtom } from 'jotai' -import { useResetAtom } from 'jotai/utils' -import usePlayer from './hooks/usePlayer' +import PlayerControl from './components/PlayerControl' -import { - playerSourceAtom, - playerVolumeAtom, - playerMutedAtom, -} from './atoms/player' -import { - audioMomentsAtom, - generateRandomAudioMomentsAtom, - removeActualAudioMomentAtom, - audioMomentShouldUnpauseAtom, - audioMomentShouldPlayAtom, -} from './atoms/audioMoments' -import { - timerIsRunningAtom, - startTimerAtom, - pauseTimerAtom, - resetTimerAtom, - timerCanResetAtom, - timeTickingEffect, -} from './atoms/timer' +import { useTranslation } from 'react-i18next' import cn from './lib/cn' -import { MediaPlayer, MediaProvider } from '@vidstack/react' -import '@vidstack/react/player/styles/base.css' - -import { - AudioOrVideoSourceInput, - Button, - StartOrPauseTimerButton, - Timer, - VolumeControl, -} from './components' - -import { useTranslation } from 'react-i18next' - import { FaGithub } from 'react-icons/fa' export default function App() { - const { - player, - playerPaused, - playerCurrentTime, - playerDuration, - playerCanPlay, - resumePlayer, - pausePlayer, - resetPlayerCurrentTime, - resetPlayer, - } = usePlayer() - - const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) - const playerVolume = useAtomValue(playerVolumeAtom) - const playerMuted = useAtomValue(playerMutedAtom) - - const audioMoments = useAtomValue(audioMomentsAtom) - const generateRandomAudioMoments = useSetAtom(generateRandomAudioMomentsAtom) - const removeActualAudioMoment = useSetAtom(removeActualAudioMomentAtom) - const resetAudioMoments = useResetAtom(audioMomentsAtom) - const [audioMomentShouldUnpause, setAudioMomentShouldUnpause] = useAtom( - audioMomentShouldUnpauseAtom, - ) - const audioMomentShouldPlay = useAtomValue(audioMomentShouldPlayAtom) - - const timerIsRunning = useAtomValue(timerIsRunningAtom) - const startTimer = useSetAtom(startTimerAtom) - const pauseTimer = useSetAtom(pauseTimerAtom) - const resetTimer = useSetAtom(resetTimerAtom) - const timerCanReset = useAtomValue(timerCanResetAtom) - useAtom(timeTickingEffect) - const { t, i18n } = useTranslation('', { keyPrefix: 'app' }) - function handleAudioOrVideoSourceInputChange(input: string | File): void { - resetTimer() - if (playerSource !== '') { - resetAudioMoments() - pausePlayer() - resetPlayerCurrentTime() - } - - setPlayerSource(input) - } - - function handleStartTimer(): void { - startTimer() - if (playerPaused && audioMomentShouldUnpause) { - resumePlayer() - setAudioMomentShouldUnpause(false) - } - } - - function handlePauseTimer(): void { - pauseTimer() - if (!playerPaused) { - pausePlayer() - setAudioMomentShouldUnpause(true) - } - } - - function handleStartOrPauseTimerButtonClick(): void { - if (!audioMoments) generateRandomAudioMoments(playerDuration) - - if (timerIsRunning) { - handlePauseTimer() - return - } - - handleStartTimer() - } - - function handleResetTimerButtonClick(): void { - resetTimer() - - if (playerCurrentTime > 0) { - resetPlayer() - resetAudioMoments() - } - } - - useEffect(() => { - function handleAudioMoments() { - if (audioMomentShouldPlay) { - resumePlayer() - removeActualAudioMoment() - } - } - - handleAudioMoments() - }, [audioMomentShouldPlay, resumePlayer, removeActualAudioMoment]) - useEffect(() => { document.title = t('pageTitle') document.documentElement.lang = i18n.language @@ -155,39 +31,7 @@ export default function App() { > {t('title')} -
- - - -
- - -
-
-
- - - -
+ | undefined>(undefined) const playerSourceAtom = atom('') const playerMutedAtom = atom(false) const playerVolumeAtom = atom( @@ -19,4 +26,10 @@ const playerVolumeAtom = atom( }, ) -export { playerSourceAtom, playerMutedAtom, playerVolumeAtom } +export { + playerAtom, + playerSourceAtom, + playerMutedAtom, + playerVolumeAtom, + storeAtom, +} diff --git a/src/atoms/player/remote.ts b/src/atoms/player/remote.ts new file mode 100644 index 0000000..a382ef2 --- /dev/null +++ b/src/atoms/player/remote.ts @@ -0,0 +1,34 @@ +import { atom } from 'jotai' + +import { playerAtom } from '.' + +import { type MediaRemoteControl, useMediaRemote } from '@vidstack/react' + +const remoteAtom = atom((get) => + useMediaRemote(get(playerAtom)), +) + +const resumePlayerAtom = atom(null, (get) => { + const remote = get(remoteAtom) + + remote.play() +}) + +const pausePlayerAtom = atom(null, (get) => { + const remote = get(remoteAtom) + + remote.pause() +}) + +const resetPlayerCurrentTimeAtom = atom(null, (get) => { + const remote = get(remoteAtom) + + remote.seek(0) +}) + +export { + remoteAtom, + resumePlayerAtom, + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} diff --git a/src/atoms/player/store.ts b/src/atoms/player/store.ts new file mode 100644 index 0000000..7c96e16 --- /dev/null +++ b/src/atoms/player/store.ts @@ -0,0 +1,11 @@ +import { atom } from 'jotai' + +import { playerAtom } from '.' + +import { useMediaStore, type MediaState } from '@vidstack/react' + +const storeAtom = atom>((get) => + useMediaStore(get(playerAtom)), +) + +export { storeAtom } diff --git a/src/atoms/timer/formattedTimeAtom.ts b/src/atoms/timer/formattedTimeAtom.ts new file mode 100644 index 0000000..38bd848 --- /dev/null +++ b/src/atoms/timer/formattedTimeAtom.ts @@ -0,0 +1,40 @@ +import { atom } from 'jotai' + +import { timerTotalSecondsAtom } from '.' + +import { ONE_HOUR_IN_SECONDS } from '../../constants' + +const ONE_MINUTE_IN_SECONDS = 60 + +function getHoursDigit(totalSeconds: number): number { + return Math.floor(totalSeconds / ONE_HOUR_IN_SECONDS) +} + +function getMinutesDigit(totalSeconds: number): number { + return Math.floor( + (totalSeconds % ONE_HOUR_IN_SECONDS) / ONE_MINUTE_IN_SECONDS, + ) +} + +function getSecondsDigit(totalSeconds: number): number { + return totalSeconds % ONE_MINUTE_IN_SECONDS +} + +const TIMER_FORMAT_LENGTH = 2 +const TIMER_FORMAT_PADDING = '0' + +function formatDigit(digit: number): string { + return digit.toString().padStart(TIMER_FORMAT_LENGTH, TIMER_FORMAT_PADDING) +} + +const formattedTimeAtom = atom((get) => { + const timerTotalSeconds = get(timerTotalSecondsAtom) + + const hoursDigit = getHoursDigit(timerTotalSeconds) + const minutesDigit = getMinutesDigit(timerTotalSeconds) + const secondsDigit = getSecondsDigit(timerTotalSeconds) + + return [hoursDigit, minutesDigit, secondsDigit].map(formatDigit).join(':') +}) + +export default formattedTimeAtom diff --git a/src/atoms/timer.ts b/src/atoms/timer/index.ts similarity index 71% rename from src/atoms/timer.ts rename to src/atoms/timer/index.ts index 7306d33..0b8d408 100644 --- a/src/atoms/timer.ts +++ b/src/atoms/timer/index.ts @@ -2,9 +2,9 @@ import { atom } from 'jotai' import { atomWithReset, RESET } from 'jotai/utils' import { atomEffect } from 'jotai-effect' -import { ONE_HOUR_IN_SECONDS } from '../constants' +import formattedTimeAtom from './formattedTimeAtom' -const ONE_MINUTE_IN_SECONDS = 60 +import { ONE_HOUR_IN_SECONDS } from '../../constants' const timerTotalSecondsAtom = atomWithReset(ONE_HOUR_IN_SECONDS) const decreaseTimerTotalSecondsAtom = atom(null, (_get, set) => { @@ -13,17 +13,6 @@ const decreaseTimerTotalSecondsAtom = atom(null, (_get, set) => { (previousTimerTotalSeconds) => previousTimerTotalSeconds - 1, ) }) -const timerHoursAtom = atom((get) => - Math.floor(get(timerTotalSecondsAtom) / ONE_HOUR_IN_SECONDS), -) -const timerMinutesAtom = atom((get) => - Math.floor( - (get(timerTotalSecondsAtom) % ONE_HOUR_IN_SECONDS) / ONE_MINUTE_IN_SECONDS, - ), -) -const timerSecondsAtom = atom( - (get) => get(timerTotalSecondsAtom) % ONE_MINUTE_IN_SECONDS, -) const timerIsRunningAtom = atom(false) @@ -55,9 +44,7 @@ const timeTickingEffect = atomEffect((get, set) => { export { timerTotalSecondsAtom, - timerHoursAtom, - timerMinutesAtom, - timerSecondsAtom, + formattedTimeAtom, timerIsRunningAtom, startTimerAtom, pauseTimerAtom, diff --git a/src/components/AudioOrVideoSourceInput/index.tsx b/src/components/AudioOrVideoSourceInput/index.tsx deleted file mode 100644 index 81257d1..0000000 --- a/src/components/AudioOrVideoSourceInput/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import FileInputButton from './FileInputButton' -import YoutubeVideoUrlInput from './YoutubeVideoUrlInput' - -import { useTranslation } from 'react-i18next' - -interface AudioOrVideoInputProps { - onChange?: (input: string | File) => void -} - -export default function AudioOrVideoSourceInput({ - onChange, -}: AudioOrVideoInputProps) { - const { t } = useTranslation('', { keyPrefix: 'audioOrVideoSourceInput' }) - - return ( -
-
- - - {t('audioOrVideoSourceInputSpan')} - -
- -
- ) -} diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 5dde83a..6c807d6 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -6,7 +6,7 @@ interface ButtonProps { children?: ReactNode className?: string disabled?: boolean - onClick?: () => void + onClick?: VoidFunction } export default function Button({ diff --git a/src/components/PlayerControl/HiddenMediaPlayer.tsx b/src/components/PlayerControl/HiddenMediaPlayer.tsx new file mode 100644 index 0000000..8227d23 --- /dev/null +++ b/src/components/PlayerControl/HiddenMediaPlayer.tsx @@ -0,0 +1,57 @@ +import { useAtom, useAtomValue, useSetAtom } from 'jotai' + +import { + playerAtom, + playerSourceAtom, + playerVolumeAtom, + playerMutedAtom, +} from '../../atoms/player' +import { pausePlayerAtom, resetPlayerCurrentTimeAtom } from '../../atoms/player/remote' + +import { MediaPlayer, MediaProvider, useMediaStore } from '@vidstack/react' +import '@vidstack/react/player/styles/base.css' + +export default function HiddenMediaPlayer() { + const player = useAtomValue(playerAtom) + const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) + const playerVolume = useAtomValue(playerVolumeAtom) + const playerMuted = useAtomValue(playerMutedAtom) + + const pausePlayer = useSetAtom(pausePlayerAtom) + const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) + + const { paused: playerPaused, currentTime: playerCurrentTime } = + useMediaStore(player) + + async function resetPlayer(): Promise { + const playerReset = playerPaused && playerCurrentTime === 0 + + if (!playerReset) { + pausePlayer() + resetPlayerCurrentTime() + + if (playerSource instanceof File) { + await new Promise((resolve) => { + setPlayerSource('') + resolve(true) + }) + + setPlayerSource(playerSource) + } + } + } + + return ( +
+ + + +
+ ) +} diff --git a/src/components/AudioOrVideoSourceInput/FileInputButton.tsx b/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx similarity index 52% rename from src/components/AudioOrVideoSourceInput/FileInputButton.tsx rename to src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx index 7ce380e..dd9e3e2 100644 --- a/src/components/AudioOrVideoSourceInput/FileInputButton.tsx +++ b/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx @@ -1,40 +1,38 @@ -import { useRef } from 'react' -import { atom, useAtom } from 'jotai' +import { useRef, useState } from 'react' -import Button from '../Button' +import Button from '../../Button' import { useTranslation } from 'react-i18next' -const fileAtom = atom(undefined) - interface FileInputButtonProps { onChange?: (file: File) => void } export default function FileInputButton({ onChange }: FileInputButtonProps) { const inputRef = useRef(null) - const [file, setFile] = useAtom(fileAtom) - const { t } = useTranslation('', { keyPrefix: 'fileInputButton' }) - function isNewFileEqualToPrevious(newFile: File | undefined): boolean { - if (!newFile || !file) return false + const [file, setFile] = useState(undefined) + const { t } = useTranslation('', { keyPrefix: 'fileInputButton' }) + + function isNewFileEqualToPrevious(newFile: File): boolean { return ( - newFile.name === file.name && - newFile.size === file.size && - newFile.lastModified === file.lastModified + newFile.name === file!.name && + newFile.size === file!.size && + newFile.lastModified === file!.lastModified ) } function handleInputChange(event: React.ChangeEvent) { - const newFile = event.target.files?.[0] - if (!newFile || isNewFileEqualToPrevious(newFile)) return + const newFile = event.target.files?.item(0) + + if (!newFile || (file && isNewFileEqualToPrevious(newFile))) return setFile(newFile) onChange?.(newFile) } - function handleButtonClick() { + function triggerFileInput() { inputRef.current?.click() } @@ -47,7 +45,7 @@ export default function FileInputButton({ onChange }: FileInputButtonProps) { className='hidden' onChange={handleInputChange} /> - + ) } diff --git a/src/components/AudioOrVideoSourceInput/YoutubeVideoUrlInput.tsx b/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx similarity index 67% rename from src/components/AudioOrVideoSourceInput/YoutubeVideoUrlInput.tsx rename to src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx index 34e479a..fe73912 100644 --- a/src/components/AudioOrVideoSourceInput/YoutubeVideoUrlInput.tsx +++ b/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx @@ -1,27 +1,32 @@ -import { atom, useAtom } from 'jotai' +import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from '../../lib/cn' - -import extractYoutubeVideoId from '../../utils/extractYoutubeVideoId' - -const sourceAtom = atom('') +import cn from '../../../lib/cn' interface YoutubeVideoUrlInputProps { onChange?: (youtubeVideoUrl: string) => void } +function extractYoutubeVideoIdByUrl(url: string): string | null { + const regex: RegExp = + /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/ ]{11})/ + const match: RegExpMatchArray | null = url.match(regex) + + return match ? match[1] : null +} + export default function YoutubeVideoUrlInput({ onChange, }: YoutubeVideoUrlInputProps) { - const [source, setSource] = useAtom(sourceAtom) + const [source, setSource] = useState('') const { t } = useTranslation('', { keyPrefix: 'youtubeVideoUrlInput' }) function handleInputChange(event: React.ChangeEvent) { const url = event.target.value - const id = extractYoutubeVideoId(url) + const id = extractYoutubeVideoIdByUrl(url) + if (!id) return const newSource = `youtube/${id}` diff --git a/src/components/PlayerControl/PlayerSourceSelector/index.tsx b/src/components/PlayerControl/PlayerSourceSelector/index.tsx new file mode 100644 index 0000000..614844b --- /dev/null +++ b/src/components/PlayerControl/PlayerSourceSelector/index.tsx @@ -0,0 +1,51 @@ +import { useAtom, useSetAtom } from 'jotai' +import { useResetAtom } from 'jotai/utils' + +import { resetTimerAtom } from '../../../atoms/timer' +import { playerSourceAtom } from '../../../atoms/player' +import { + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} from '../../../atoms/player/remote' +import { audioMomentsAtom } from '../../../atoms/audioMoments' + +import FileInputButton from './FileInputButton' +import YoutubeVideoUrlInput from './YoutubeVideoUrlInput' + +import { useTranslation } from 'react-i18next' + +export default function PlayerSourceSelector() { + const resetTimer = useSetAtom(resetTimerAtom) + + const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) + + const resetAudioMoments = useResetAtom(audioMomentsAtom) + + const pausePlayer = useSetAtom(pausePlayerAtom) + const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) + + function handlePlayerSourceChange(input: string | File): void { + resetTimer() + if (playerSource !== '') { + resetAudioMoments() + pausePlayer() + resetPlayerCurrentTime() + } + + setPlayerSource(input) + } + + const { t } = useTranslation('', { keyPrefix: 'playerSourceSelector' }) + + return ( +
+
+ + + {t('playerSourceSelectorSpan')} + +
+ +
+ ) +} diff --git a/src/components/PlayerControl/Timer.tsx b/src/components/PlayerControl/Timer.tsx new file mode 100644 index 0000000..ee1f7a7 --- /dev/null +++ b/src/components/PlayerControl/Timer.tsx @@ -0,0 +1,21 @@ +import { useAtomValue } from 'jotai' + +import { formattedTimeAtom } from '../../atoms/timer' + +import cn from '../../lib/cn' + +export default function Timer({ className }: { className?: string }) { + const formattedTime = useAtomValue(formattedTimeAtom) + + return ( + + ) +} diff --git a/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx b/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx new file mode 100644 index 0000000..aaac82e --- /dev/null +++ b/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx @@ -0,0 +1,114 @@ +import { useAtom, useAtomValue, useSetAtom } from 'jotai' + +import { playerAtom } from '../../../atoms/player' +import { pausePlayerAtom, resumePlayerAtom } from '../../../atoms/player/remote' +import { + timerIsRunningAtom, + timerCanResetAtom, + startTimerAtom, + pauseTimerAtom, +} from '../../../atoms/timer' +import { + audioMomentsAtom, + audioMomentShouldUnpauseAtom, + generateRandomAudioMomentsAtom, +} from '../../../atoms/audioMoments' + +import { useTranslation } from 'react-i18next' + +import Button from '../../Button' + +import { useMediaStore } from '@vidstack/react' + +import cn from '../../../lib/cn' + +export default function StartOrPauseTimerButton() { + const player = useAtomValue(playerAtom) + + const { + paused: playerPaused, + duration: playerDuration, + canPlay: playerCanPlay, + } = useMediaStore(player) + + const resumePlayer = useSetAtom(resumePlayerAtom) + const pausePlayer = useSetAtom(pausePlayerAtom) + + const startTimer = useSetAtom(startTimerAtom) + const pauseTimer = useSetAtom(pauseTimerAtom) + const timerIsRunning = useAtomValue(timerIsRunningAtom) + const timerCanReset = useAtomValue(timerCanResetAtom) + + const [audioMomentShouldUnpause, setAudioMomentShouldUnpause] = useAtom( + audioMomentShouldUnpauseAtom, + ) + const audioMoments = useAtomValue(audioMomentsAtom) + const generateRandomAudioMoments = useSetAtom(generateRandomAudioMomentsAtom) + + const { t, i18n } = useTranslation('', { + keyPrefix: 'startOrPauseTimerButton', + }) + + type LanguagesAbbreviations = 'en' | 'pt-BR' | 'pt' + + const width24 = 'w-24' + const width28 = 'w-28' + const width32 = 'w-32' + const width40 = 'w-40' + + const widthMapping: { [key in LanguagesAbbreviations]: string } = { + en: timerIsRunning ? width28 : timerCanReset ? width32 : width24, + 'pt-BR': timerIsRunning ? width32 : width40, + pt: timerIsRunning ? width32 : width40, + } + + function getWidthClass(): string | undefined { + return ( + widthMapping[i18n.language as LanguagesAbbreviations] || + (timerIsRunning ? width28 : timerCanReset ? width32 : width24) + ) + } + + function handleStartTimer(): void { + startTimer() + if (playerPaused && audioMomentShouldUnpause) { + resumePlayer() + setAudioMomentShouldUnpause(false) + } + } + + function handlePauseTimer(): void { + pauseTimer() + if (!playerPaused) { + pausePlayer() + setAudioMomentShouldUnpause(true) + } + } + + function handleClick(): void { + if (!audioMoments) generateRandomAudioMoments(playerDuration) + + if (timerIsRunning) { + handlePauseTimer() + } else { + handleStartTimer() + } + } + + return ( + + ) +} diff --git a/src/components/PlayerControl/TimerControlButtons/index.tsx b/src/components/PlayerControl/TimerControlButtons/index.tsx new file mode 100644 index 0000000..3b99936 --- /dev/null +++ b/src/components/PlayerControl/TimerControlButtons/index.tsx @@ -0,0 +1,81 @@ +import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { useResetAtom } from 'jotai/utils' + +import { playerAtom, playerSourceAtom } from '../../../atoms/player' +import { + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} from '../../../atoms/player/remote' +import { resetTimerAtom, timerCanResetAtom } from '../../../atoms/timer' +import { audioMomentsAtom } from '../../../atoms/audioMoments' + +import StartOrPauseTimerButton from './StartOrPauseTimerButton' +import Button from '../../Button' + +import { useMediaStore } from '@vidstack/react' + +import { useTranslation } from 'react-i18next' + +export default function TimerControlButtons() { + const player = useAtomValue(playerAtom) + const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) + + const { paused: playerPaused, currentTime: playerCurrentTime } = + useMediaStore(player) + + const pausePlayer = useSetAtom(pausePlayerAtom) + const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) + + const timerCanReset = useAtomValue(timerCanResetAtom) + const resetTimer = useSetAtom(resetTimerAtom) + + const resetAudioMoments = useResetAtom(audioMomentsAtom) + + const { t } = useTranslation('', { keyPrefix: 'app' }) + + function playerIsNotReset(): boolean { + return !(playerPaused && playerCurrentTime === 0) + } + + function playerSourceIsAFile(): boolean { + return playerSource instanceof File + } + + async function resetPlayer(): Promise { + if (playerIsNotReset()) { + pausePlayer() + resetPlayerCurrentTime() + + if (playerSourceIsAFile()) { + await new Promise((resolve) => { + setPlayerSource('') + resolve(true) + }) + + setPlayerSource(playerSource) + } + } + } + + function handleResetTimerButtonClick(): void { + resetTimer() + + if (playerCurrentTime > 0) { + resetPlayer() + resetAudioMoments() + } + } + + return ( +
+ + +
+ ) +} diff --git a/src/components/VolumeControl.tsx b/src/components/PlayerControl/VolumeControl.tsx similarity index 73% rename from src/components/VolumeControl.tsx rename to src/components/PlayerControl/VolumeControl.tsx index 912413c..3985e15 100644 --- a/src/components/VolumeControl.tsx +++ b/src/components/PlayerControl/VolumeControl.tsx @@ -1,11 +1,20 @@ +import type { ChangeEvent } from 'react' + import { useAtom } from 'jotai' -import { playerMutedAtom, playerVolumeAtom } from '../atoms/player' + +import { playerMutedAtom, playerVolumeAtom } from '../../atoms/player' import { FaVolumeMute, FaVolumeUp } from 'react-icons/fa' export default function VolumeControl() { - const [playerVolume, changePlayerVolume] = useAtom(playerVolumeAtom) const [playerMuted, setPlayerMuted] = useAtom(playerMutedAtom) + const [playerVolume, changePlayerVolume] = useAtom(playerVolumeAtom) + + function handleInputChange(event: ChangeEvent) { + const valueToNumber = parseFloat(event.target.value) + + changePlayerVolume(valueToNumber) + } return (
@@ -25,7 +34,7 @@ export default function VolumeControl() { max='1' step='0.01' value={playerMuted ? 0 : playerVolume} - onChange={(e) => changePlayerVolume(parseFloat(e.target.value))} + onChange={handleInputChange} className='w-16 accent-blue-500' aria-labelledby='volume-control' /> diff --git a/src/components/PlayerControl/index.tsx b/src/components/PlayerControl/index.tsx new file mode 100644 index 0000000..83c1175 --- /dev/null +++ b/src/components/PlayerControl/index.tsx @@ -0,0 +1,54 @@ +import { useEffect, useRef } from 'react' + +import { useAtom, useAtomValue, useSetAtom } from 'jotai' + +import { playerAtom } from '../../atoms/player' +import { resumePlayerAtom } from '../../atoms/player/remote' +import { + removeActualAudioMomentAtom, + audioMomentShouldPlayAtom, +} from '../../atoms/audioMoments' +import { timeTickingEffect } from '../../atoms/timer' + +import { type MediaPlayerInstance } from '@vidstack/react' + +import Timer from './Timer' +import VolumeControl from './VolumeControl' +import PlayerSourceSelector from './PlayerSourceSelector' +import TimerControlButtons from './TimerControlButtons' +import HiddenMediaPlayer from './HiddenMediaPlayer' + +export default function PlayerControl() { + const setPlayer = useSetAtom(playerAtom) + setPlayer(useRef(null)) + + const audioMomentShouldPlay = useAtomValue(audioMomentShouldPlayAtom) + const removeActualAudioMoment = useSetAtom(removeActualAudioMomentAtom) + + const resumePlayer = useSetAtom(resumePlayerAtom) + + useAtom(timeTickingEffect) + + useEffect(() => { + function handleAudioMoments() { + if (audioMomentShouldPlay) { + resumePlayer() + removeActualAudioMoment() + } + } + + handleAudioMoments() + }, [audioMomentShouldPlay, resumePlayer, removeActualAudioMoment]) + + return ( + <> +
+ + + + +
+ + + ) +} diff --git a/src/components/StartOrPauseTimerButton.tsx b/src/components/StartOrPauseTimerButton.tsx deleted file mode 100644 index f2382b9..0000000 --- a/src/components/StartOrPauseTimerButton.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useAtomValue } from 'jotai' -import { timerIsRunningAtom, timerCanResetAtom } from '../atoms/timer' - -import { useTranslation } from 'react-i18next' - -import cn from '../lib/cn' - -import Button from './Button' - -interface StartOrPauseTimerButtonProps { - disabled: boolean - onClick: () => void -} - -export default function StartOrPauseTimerButton({ - disabled, - onClick, -}: StartOrPauseTimerButtonProps) { - const timerIsRunning = useAtomValue(timerIsRunningAtom) - const timerCanReset = useAtomValue(timerCanResetAtom) - - const { t, i18n } = useTranslation('', { - keyPrefix: 'startOrPauseTimerButton', - }) - - function startOrPauseTimerButtonText() { - if (timerIsRunning) return t('pauseTimerButtonText') - if (timerCanReset) return t('resumeTimerButtonText') - - return t('startTimerButtonText') - } - - type LanguagesAbbreviations = 'en' | 'pt-BR' | 'pt' - - const width24 = 'w-24' - const width28 = 'w-28' - const width32 = 'w-32' - const width40 = 'w-40' - - const widthMapping: { [key in LanguagesAbbreviations]: string } = { - en: timerIsRunning ? width28 : timerCanReset ? width32 : width24, - 'pt-BR': timerIsRunning ? width32 : width40, - pt: timerIsRunning ? width32 : width40, - } - - function getWidthClass(): string | undefined { - return ( - widthMapping[i18n.language as LanguagesAbbreviations] || - (timerIsRunning ? width28 : timerCanReset ? width32 : width24) - ) - } - - return ( - - ) -} diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx deleted file mode 100644 index 8b32b9b..0000000 --- a/src/components/Timer.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useAtomValue } from 'jotai' -import { - timerHoursAtom, - timerMinutesAtom, - timerSecondsAtom, -} from '../atoms/timer' - -import cn from '../lib/cn' - -const TIMER_FORMAT_LENGTH = 2 -const TIMER_FORMAT_PADDING = '0' - -function formatDigit(digit: number): string { - return digit.toString().padStart(TIMER_FORMAT_LENGTH, TIMER_FORMAT_PADDING) -} - -interface TimerProps { - className?: string -} - -export default function Timer({ className }: TimerProps) { - const timerHours = useAtomValue(timerHoursAtom) - const timerMinutes = useAtomValue(timerMinutesAtom) - const timerSeconds = useAtomValue(timerSecondsAtom) - - function formatTime(): string { - const digits: number[] = [timerHours, timerMinutes, timerSeconds] - - return digits.map(formatDigit).join(':') - } - - return ( - - ) -} diff --git a/src/components/index.tsx b/src/components/index.tsx deleted file mode 100644 index 952b12b..0000000 --- a/src/components/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import AudioOrVideoSourceInput from './AudioOrVideoSourceInput' -import Button from './Button' -import StartOrPauseTimerButton from './StartOrPauseTimerButton' -import Timer from './Timer' -import VolumeControl from './VolumeControl' - -export { - AudioOrVideoSourceInput, - Button, - StartOrPauseTimerButton, - Timer, - VolumeControl, -} diff --git a/src/hooks/usePlayer.ts b/src/hooks/usePlayer.ts deleted file mode 100644 index 415bef3..0000000 --- a/src/hooks/usePlayer.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useRef } from 'react' -import { useAtom } from 'jotai' - -import { playerSourceAtom } from '../atoms/player' - -import { - useMediaRemote, - useMediaStore, - type MediaPlayerInstance, -} from '@vidstack/react' - -export default function usePlayer() { - const player = useRef(null) - const remote = useMediaRemote(player) - const { paused, currentTime, duration, canPlay } = useMediaStore(player) - - const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) - - const pausePlayer = () => remote.pause() - const resetPlayerCurrentTime = () => remote.seek(0) - - async function resetPlayer(): Promise { - const playerReset = paused && currentTime === 0 - if (!playerReset) { - pausePlayer() - resetPlayerCurrentTime() - if (playerSource instanceof File) { - await new Promise((resolve) => { - setPlayerSource('') - resolve(true) - }) - setPlayerSource(playerSource) - } - } - } - - return { - player, - playerPaused: paused, - playerCurrentTime: currentTime, - playerDuration: duration, - playerCanPlay: canPlay, - resumePlayer: () => remote.play(), - pausePlayer, - resetPlayerCurrentTime, - resetPlayer, - } -} diff --git a/src/locales/en.json b/src/locales/en.json index 12f9565..722625d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -4,8 +4,8 @@ "title": "One hour of interrupted silence", "resetButton": "Reset" }, - "audioOrVideoSourceInput": { - "audioOrVideoSourceInputSpan": "or" + "playerSourceSelector": { + "playerSourceSelectorSpan": "or" }, "fileInputButton": { "fileInputButtonText": "Use audio or video file" diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json index a6a9dea..4522a88 100644 --- a/src/locales/pt-BR.json +++ b/src/locales/pt-BR.json @@ -4,8 +4,8 @@ "title": "Uma hora de silêncio interrompido", "resetButton": "Zerar" }, - "audioOrVideoSourceInput": { - "audioOrVideoSourceInputSpan": "ou" + "playerSourceSelector": { + "playerSourceSelectorSpan": "ou" }, "fileInputButton": { "fileInputButtonText": "Usar arquivo de áudio ou vídeo" diff --git a/src/utils/extractYoutubeVideoId.ts b/src/utils/extractYoutubeVideoId.ts deleted file mode 100644 index 09d1758..0000000 --- a/src/utils/extractYoutubeVideoId.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default function extractYoutubeVideoId(url: string): string | null { - const regex: RegExp = - /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/ ]{11})/ - const match: RegExpMatchArray | null = url.match(regex) - - return match ? match[1] : null -} From 82d153b3117ca7b0be12059eacf1d6c682d45b71 Mon Sep 17 00:00:00 2001 From: ZaikoXander <80606136+ZaikoXander@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:11:24 -0300 Subject: [PATCH 2/5] refactor: Create and use storeAtoms --- src/atoms/player/index.ts | 10 +--------- src/atoms/player/store.ts | 11 ++++++++++- .../PlayerControl/HiddenMediaPlayer.tsx | 15 +++++++++++---- .../StartOrPauseTimerButton.tsx | 18 ++++++++---------- .../TimerControlButtons/index.tsx | 13 +++++++------ 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/atoms/player/index.ts b/src/atoms/player/index.ts index 60add9c..02f383e 100644 --- a/src/atoms/player/index.ts +++ b/src/atoms/player/index.ts @@ -2,8 +2,6 @@ import type { RefObject } from 'react' import { atom } from 'jotai' -import { storeAtom } from './store' - import type { MediaPlayerInstance } from '@vidstack/react' const volumeAtFifthPercent = 0.5 @@ -26,10 +24,4 @@ const playerVolumeAtom = atom( }, ) -export { - playerAtom, - playerSourceAtom, - playerMutedAtom, - playerVolumeAtom, - storeAtom, -} +export { playerAtom, playerSourceAtom, playerMutedAtom, playerVolumeAtom } diff --git a/src/atoms/player/store.ts b/src/atoms/player/store.ts index 7c96e16..779cd16 100644 --- a/src/atoms/player/store.ts +++ b/src/atoms/player/store.ts @@ -7,5 +7,14 @@ import { useMediaStore, type MediaState } from '@vidstack/react' const storeAtom = atom>((get) => useMediaStore(get(playerAtom)), ) +const playerPausedAtom = atom((get) => get(storeAtom).paused) +const playerDurationAtom = atom((get) => get(storeAtom).duration) +const playerCanPlayAtom = atom((get) => get(storeAtom).canPlay) +const playerCurrentTimeAtom = atom((get) => get(storeAtom).currentTime) -export { storeAtom } +export { + playerPausedAtom, + playerDurationAtom, + playerCanPlayAtom, + playerCurrentTimeAtom, +} diff --git a/src/components/PlayerControl/HiddenMediaPlayer.tsx b/src/components/PlayerControl/HiddenMediaPlayer.tsx index 8227d23..1dea6f7 100644 --- a/src/components/PlayerControl/HiddenMediaPlayer.tsx +++ b/src/components/PlayerControl/HiddenMediaPlayer.tsx @@ -6,9 +6,16 @@ import { playerVolumeAtom, playerMutedAtom, } from '../../atoms/player' -import { pausePlayerAtom, resetPlayerCurrentTimeAtom } from '../../atoms/player/remote' +import { + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} from '../../atoms/player/remote' +import { + playerCurrentTimeAtom, + playerPausedAtom, +} from '../../atoms/player/store' -import { MediaPlayer, MediaProvider, useMediaStore } from '@vidstack/react' +import { MediaPlayer, MediaProvider } from '@vidstack/react' import '@vidstack/react/player/styles/base.css' export default function HiddenMediaPlayer() { @@ -20,8 +27,8 @@ export default function HiddenMediaPlayer() { const pausePlayer = useSetAtom(pausePlayerAtom) const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) - const { paused: playerPaused, currentTime: playerCurrentTime } = - useMediaStore(player) + const playerPaused = useAtomValue(playerPausedAtom) + const playerCurrentTime = useAtomValue(playerCurrentTimeAtom) async function resetPlayer(): Promise { const playerReset = playerPaused && playerCurrentTime === 0 diff --git a/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx b/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx index aaac82e..eb31602 100644 --- a/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx +++ b/src/components/PlayerControl/TimerControlButtons/StartOrPauseTimerButton.tsx @@ -1,6 +1,10 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai' -import { playerAtom } from '../../../atoms/player' +import { + playerCanPlayAtom, + playerDurationAtom, + playerPausedAtom, +} from '../../../atoms/player/store' import { pausePlayerAtom, resumePlayerAtom } from '../../../atoms/player/remote' import { timerIsRunningAtom, @@ -18,18 +22,12 @@ import { useTranslation } from 'react-i18next' import Button from '../../Button' -import { useMediaStore } from '@vidstack/react' - import cn from '../../../lib/cn' export default function StartOrPauseTimerButton() { - const player = useAtomValue(playerAtom) - - const { - paused: playerPaused, - duration: playerDuration, - canPlay: playerCanPlay, - } = useMediaStore(player) + const playerPaused = useAtomValue(playerPausedAtom) + const playerDuration = useAtomValue(playerDurationAtom) + const playerCanPlay = useAtomValue(playerCanPlayAtom) const resumePlayer = useSetAtom(resumePlayerAtom) const pausePlayer = useSetAtom(pausePlayerAtom) diff --git a/src/components/PlayerControl/TimerControlButtons/index.tsx b/src/components/PlayerControl/TimerControlButtons/index.tsx index 3b99936..66c2004 100644 --- a/src/components/PlayerControl/TimerControlButtons/index.tsx +++ b/src/components/PlayerControl/TimerControlButtons/index.tsx @@ -1,27 +1,28 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useResetAtom } from 'jotai/utils' -import { playerAtom, playerSourceAtom } from '../../../atoms/player' +import { playerSourceAtom } from '../../../atoms/player' import { pausePlayerAtom, resetPlayerCurrentTimeAtom, } from '../../../atoms/player/remote' import { resetTimerAtom, timerCanResetAtom } from '../../../atoms/timer' +import { + playerCurrentTimeAtom, + playerPausedAtom, +} from '../../../atoms/player/store' import { audioMomentsAtom } from '../../../atoms/audioMoments' import StartOrPauseTimerButton from './StartOrPauseTimerButton' import Button from '../../Button' -import { useMediaStore } from '@vidstack/react' - import { useTranslation } from 'react-i18next' export default function TimerControlButtons() { - const player = useAtomValue(playerAtom) const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) - const { paused: playerPaused, currentTime: playerCurrentTime } = - useMediaStore(player) + const playerPaused = useAtomValue(playerPausedAtom) + const playerCurrentTime = useAtomValue(playerCurrentTimeAtom) const pausePlayer = useSetAtom(pausePlayerAtom) const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) From 0041bb0a9113b6f3f970b9188cca41a9941e01a3 Mon Sep 17 00:00:00 2001 From: ZaikoXander <80606136+ZaikoXander@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:30:13 -0300 Subject: [PATCH 3/5] refactor(PlayerSourceSelector): Move playerSourceChange logic into a atom --- .../PlayerSourceSelector/FileInputButton.tsx | 13 +++---- .../YoutubeVideoUrlInput.tsx | 15 ++++---- .../handlePlayerSourceChange.ts | 26 ++++++++++++++ .../PlayerSourceSelector/index.tsx | 35 ++----------------- 4 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 src/components/PlayerControl/PlayerSourceSelector/handlePlayerSourceChange.ts diff --git a/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx b/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx index dd9e3e2..76dd1b0 100644 --- a/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx +++ b/src/components/PlayerControl/PlayerSourceSelector/FileInputButton.tsx @@ -1,17 +1,18 @@ import { useRef, useState } from 'react' +import { useSetAtom } from 'jotai' + +import handlePlayerSourceChangeAtom from './handlePlayerSourceChange' + import Button from '../../Button' import { useTranslation } from 'react-i18next' -interface FileInputButtonProps { - onChange?: (file: File) => void -} - -export default function FileInputButton({ onChange }: FileInputButtonProps) { +export default function FileInputButton() { const inputRef = useRef(null) const [file, setFile] = useState(undefined) + const handlePlayerSourceChange = useSetAtom(handlePlayerSourceChangeAtom) const { t } = useTranslation('', { keyPrefix: 'fileInputButton' }) @@ -29,7 +30,7 @@ export default function FileInputButton({ onChange }: FileInputButtonProps) { if (!newFile || (file && isNewFileEqualToPrevious(newFile))) return setFile(newFile) - onChange?.(newFile) + handlePlayerSourceChange(newFile) } function triggerFileInput() { diff --git a/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx b/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx index fe73912..65d7cac 100644 --- a/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx +++ b/src/components/PlayerControl/PlayerSourceSelector/YoutubeVideoUrlInput.tsx @@ -1,13 +1,13 @@ import { useState } from 'react' +import { useSetAtom } from 'jotai' + +import handlePlayerSourceChangeAtom from './handlePlayerSourceChange' + import { useTranslation } from 'react-i18next' import cn from '../../../lib/cn' -interface YoutubeVideoUrlInputProps { - onChange?: (youtubeVideoUrl: string) => void -} - function extractYoutubeVideoIdByUrl(url: string): string | null { const regex: RegExp = /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/ ]{11})/ @@ -16,10 +16,9 @@ function extractYoutubeVideoIdByUrl(url: string): string | null { return match ? match[1] : null } -export default function YoutubeVideoUrlInput({ - onChange, -}: YoutubeVideoUrlInputProps) { +export default function YoutubeVideoUrlInput() { const [source, setSource] = useState('') + const handlePlayerSourceChange = useSetAtom(handlePlayerSourceChangeAtom) const { t } = useTranslation('', { keyPrefix: 'youtubeVideoUrlInput' }) @@ -33,7 +32,7 @@ export default function YoutubeVideoUrlInput({ if (newSource === source) return setSource(newSource) - onChange?.(newSource) + handlePlayerSourceChange(newSource) } return ( diff --git a/src/components/PlayerControl/PlayerSourceSelector/handlePlayerSourceChange.ts b/src/components/PlayerControl/PlayerSourceSelector/handlePlayerSourceChange.ts new file mode 100644 index 0000000..24a0016 --- /dev/null +++ b/src/components/PlayerControl/PlayerSourceSelector/handlePlayerSourceChange.ts @@ -0,0 +1,26 @@ +import { atom } from 'jotai' +import { RESET } from 'jotai/utils' + +import { resetTimerAtom } from '../../../atoms/timer' +import { playerSourceAtom } from '../../../atoms/player' +import { audioMomentsAtom } from '../../../atoms/audioMoments' +import { + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} from '../../../atoms/player/remote' + +const handlePlayerSourceChangeAtom = atom( + null, + (get, set, input: string | File) => { + set(resetTimerAtom) + if (get(playerSourceAtom) !== '') { + set(audioMomentsAtom, RESET) + set(pausePlayerAtom) + set(resetPlayerCurrentTimeAtom) + } + + set(playerSourceAtom, input) + }, +) + +export default handlePlayerSourceChangeAtom diff --git a/src/components/PlayerControl/PlayerSourceSelector/index.tsx b/src/components/PlayerControl/PlayerSourceSelector/index.tsx index 614844b..239e10b 100644 --- a/src/components/PlayerControl/PlayerSourceSelector/index.tsx +++ b/src/components/PlayerControl/PlayerSourceSelector/index.tsx @@ -1,51 +1,20 @@ -import { useAtom, useSetAtom } from 'jotai' -import { useResetAtom } from 'jotai/utils' - -import { resetTimerAtom } from '../../../atoms/timer' -import { playerSourceAtom } from '../../../atoms/player' -import { - pausePlayerAtom, - resetPlayerCurrentTimeAtom, -} from '../../../atoms/player/remote' -import { audioMomentsAtom } from '../../../atoms/audioMoments' - import FileInputButton from './FileInputButton' import YoutubeVideoUrlInput from './YoutubeVideoUrlInput' import { useTranslation } from 'react-i18next' export default function PlayerSourceSelector() { - const resetTimer = useSetAtom(resetTimerAtom) - - const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) - - const resetAudioMoments = useResetAtom(audioMomentsAtom) - - const pausePlayer = useSetAtom(pausePlayerAtom) - const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) - - function handlePlayerSourceChange(input: string | File): void { - resetTimer() - if (playerSource !== '') { - resetAudioMoments() - pausePlayer() - resetPlayerCurrentTime() - } - - setPlayerSource(input) - } - const { t } = useTranslation('', { keyPrefix: 'playerSourceSelector' }) return (
- + {t('playerSourceSelectorSpan')}
- +
) } From b62868dd4dd35d42f5e5d1d5bdbccdbec6c2a28e Mon Sep 17 00:00:00 2001 From: ZaikoXander <80606136+ZaikoXander@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:36:38 -0300 Subject: [PATCH 4/5] refactor(TimerControlButtons): Create ResetTimerButton component --- .../TimerControlButtons/ResetTimerButton.tsx | 78 +++++++++++++++++++ .../TimerControlButtons/index.tsx | 75 +----------------- 2 files changed, 80 insertions(+), 73 deletions(-) create mode 100644 src/components/PlayerControl/TimerControlButtons/ResetTimerButton.tsx diff --git a/src/components/PlayerControl/TimerControlButtons/ResetTimerButton.tsx b/src/components/PlayerControl/TimerControlButtons/ResetTimerButton.tsx new file mode 100644 index 0000000..307b295 --- /dev/null +++ b/src/components/PlayerControl/TimerControlButtons/ResetTimerButton.tsx @@ -0,0 +1,78 @@ +import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { useResetAtom } from 'jotai/utils' + +import { playerSourceAtom } from '../../../atoms/player' +import { + pausePlayerAtom, + resetPlayerCurrentTimeAtom, +} from '../../../atoms/player/remote' +import { resetTimerAtom, timerCanResetAtom } from '../../../atoms/timer' +import { + playerCurrentTimeAtom, + playerPausedAtom, +} from '../../../atoms/player/store' +import { audioMomentsAtom } from '../../../atoms/audioMoments' + +import Button from '../../Button' + +import { useTranslation } from 'react-i18next' + +export default function ResetTimerButton() { + const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) + + const playerPaused = useAtomValue(playerPausedAtom) + const playerCurrentTime = useAtomValue(playerCurrentTimeAtom) + + const pausePlayer = useSetAtom(pausePlayerAtom) + const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) + + const timerCanReset = useAtomValue(timerCanResetAtom) + const resetTimer = useSetAtom(resetTimerAtom) + + const resetAudioMoments = useResetAtom(audioMomentsAtom) + + const { t } = useTranslation('', { keyPrefix: 'app' }) + + function playerIsNotReset(): boolean { + return !(playerPaused && playerCurrentTime === 0) + } + + function playerSourceIsAFile(): boolean { + return playerSource instanceof File + } + + async function resetPlayer(): Promise { + if (playerIsNotReset()) { + pausePlayer() + resetPlayerCurrentTime() + + if (playerSourceIsAFile()) { + await new Promise((resolve) => { + setPlayerSource('') + resolve(true) + }) + + setPlayerSource(playerSource) + } + } + } + + function handleClick(): void { + resetTimer() + + if (playerCurrentTime > 0) { + resetPlayer() + resetAudioMoments() + } + } + + return ( + + ) +} diff --git a/src/components/PlayerControl/TimerControlButtons/index.tsx b/src/components/PlayerControl/TimerControlButtons/index.tsx index 66c2004..8017412 100644 --- a/src/components/PlayerControl/TimerControlButtons/index.tsx +++ b/src/components/PlayerControl/TimerControlButtons/index.tsx @@ -1,82 +1,11 @@ -import { useAtom, useAtomValue, useSetAtom } from 'jotai' -import { useResetAtom } from 'jotai/utils' - -import { playerSourceAtom } from '../../../atoms/player' -import { - pausePlayerAtom, - resetPlayerCurrentTimeAtom, -} from '../../../atoms/player/remote' -import { resetTimerAtom, timerCanResetAtom } from '../../../atoms/timer' -import { - playerCurrentTimeAtom, - playerPausedAtom, -} from '../../../atoms/player/store' -import { audioMomentsAtom } from '../../../atoms/audioMoments' - import StartOrPauseTimerButton from './StartOrPauseTimerButton' -import Button from '../../Button' - -import { useTranslation } from 'react-i18next' +import ResetTimerButton from './ResetTimerButton' export default function TimerControlButtons() { - const [playerSource, setPlayerSource] = useAtom(playerSourceAtom) - - const playerPaused = useAtomValue(playerPausedAtom) - const playerCurrentTime = useAtomValue(playerCurrentTimeAtom) - - const pausePlayer = useSetAtom(pausePlayerAtom) - const resetPlayerCurrentTime = useSetAtom(resetPlayerCurrentTimeAtom) - - const timerCanReset = useAtomValue(timerCanResetAtom) - const resetTimer = useSetAtom(resetTimerAtom) - - const resetAudioMoments = useResetAtom(audioMomentsAtom) - - const { t } = useTranslation('', { keyPrefix: 'app' }) - - function playerIsNotReset(): boolean { - return !(playerPaused && playerCurrentTime === 0) - } - - function playerSourceIsAFile(): boolean { - return playerSource instanceof File - } - - async function resetPlayer(): Promise { - if (playerIsNotReset()) { - pausePlayer() - resetPlayerCurrentTime() - - if (playerSourceIsAFile()) { - await new Promise((resolve) => { - setPlayerSource('') - resolve(true) - }) - - setPlayerSource(playerSource) - } - } - } - - function handleResetTimerButtonClick(): void { - resetTimer() - - if (playerCurrentTime > 0) { - resetPlayer() - resetAudioMoments() - } - } - return (
- +
) } From b58f50857b21e65bfccab9ce468477302ccdf79f Mon Sep 17 00:00:00 2001 From: ZaikoXander <80606136+ZaikoXander@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:42:29 -0300 Subject: [PATCH 5/5] Replace import type syntax PlayerControl --- src/components/PlayerControl/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlayerControl/index.tsx b/src/components/PlayerControl/index.tsx index 83c1175..bc62093 100644 --- a/src/components/PlayerControl/index.tsx +++ b/src/components/PlayerControl/index.tsx @@ -10,7 +10,7 @@ import { } from '../../atoms/audioMoments' import { timeTickingEffect } from '../../atoms/timer' -import { type MediaPlayerInstance } from '@vidstack/react' +import type { MediaPlayerInstance } from '@vidstack/react' import Timer from './Timer' import VolumeControl from './VolumeControl'