mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 08:54:13 +00:00
feat: implement shuffle functionality
This commit is contained in:
parent
4124beb5b4
commit
26ba017815
9 changed files with 166 additions and 71 deletions
51
src/components/buttons/buttons.module.css
Normal file
51
src/components/buttons/buttons.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/components/buttons/buttons.tsx
Normal file
55
src/components/buttons/buttons.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/components/buttons/index.ts
Normal file
1
src/components/buttons/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { Buttons } from './buttons';
|
||||||
|
|
@ -7,7 +7,7 @@ import { useFavoriteStore } from '@/store/favorite';
|
||||||
import { Container } from '@/components/container';
|
import { Container } from '@/components/container';
|
||||||
import { StoreConsumer } from '../store-consumer';
|
import { StoreConsumer } from '../store-consumer';
|
||||||
import { Category } from '@/components/category';
|
import { Category } from '@/components/category';
|
||||||
import { PlayButton } from '@/components/play-button';
|
import { Buttons } from '@/components/buttons';
|
||||||
import { PlayProvider } from '@/contexts/play';
|
import { PlayProvider } from '@/contexts/play';
|
||||||
|
|
||||||
import { sounds } from '@/data/sounds';
|
import { sounds } from '@/data/sounds';
|
||||||
|
|
@ -37,7 +37,7 @@ export function Categories() {
|
||||||
<StoreConsumer>
|
<StoreConsumer>
|
||||||
<PlayProvider>
|
<PlayProvider>
|
||||||
<Container>
|
<Container>
|
||||||
<PlayButton />
|
<Buttons />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{!!favoriteSounds.length && (
|
{!!favoriteSounds.length && (
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { PlayButton } from './play-button';
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
26
src/helpers/random.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,14 @@ import type { StateCreator } from 'zustand';
|
||||||
|
|
||||||
import type { SoundState } from './sound.state';
|
import type { SoundState } from './sound.state';
|
||||||
|
|
||||||
|
import { pickMany, random } from '@/helpers/random';
|
||||||
|
|
||||||
export interface SoundActions {
|
export interface SoundActions {
|
||||||
select: (id: string) => void;
|
select: (id: string) => void;
|
||||||
unselect: (id: string) => void;
|
unselect: (id: string) => void;
|
||||||
setVolume: (id: string, volume: number) => void;
|
setVolume: (id: string, volume: number) => void;
|
||||||
|
unselectAll: () => void;
|
||||||
|
shuffle: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions: StateCreator<
|
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) {
|
unselect(id) {
|
||||||
set({
|
set({
|
||||||
sounds: {
|
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 });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue