feat: implement shuffle functionality

This commit is contained in:
MAZE 2023-10-11 18:06:11 +03:30
parent 4124beb5b4
commit 26ba017815
9 changed files with 166 additions and 71 deletions

View File

@ -0,0 +1,51 @@
.buttons {
position: sticky;
z-index: 10;
top: 30px;
display: flex;
align-items: center;
justify-content: center;
column-gap: 10px;
& .playButton {
display: flex;
width: 150px;
height: 45px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid #818cf8;
border-bottom: 3px solid #4f46e5;
background-color: #6366f1;
color: var(--color-foreground);
cursor: pointer;
font-family: var(--font-heading);
font-size: var(--font-base);
line-height: 0;
outline: none;
& span {
font-size: var(--font-lg);
}
}
& .shuffleButton {
display: flex;
width: 45px;
height: 45px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid var(--color-neutral-950);
border-bottom: 3px solid var(--color-neutral-600);
background-color: var(--color-neutral-800);
color: var(--color-neutral-200);
cursor: pointer;
font-family: var(--font-heading);
font-size: var(--font-md);
line-height: 0;
outline: none;
}
}

View File

@ -0,0 +1,55 @@
import { useEffect } from 'react';
import { BiPause, BiPlay, BiShuffle } from 'react-icons/bi/index';
import { useSoundStore } from '@/store';
import { usePlay } from '@/contexts/play';
import styles from './buttons.module.css';
export function Buttons() {
const { isPlaying, pause, play, toggle } = usePlay();
const noSelected = useSoundStore(state => state.noSelected());
const shuffle = useSoundStore(state => state.shuffle);
const handleClick = () => {
if (noSelected) return pause();
toggle();
};
useEffect(() => {
if (isPlaying && noSelected) pause();
}, [isPlaying, pause, noSelected]);
return (
<div className={styles.buttons}>
<button className={styles.playButton} onClick={handleClick}>
{isPlaying ? (
<>
<span>
<BiPause />
</span>{' '}
Pause
</>
) : (
<>
<span>
<BiPlay />
</span>{' '}
Play
</>
)}
</button>
<button
className={styles.shuffleButton}
onClick={() => {
shuffle();
play();
}}
>
<BiShuffle />
</button>
</div>
);
}

View File

@ -0,0 +1 @@
export { Buttons } from './buttons';

View File

@ -7,7 +7,7 @@ import { useFavoriteStore } from '@/store/favorite';
import { Container } from '@/components/container';
import { StoreConsumer } from '../store-consumer';
import { Category } from '@/components/category';
import { PlayButton } from '@/components/play-button';
import { Buttons } from '@/components/buttons';
import { PlayProvider } from '@/contexts/play';
import { sounds } from '@/data/sounds';
@ -37,7 +37,7 @@ export function Categories() {
<StoreConsumer>
<PlayProvider>
<Container>
<PlayButton />
<Buttons />
<div>
{!!favoriteSounds.length && (

View File

@ -1 +0,0 @@
export { PlayButton } from './play-button';

View File

@ -1,26 +0,0 @@
.playButton {
position: sticky;
z-index: 10;
top: 30px;
display: flex;
width: 150px;
height: 45px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid #818cf8;
border-bottom: 3px solid #4f46e5;
margin: 0 auto;
background-color: #6366f1;
color: var(--color-foreground);
cursor: pointer;
font-family: var(--font-heading);
font-size: var(--font-base);
line-height: 0;
outline: none;
& span {
font-size: var(--font-lg);
}
}

View File

@ -1,42 +0,0 @@
import { useEffect } from 'react';
import { BiPause, BiPlay } from 'react-icons/bi/index';
import { useSoundStore } from '@/store';
import { usePlay } from '@/contexts/play';
import styles from './play-button.module.css';
export function PlayButton() {
const { isPlaying, pause, toggle } = usePlay();
const noSelected = useSoundStore(state => state.noSelected());
const handleClick = () => {
if (noSelected) return pause();
toggle();
};
useEffect(() => {
if (isPlaying && noSelected) pause();
}, [isPlaying, pause, noSelected]);
return (
<button className={styles.playButton} onClick={handleClick}>
{isPlaying ? (
<>
<span>
<BiPause />
</span>{' '}
Pause
</>
) : (
<>
<span>
<BiPlay />
</span>{' '}
Play
</>
)}
</button>
);
}

26
src/helpers/random.ts Normal file
View File

@ -0,0 +1,26 @@
export function random(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
export function randomInt(min: number, max: number): number {
return Math.floor(random(min, max));
}
export function pickOne<T>(array: Array<T>): T {
const randomIndex = random(0, array.length);
return array[randomIndex];
}
export function shuffle<T>(array: Array<T>): Array<T> {
return array
.map(value => ({ sort: Math.random(), value }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}
export function pickMany<T>(array: Array<T>, count: number): Array<T> {
const shuffled = shuffle(array);
return shuffled.slice(0, count);
}

View File

@ -2,10 +2,14 @@ import type { StateCreator } from 'zustand';
import type { SoundState } from './sound.state';
import { pickMany, random } from '@/helpers/random';
export interface SoundActions {
select: (id: string) => void;
unselect: (id: string) => void;
setVolume: (id: string, volume: number) => void;
unselectAll: () => void;
shuffle: () => void;
}
export const createActions: StateCreator<
@ -33,6 +37,21 @@ export const createActions: StateCreator<
});
},
shuffle() {
get().unselectAll();
const sounds = get().sounds;
const ids = Object.keys(sounds);
const randomIDs = pickMany(ids, 4);
randomIDs.forEach(id => {
sounds[id].isSelected = true;
sounds[id].volume = random(0.2, 0.8);
});
set({ sounds });
},
unselect(id) {
set({
sounds: {
@ -41,5 +60,17 @@ export const createActions: StateCreator<
},
});
},
unselectAll() {
const sounds = get().sounds;
const ids = Object.keys(sounds);
ids.forEach(id => {
sounds[id].isSelected = false;
sounds[id].volume = 0.5;
});
set({ sounds });
},
};
};