feat: implement unselect all functionality

This commit is contained in:
MAZE 2023-10-11 20:05:23 +03:30
parent 6d02cfb134
commit 8966d59d75
6 changed files with 75 additions and 57 deletions

View file

@ -25,12 +25,16 @@
line-height: 0; line-height: 0;
outline: none; outline: none;
&:disabled {
cursor: default;
}
& span { & span {
font-size: var(--font-lg); font-size: var(--font-lg);
} }
} }
& .shuffleButton { & .smallButton {
display: flex; display: flex;
width: 45px; width: 45px;
height: 45px; height: 45px;
@ -47,5 +51,24 @@
font-size: var(--font-md); font-size: var(--font-md);
line-height: 0; line-height: 0;
outline: none; outline: none;
transition: 0.2s;
&:disabled {
cursor: default;
}
&.restore {
border-top-color: #34d399;
border-bottom-color: #047857;
background-color: #10b981;
color: var(--color-foreground);
}
&.delete {
border-top-color: #fb7185;
border-bottom-color: #be123c;
background-color: #f43f5e;
color: var(--color-foreground);
}
} }
} }

View file

@ -1,15 +1,18 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { BiPause, BiPlay, BiShuffle } from 'react-icons/bi/index'; import { BiPause, BiPlay, BiUndo, BiTrash } from 'react-icons/bi/index';
import { useSoundStore } from '@/store'; import { useSoundStore } from '@/store';
import { usePlay } from '@/contexts/play'; import { usePlay } from '@/contexts/play';
import { cn } from '@/helpers/styles';
import styles from './buttons.module.css'; import styles from './buttons.module.css';
export function Buttons() { export function Buttons() {
const { isPlaying, pause, play, toggle } = usePlay(); const { isPlaying, pause, toggle } = usePlay();
const noSelected = useSoundStore(state => state.noSelected()); const noSelected = useSoundStore(state => state.noSelected());
const shuffle = useSoundStore(state => state.shuffle); const restoreHistory = useSoundStore(state => state.restoreHistory);
const hasHistory = useSoundStore(state => !!state.history);
const unselectAll = useSoundStore(state => state.unselectAll);
const handleClick = () => { const handleClick = () => {
if (noSelected) return pause(); if (noSelected) return pause();
@ -23,7 +26,11 @@ export function Buttons() {
return ( return (
<div className={styles.buttons}> <div className={styles.buttons}>
<button className={styles.playButton} onClick={handleClick}> <button
className={styles.playButton}
disabled={noSelected}
onClick={handleClick}
>
{isPlaying ? ( {isPlaying ? (
<> <>
<span> <span>
@ -42,14 +49,20 @@ export function Buttons() {
</button> </button>
<button <button
aria-label="Shuffle Sounds" disabled={noSelected && !hasHistory}
className={styles.shuffleButton} aria-label={
hasHistory ? 'Restore Unselected Sounds' : 'Unselect All Sounds'
}
className={cn(
styles.smallButton,
hasHistory ? styles.restore : styles.delete,
)}
onClick={() => { onClick={() => {
shuffle(); if (hasHistory) restoreHistory();
play(); else if (!noSelected) unselectAll(true);
}} }}
> >
<BiShuffle /> {hasHistory ? <BiUndo /> : <BiTrash />}
</button> </button>
</div> </div>
); );

View file

@ -1,26 +0,0 @@
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

@ -12,6 +12,7 @@ export const useSoundStore = create<SoundState & SoundActions>()(
}), }),
{ {
name: 'moodist-sounds', name: 'moodist-sounds',
partialize: state => ({ sounds: state.sounds }),
skipHydration: true, skipHydration: true,
storage: createJSONStorage(() => localStorage), storage: createJSONStorage(() => localStorage),
version: 0, version: 0,

View file

@ -2,14 +2,12 @@ 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; unselectAll: (pushToHistory?: boolean) => void;
shuffle: () => void; restoreHistory: () => void;
} }
export const createActions: StateCreator< export const createActions: StateCreator<
@ -19,6 +17,14 @@ export const createActions: StateCreator<
SoundActions SoundActions
> = (set, get) => { > = (set, get) => {
return { return {
restoreHistory() {
const history = get().history;
if (!history) return;
set({ history: null, sounds: history });
},
select(id) { select(id) {
set({ set({
sounds: { sounds: {
@ -37,21 +43,6 @@ 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: {
@ -61,8 +52,18 @@ export const createActions: StateCreator<
}); });
}, },
unselectAll() { unselectAll(pushToHistory = false) {
const noSelected = get().noSelected();
if (noSelected) return;
const sounds = get().sounds; const sounds = get().sounds;
if (pushToHistory) {
const history = JSON.parse(JSON.stringify(sounds));
set({ history });
}
const ids = Object.keys(sounds); const ids = Object.keys(sounds);
ids.forEach(id => { ids.forEach(id => {

View file

@ -11,6 +11,12 @@ export interface SoundState {
volume: number; volume: number;
}; };
}; };
history: {
[id: string]: {
isSelected: boolean;
volume: number;
};
} | null;
noSelected: () => boolean; noSelected: () => boolean;
} }
@ -21,13 +27,13 @@ export const createState: StateCreator<
SoundState SoundState
> = (set, get) => { > = (set, get) => {
const state: SoundState = { const state: SoundState = {
history: null,
noSelected() { noSelected() {
const { sounds } = get(); const { sounds } = get();
const keys = Object.keys(sounds); const keys = Object.keys(sounds);
return keys.every(key => !sounds[key].isSelected); return keys.every(key => !sounds[key].isSelected);
}, },
sounds: {}, sounds: {},
}; };