diff --git a/packages/renderer/src/features/now-playing/components/play-queue.tsx b/packages/renderer/src/features/now-playing/components/play-queue.tsx index 15d2bc2aa..a920e2916 100644 --- a/packages/renderer/src/features/now-playing/components/play-queue.tsx +++ b/packages/renderer/src/features/now-playing/components/play-queue.tsx @@ -6,19 +6,20 @@ import type { RowDragEvent, } from '@ag-grid-community/core'; import '@ag-grid-community/styles/ag-theme-alpine.css'; +import { VirtualGridAutoSizerContainer, VirtualGridContainer, getColumnDefs } from '/@/components'; import { - VirtualGridAutoSizerContainer, - VirtualGridContainer, - getColumnDefs, - TableConfigDropdown, -} from '/@/components'; -import { useAppStoreActions, usePlayerStore } from '/@/store'; + useAppStoreActions, + useCurrentSong, + useDefaultQueue, + usePreviousSong, + useQueueControls, +} from '/@/store'; import { useSettingsStore } from '/@/store/settings.store'; import type { QueueSong, TableType } from '/@/types'; import { ErrorBoundary } from 'react-error-boundary'; import { mpvPlayer } from '#preload'; -import { VirtualTable } from '../../../components/virtual-table'; -import { ErrorFallback } from '../../action-required'; +import { VirtualTable } from '/@/components/virtual-table'; +import { ErrorFallback } from '/@/features/action-required'; type QueueProps = { type: TableType; @@ -26,11 +27,10 @@ type QueueProps = { export const PlayQueue = ({ type }: QueueProps) => { const gridRef = useRef(null); - const queue = usePlayerStore((state) => state.queue.default); - const reorderQueue = usePlayerStore((state) => state.reorderQueue); - const current = usePlayerStore((state) => state.getQueueData().current); - const previous = usePlayerStore((state) => state.queue.previousNode); - const setCurrentTrack = usePlayerStore((state) => state.setCurrentTrack); + const queue = useDefaultQueue(); + const { reorderQueue, setCurrentTrack } = useQueueControls(); + const currentSong = useCurrentSong(); + const previousSong = usePreviousSong(); const setSettings = useSettingsStore((state) => state.setSettings); const { setAppStore } = useAppStoreActions(); const tableConfig = useSettingsStore((state) => state.tables[type]); @@ -77,7 +77,7 @@ export const PlayQueue = ({ type }: QueueProps) => { const handleGridReady = () => { const { api } = gridRef?.current || {}; - const currentNode = api.getRowNode(current?.uniqueId); + const currentNode = api.getRowNode(currentSong?.uniqueId); api.ensureNodeVisible(currentNode, 'middle'); }; @@ -121,10 +121,10 @@ export const PlayQueue = ({ type }: QueueProps) => { const rowClassRules = useMemo(() => { return { 'current-song': (params) => { - return params.data.uniqueId === current?.uniqueId; + return params.data.uniqueId === currentSong?.uniqueId; }, }; - }, [current?.uniqueId]); + }, [currentSong?.uniqueId]); // Redraw the current song row when the previous song changes useEffect(() => { @@ -134,8 +134,8 @@ export const PlayQueue = ({ type }: QueueProps) => { return; } - const currentNode = api.getRowNode(current?.uniqueId); - const previousNode = api.getRowNode(previous?.uniqueId); + const currentNode = api.getRowNode(currentSong?.uniqueId); + const previousNode = api.getRowNode(previousSong?.uniqueId); const rowNodes = [currentNode, previousNode]; @@ -146,7 +146,7 @@ export const PlayQueue = ({ type }: QueueProps) => { } } } - }, [current, previous, tableConfig.followCurrentSong]); + }, [currentSong, previousSong, tableConfig.followCurrentSong]); // Auto resize the columns when the column config changes useEffect(() => { @@ -198,7 +198,7 @@ export const PlayQueue = ({ type }: QueueProps) => { /> - + {/* */} ); }; diff --git a/packages/renderer/src/features/player/components/center-controls.tsx b/packages/renderer/src/features/player/components/center-controls.tsx index 53132b959..f1ab7b5a7 100644 --- a/packages/renderer/src/features/player/components/center-controls.tsx +++ b/packages/renderer/src/features/player/components/center-controls.tsx @@ -14,7 +14,15 @@ import { } from 'react-icons/ri'; import styled from 'styled-components'; import { Text } from '/@/components'; -import { usePlayerStore } from '/@/store'; +import { + useCurrentPlayer, + useCurrentSong, + useCurrentStatus, + useCurrentTime, + useRepeatStatus, + useSetCurrentTime, + useShuffleStatus, +} from '/@/store'; import { useSettingsStore } from '/@/store/settings.store'; import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/types'; import { useCenterControls } from '../hooks/use-center-controls'; @@ -58,15 +66,17 @@ const SliderWrapper = styled.div` export const CenterControls = ({ playersRef }: CenterControlsProps) => { const [isSeeking, setIsSeeking] = useState(false); - const songDuration = usePlayerStore((state) => state.current.song?.duration); + const currentSong = useCurrentSong(); + const songDuration = currentSong?.duration; const skip = useSettingsStore((state) => state.player.skipButtons); const playerType = useSettingsStore((state) => state.player.type); const player1 = playersRef?.current?.player1; const player2 = playersRef?.current?.player2; - const { status, player } = usePlayerStore((state) => state.current); - const setCurrentTime = usePlayerStore((state) => state.setCurrentTime); - const repeat = usePlayerStore((state) => state.repeat); - const shuffle = usePlayerStore((state) => state.shuffle); + const status = useCurrentStatus(); + const player = useCurrentPlayer(); + const setCurrentTime = useSetCurrentTime(); + const repeat = useRepeatStatus(); + const shuffle = useShuffleStatus(); const { handleNextTrack, @@ -79,7 +89,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { handleToggleShuffle, } = useCenterControls({ playersRef }); - const currentTime = usePlayerStore((state) => state.current.time); + const currentTime = useCurrentTime(); const currentPlayerRef = player === 1 ? player1 : player2; const duration = format((songDuration || 0) * 1000); const formattedTime = format(currentTime * 1000 || 0); diff --git a/packages/renderer/src/features/player/components/left-controls.tsx b/packages/renderer/src/features/player/components/left-controls.tsx index d4f74ab42..324484123 100644 --- a/packages/renderer/src/features/player/components/left-controls.tsx +++ b/packages/renderer/src/features/player/components/left-controls.tsx @@ -6,7 +6,7 @@ import { generatePath, Link } from 'react-router-dom'; import styled from 'styled-components'; import { Button, Text } from '/@/components'; import { AppRoute } from '/@/router/routes'; -import { useAppStore, useAppStoreActions, usePlayerStore } from '/@/store'; +import { useAppStore, useAppStoreActions, useCurrentSong } from '/@/store'; import { fadeIn } from '/@/styles'; const LeftControlsContainer = styled.div` @@ -72,9 +72,9 @@ const LineItem = styled.div<{ $secondary?: boolean }>` export const LeftControls = () => { const { setSidebar } = useAppStoreActions(); const hideImage = useAppStore((state) => state.sidebar.image); - const song = usePlayerStore((state) => state.current.song); - const title = song?.name; - const artists = song?.artists; + const currentSong = useCurrentSong(); + const title = currentSong?.name; + const artists = currentSong?.artists; return ( @@ -93,10 +93,10 @@ export const LeftControls = () => { to={AppRoute.NOW_PLAYING} transition={{ duration: 0.3, ease: 'easeInOut' }} > - {song?.imageUrl ? ( + {currentSong?.imageUrl ? ( ) : ( <> @@ -190,15 +190,15 @@ export const LeftControls = () => { overflow="hidden" size="xs" to={ - song?.albumId + currentSong?.albumId ? generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { - albumId: song.albumId, + albumId: currentSong.albumId, }) : '' } weight={500} > - {song?.album || '—'} + {currentSong?.album || '—'} diff --git a/packages/renderer/src/features/player/components/playerbar.tsx b/packages/renderer/src/features/player/components/playerbar.tsx index 06f340e36..d2b2b92cc 100644 --- a/packages/renderer/src/features/player/components/playerbar.tsx +++ b/packages/renderer/src/features/player/components/playerbar.tsx @@ -2,8 +2,15 @@ import { useRef } from 'react'; import styled from 'styled-components'; import { useSettingsStore } from '/@/store/settings.store'; import { PlaybackType } from '/@/types'; -import { AudioPlayer } from '../../../components'; -import { usePlayerStore } from '../../../store'; +import { AudioPlayer } from '/@/components'; +import { + useCurrentPlayer, + useCurrentStatus, + usePlayer1Data, + usePlayer2Data, + usePlayerControls, + useVolume, +} from '/@/store'; import { CenterControls } from './center-controls'; import { LeftControls } from './left-controls'; import { RightControls } from './right-controls'; @@ -46,12 +53,12 @@ const CenterGridItem = styled.div` export const Playerbar = () => { const playersRef = useRef(); const settings = useSettingsStore((state) => state.player); - const volume = usePlayerStore((state) => state.volume); - const player1 = usePlayerStore((state) => state.player1()); - const player2 = usePlayerStore((state) => state.player2()); - const status = usePlayerStore((state) => state.current.status); - const player = usePlayerStore((state) => state.current.player); - const autoNext = usePlayerStore((state) => state.autoNext); + const volume = useVolume(); + const player1 = usePlayer1Data(); + const player2 = usePlayer2Data(); + const status = useCurrentStatus(); + const player = useCurrentPlayer(); + const { autoNext } = usePlayerControls(); return ( diff --git a/packages/renderer/src/features/player/components/right-controls.tsx b/packages/renderer/src/features/player/components/right-controls.tsx index 8d580d460..1f985d5dc 100644 --- a/packages/renderer/src/features/player/components/right-controls.tsx +++ b/packages/renderer/src/features/player/components/right-controls.tsx @@ -2,7 +2,7 @@ import { Group } from '@mantine/core'; import { HiOutlineQueueList } from 'react-icons/hi2'; import { RiVolumeUpFill, RiVolumeDownFill, RiVolumeMuteFill } from 'react-icons/ri'; import styled from 'styled-components'; -import { usePlayerStore, useAppStoreActions, useSidebarStore } from '/@/store'; +import { useAppStoreActions, useMuted, useSidebarStore, useVolume } from '/@/store'; import { useRightControls } from '../hooks/use-right-controls'; import { PlayerButton } from './player-button'; import { Slider } from './slider'; @@ -33,8 +33,8 @@ const MetadataStack = styled.div` `; export const RightControls = () => { - const volume = usePlayerStore((state) => state.volume); - const muted = usePlayerStore((state) => state.muted); + const volume = useVolume(); + const muted = useMuted(); const { setSidebar } = useAppStoreActions(); const { rightExpanded: isQueueExpanded } = useSidebarStore(); const { handleVolumeSlider, handleVolumeSliderState, handleMute } = useRightControls(); diff --git a/packages/renderer/src/features/player/hooks/use-center-controls.ts b/packages/renderer/src/features/player/hooks/use-center-controls.ts index 8330fb814..5ee92d598 100644 --- a/packages/renderer/src/features/player/hooks/use-center-controls.ts +++ b/packages/renderer/src/features/player/hooks/use-center-controls.ts @@ -2,30 +2,31 @@ import { useCallback, useEffect } from 'react'; import isElectron from 'is-electron'; import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/types'; import { mpvPlayer, mpvPlayerListener, ipc } from '#preload'; -import { usePlayerStore } from '../../../store'; -import { useSettingsStore } from '../../../store/settings.store'; +import { + useCurrentPlayer, + useCurrentStatus, + useDefaultQueue, + usePlayerControls, + usePlayerStore, + useRepeatStatus, + useSetCurrentTime, + useShuffleStatus, +} from '/@/store'; +import { useSettingsStore } from '/@/store/settings.store'; export const useCenterControls = (args: { playersRef: any }) => { const { playersRef } = args; const settings = useSettingsStore((state) => state.player); - const setShuffle = usePlayerStore((state) => state.setShuffle); - const setRepeat = usePlayerStore((state) => state.setRepeat); - const play = usePlayerStore((state) => state.play); - const pause = usePlayerStore((state) => state.pause); - const prev = usePlayerStore((state) => state.prev); - const next = usePlayerStore((state) => state.next); - const setCurrentIndex = usePlayerStore((state) => state.setCurrentIndex); - const autoNext = usePlayerStore((state) => state.autoNext); - const queue = usePlayerStore((state) => state.queue.default); - const playerStatus = usePlayerStore((state) => state.current.status); - const currentPlayer = usePlayerStore((state) => state.current.player); - const repeat = usePlayerStore((state) => state.repeat); - const shuffle = usePlayerStore((state) => state.shuffle); + const currentPlayer = useCurrentPlayer(); + const { setShuffle, setRepeat, play, pause, previous, next, setCurrentIndex, autoNext } = + usePlayerControls(); + const setCurrentTime = useSetCurrentTime(); + const queue = useDefaultQueue(); + const playerStatus = useCurrentStatus(); + const repeatStatus = useRepeatStatus(); + const shuffleStatus = useShuffleStatus(); const playerType = useSettingsStore((state) => state.player.type); - - const setCurrentTime = usePlayerStore((state) => state.setCurrentTime); - const player1Ref = playersRef?.current?.player1; const player2Ref = playersRef?.current?.player2; const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref; @@ -87,35 +88,35 @@ export const useCenterControls = (args: { playersRef: any }) => { }, [isMpvPlayer, pause, setCurrentTime, stopPlayback]); const handleToggleShuffle = useCallback(() => { - if (shuffle === PlayerShuffle.NONE) { + if (shuffleStatus === PlayerShuffle.NONE) { const playerData = setShuffle(PlayerShuffle.TRACK); return mpvPlayer.setQueueNext(playerData); } const playerData = setShuffle(PlayerShuffle.NONE); return mpvPlayer.setQueueNext(playerData); - }, [setShuffle, shuffle]); + }, [setShuffle, shuffleStatus]); const handleToggleRepeat = useCallback(() => { - if (repeat === PlayerRepeat.NONE) { + if (repeatStatus === PlayerRepeat.NONE) { const playerData = setRepeat(PlayerRepeat.ALL); return mpvPlayer.setQueueNext(playerData); } - if (repeat === PlayerRepeat.ALL) { + if (repeatStatus === PlayerRepeat.ALL) { const playerData = setRepeat(PlayerRepeat.ONE); return mpvPlayer.setQueueNext(playerData); } return setRepeat(PlayerRepeat.NONE); - }, [repeat, setRepeat]); + }, [repeatStatus, setRepeat]); const checkIsLastTrack = useCallback(() => { - return usePlayerStore.getState().checkIsLastTrack(); + return usePlayerStore.getState().actions.checkIsLastTrack(); }, []); const checkIsFirstTrack = useCallback(() => { - return usePlayerStore.getState().checkIsFirstTrack(); + return usePlayerStore.getState().actions.checkIsFirstTrack(); }, []); const handleAutoNext = useCallback(() => { @@ -172,7 +173,7 @@ export const useCenterControls = (args: { playersRef: any }) => { }, }; - switch (repeat) { + switch (repeatStatus) { case PlayerRepeat.NONE: handleRepeatNone[playerType](); break; @@ -186,7 +187,16 @@ export const useCenterControls = (args: { playersRef: any }) => { default: break; } - }, [autoNext, checkIsLastTrack, pause, play, playerType, repeat, resetPlayers, setCurrentIndex]); + }, [ + autoNext, + checkIsLastTrack, + pause, + play, + playerType, + repeatStatus, + resetPlayers, + setCurrentIndex, + ]); const handleNextTrack = useCallback(() => { const isLastTrack = checkIsLastTrack(); @@ -240,7 +250,7 @@ export const useCenterControls = (args: { playersRef: any }) => { }, }; - switch (repeat) { + switch (repeatStatus) { case PlayerRepeat.NONE: handleRepeatNone[playerType](); break; @@ -261,7 +271,7 @@ export const useCenterControls = (args: { playersRef: any }) => { next, pause, playerType, - repeat, + repeatStatus, resetPlayers, setCurrentIndex, setCurrentTime, @@ -285,7 +295,7 @@ export const useCenterControls = (args: { playersRef: any }) => { const handleRepeatAll = { local: () => { if (!isFirstTrack) { - const playerData = prev(); + const playerData = previous(); mpvPlayer.setQueue(playerData); mpvPlayer.previous(); } else { @@ -299,7 +309,7 @@ export const useCenterControls = (args: { playersRef: any }) => { setCurrentIndex(queue.length - 1); resetPlayers(); } else { - prev(); + previous(); resetPlayers(); } }, @@ -307,7 +317,7 @@ export const useCenterControls = (args: { playersRef: any }) => { const handleRepeatNone = { local: () => { - const playerData = prev(); + const playerData = previous(); mpvPlayer.setQueue(playerData); mpvPlayer.previous(); }, @@ -316,7 +326,7 @@ export const useCenterControls = (args: { playersRef: any }) => { resetPlayers(); pause(); } else { - prev(); + previous(); resetPlayers(); } }, @@ -325,7 +335,7 @@ export const useCenterControls = (args: { playersRef: any }) => { const handleRepeatOne = { local: () => { if (!isFirstTrack) { - const playerData = prev(); + const playerData = previous(); mpvPlayer.setQueue(playerData); mpvPlayer.previous(); } else { @@ -333,12 +343,12 @@ export const useCenterControls = (args: { playersRef: any }) => { } }, web: () => { - prev(); + previous(); resetPlayers(); }, }; - switch (repeat) { + switch (repeatStatus) { case PlayerRepeat.NONE: handleRepeatNone[playerType](); break; @@ -360,9 +370,9 @@ export const useCenterControls = (args: { playersRef: any }) => { isMpvPlayer, pause, playerType, - prev, + previous, queue.length, - repeat, + repeatStatus, resetPlayers, setCurrentIndex, setCurrentTime, @@ -489,7 +499,7 @@ export const useCenterControls = (args: { playersRef: any }) => { next, pause, play, - prev, + previous, setCurrentTime, ]); diff --git a/packages/renderer/src/features/player/hooks/use-right-controls.ts b/packages/renderer/src/features/player/hooks/use-right-controls.ts index 2931bd248..73c5d6a72 100644 --- a/packages/renderer/src/features/player/hooks/use-right-controls.ts +++ b/packages/renderer/src/features/player/hooks/use-right-controls.ts @@ -1,13 +1,12 @@ import { useEffect } from 'react'; import isElectron from 'is-electron'; import { mpvPlayer } from '#preload'; -import { usePlayerStore } from '../../../store'; +import { useMuted, usePlayerControls, useVolume } from '/@/store'; export const useRightControls = () => { - const setVolume = usePlayerStore((state) => state.setVolume); - const volume = usePlayerStore((state) => state.volume); - const muted = usePlayerStore((state) => state.muted); - const setMuted = usePlayerStore((state) => state.setMuted); + const { setVolume, setMuted } = usePlayerControls(); + const volume = useVolume(); + const muted = useMuted(); // Ensure that the mpv player volume is set on startup useEffect(() => { diff --git a/packages/renderer/src/features/player/utils/handle-playqueue-add.ts b/packages/renderer/src/features/player/utils/handle-playqueue-add.ts index be70045d6..66a203461 100644 --- a/packages/renderer/src/features/player/utils/handle-playqueue-add.ts +++ b/packages/renderer/src/features/player/utils/handle-playqueue-add.ts @@ -46,7 +46,7 @@ export const handlePlayQueueAdd = async (options: PlayQueueAddOptions) => { if (!songs) return; - const playerData = usePlayerStore.getState().addToQueue(songs, options.play); + const playerData = usePlayerStore.getState().actions.addToQueue(songs, options.play); if (options.play === Play.NEXT || options.play === Play.LAST) { if (playerType === PlaybackType.LOCAL) { @@ -60,7 +60,7 @@ export const handlePlayQueueAdd = async (options: PlayQueueAddOptions) => { mpvPlayer.play(); } - usePlayerStore.getState().play(); + usePlayerStore.getState().actions.play(); } } }; diff --git a/packages/renderer/src/features/settings/components/playback-tab.tsx b/packages/renderer/src/features/settings/components/playback-tab.tsx index 6969e9987..2aa977f48 100644 --- a/packages/renderer/src/features/settings/components/playback-tab.tsx +++ b/packages/renderer/src/features/settings/components/playback-tab.tsx @@ -16,7 +16,7 @@ import { } from '/@/components'; import { mpvPlayer } from '#preload'; import { SettingsOptions } from '/@/features/settings/components/settings-option'; -import { usePlayerStore } from '/@/store'; +import { useCurrentStatus, usePlayerStore } from '/@/store'; import { useSettingsStore } from '/@/store/settings.store'; import { Play, PlaybackStyle, PlaybackType, PlayerStatus, CrossfadeStyle } from '/@/types'; import { localSettings } from '#preload'; @@ -29,7 +29,7 @@ const getAudioDevice = async () => { export const PlaybackTab = () => { const settings = useSettingsStore((state) => state.player); const update = useSettingsStore((state) => state.setSettings); - const status = usePlayerStore((state) => state.current.status); + const status = useCurrentStatus(); const [audioDevices, setAudioDevices] = useState([]); const [mpvPath, setMpvPath] = useState(''); const [mpvParameters, setMpvParameters] = useState(''); @@ -83,7 +83,7 @@ export const PlaybackTab = () => { onChange={(e) => { update({ player: { ...settings, type: e as PlaybackType } }); if (isElectron() && e === PlaybackType.LOCAL) { - const queueData = usePlayerStore.getState().getPlayerData(); + const queueData = usePlayerStore.getState().actions.getPlayerData(); mpvPlayer.setQueue(queueData); } }} diff --git a/packages/renderer/src/features/sidebar/components/sidebar.tsx b/packages/renderer/src/features/sidebar/components/sidebar.tsx index b727f83a7..ef67886bb 100644 --- a/packages/renderer/src/features/sidebar/components/sidebar.tsx +++ b/packages/renderer/src/features/sidebar/components/sidebar.tsx @@ -21,7 +21,7 @@ import { useNavigate, Link } from 'react-router-dom'; import styled from 'styled-components'; import { Button, TextInput } from '/@/components'; import { AppRoute } from '/@/router/routes'; -import { useAppStoreActions, usePlayerStore, useSidebarStore } from '/@/store'; +import { useAppStoreActions, useCurrentSong, useSidebarStore } from '/@/store'; import { fadeIn } from '/@/styles'; import { SidebarItem } from './sidebar-item'; @@ -62,7 +62,7 @@ export const Sidebar = () => { const navigate = useNavigate(); const sidebar = useSidebarStore(); const { setSidebar } = useAppStoreActions(); - const imageUrl = usePlayerStore((state) => state.current?.song?.imageUrl); + const imageUrl = useCurrentSong()?.imageUrl; const showImage = sidebar.image; diff --git a/packages/renderer/src/store/player.store.ts b/packages/renderer/src/store/player.store.ts index 1b16a9661..827cd6bf3 100644 --- a/packages/renderer/src/store/player.store.ts +++ b/packages/renderer/src/store/player.store.ts @@ -1,9 +1,8 @@ -/* eslint-disable prefer-destructuring */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import map from 'lodash/map'; import shuffle from 'lodash/shuffle'; import { nanoid } from 'nanoid/non-secure'; import create from 'zustand'; +import shallow from 'zustand/shallow'; import { devtools, persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import type { Song } from '/@/api/types'; @@ -55,180 +54,585 @@ export interface QueueData { } export interface PlayerSlice extends PlayerState { - addToQueue: (songs: Song[], type: Play) => PlayerData; - autoNext: () => PlayerData; - checkIsFirstTrack: () => boolean; - checkIsLastTrack: () => boolean; - // getNextTrack: () => QueueSong; - // getPreviousTrack: () => QueueSong; - getPlayerData: () => PlayerData; - getQueueData: () => QueueData; - next: () => PlayerData; - pause: () => void; - play: () => void; - player1: () => QueueSong | undefined; - player2: () => QueueSong | undefined; - prev: () => PlayerData; - reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => PlayerData; - setCurrentIndex: (index: number) => PlayerData; - setCurrentTime: (time: number) => void; - setCurrentTrack: (uniqueId: string) => PlayerData; - setMuted: (muted: boolean) => void; - setRepeat: (type: PlayerRepeat) => PlayerData; - setShuffle: (type: PlayerShuffle) => PlayerData; - setShuffledIndex: (index: number) => PlayerData; - setStore: (data: Partial) => void; - setVolume: (volume: number) => void; + actions: { + addToQueue: (songs: Song[], type: Play) => PlayerData; + autoNext: () => PlayerData; + checkIsFirstTrack: () => boolean; + checkIsLastTrack: () => boolean; + // getNextTrack: () => QueueSong; + // getPreviousTrack: () => QueueSong; + getPlayerData: () => PlayerData; + getQueueData: () => QueueData; + next: () => PlayerData; + pause: () => void; + play: () => void; + player1: () => QueueSong | undefined; + player2: () => QueueSong | undefined; + previous: () => PlayerData; + reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => PlayerData; + setCurrentIndex: (index: number) => PlayerData; + setCurrentTime: (time: number) => void; + setCurrentTrack: (uniqueId: string) => PlayerData; + setMuted: (muted: boolean) => void; + setRepeat: (type: PlayerRepeat) => PlayerData; + setShuffle: (type: PlayerShuffle) => PlayerData; + setShuffledIndex: (index: number) => PlayerData; + setStore: (data: Partial) => void; + setVolume: (volume: number) => void; + }; } export const usePlayerStore = create()( persist( devtools( immer((set, get) => ({ - addToQueue: (songs, type) => { - const shuffledIndex = get().current.shuffledIndex; - const shuffledQueue = get().queue.shuffled; - const queueSongs = map(songs, (song) => ({ - ...song, - uniqueId: nanoid(), - })); + actions: { + addToQueue: (songs, type) => { + const shuffledIndex = get().current.shuffledIndex; + const shuffledQueue = get().queue.shuffled; + const queueSongs = map(songs, (song) => ({ + ...song, + uniqueId: nanoid(), + })); - if (type === Play.NOW) { + if (type === Play.NOW) { + if (get().shuffle === PlayerShuffle.TRACK) { + const shuffledSongs = shuffle(queueSongs); + const foundIndex = queueSongs.findIndex( + (song) => song.uniqueId === shuffledSongs[0].uniqueId, + ); + set((state) => { + state.queue.shuffled = shuffledSongs.map((song) => song.uniqueId); + }); + + set((state) => { + state.queue.default = queueSongs; + state.current.time = 0; + state.current.player = 1; + state.current.index = foundIndex; + state.current.shuffledIndex = 0; + state.current.song = shuffledSongs[0]; + }); + } else { + set((state) => { + state.queue.default = queueSongs; + state.current.time = 0; + state.current.player = 1; + state.current.index = 0; + state.current.shuffledIndex = 0; + state.current.song = queueSongs[0]; + }); + } + } else if (type === Play.LAST) { + // Shuffle the queue after the current track + const shuffledQueueWithNewSongs = + get().shuffle === PlayerShuffle.TRACK + ? [ + ...shuffledQueue.slice(0, shuffledIndex + 1), + ...shuffle([ + ...queueSongs.map((song) => song.uniqueId), + ...shuffledQueue.slice(shuffledIndex + 1), + ]), + ] + : []; + + set((state) => { + state.queue.default = [...get().queue.default, ...queueSongs]; + state.queue.shuffled = shuffledQueueWithNewSongs; + }); + } else if (type === Play.NEXT) { + const queue = get().queue.default; + const currentIndex = get().current.index; + + // Shuffle the queue after the current track + const shuffledQueueWithNewSongs = + get().shuffle === PlayerShuffle.TRACK + ? [ + ...shuffledQueue.slice(0, shuffledIndex + 1), + ...shuffle([ + ...queueSongs.map((song) => song.uniqueId), + ...shuffledQueue.slice(shuffledIndex + 1), + ]), + ] + : []; + + set((state) => { + state.queue.default = [ + ...queue.slice(0, currentIndex + 1), + ...queueSongs, + ...queue.slice(currentIndex + 1), + ]; + state.queue.shuffled = shuffledQueueWithNewSongs; + }); + } + + return get().actions.getPlayerData(); + }, + autoNext: () => { + const isLastTrack = get().actions.checkIsLastTrack(); + const repeat = get().repeat; + + if (repeat === PlayerRepeat.ONE) { + const nextIndex = get().current.index; + + set((state) => { + state.current.time = 0; + state.current.index = nextIndex; + state.current.shuffledIndex = get().current.shuffledIndex; + state.current.player = state.current.player === 1 ? 2 : 1; + state.current.song = get().queue.default[nextIndex]; + state.queue.previousNode = get().current.song; + }); + } else if (get().shuffle === PlayerShuffle.TRACK) { + const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1; + + const nextSong = get().queue.default.find( + (song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex], + ); + + const nextSongIndex = get().queue.default.findIndex( + (song) => song.uniqueId === nextSong!.uniqueId, + ); + + set((state) => { + state.current.time = 0; + state.current.index = nextSongIndex!; + state.current.shuffledIndex = nextShuffleIndex; + state.current.player = state.current.player === 1 ? 2 : 1; + state.current.song = nextSong!; + state.queue.previousNode = get().current.song; + }); + } else { + const nextIndex = isLastTrack ? 0 : get().current.index + 1; + + set((state) => { + state.current.time = 0; + state.current.index = nextIndex; + state.current.player = state.current.player === 1 ? 2 : 1; + state.current.song = get().queue.default[nextIndex]; + state.queue.previousNode = get().current.song; + }); + } + + return get().actions.getPlayerData(); + }, + checkIsFirstTrack: () => { + const currentIndex = + get().shuffle === PlayerShuffle.TRACK + ? get().current.shuffledIndex + : get().current.index; + + return currentIndex === 0; + }, + checkIsLastTrack: () => { + const currentIndex = + get().shuffle === PlayerShuffle.TRACK + ? get().current.shuffledIndex + : get().current.index; + + return currentIndex === get().queue.default.length - 1; + }, + getPlayerData: () => { + const queue = get().queue.default; + const currentPlayer = get().current.player; + const repeat = get().repeat; + const isLastTrack = get().actions.checkIsLastTrack(); + const isFirstTrack = get().actions.checkIsFirstTrack(); + + let player1; + let player2; if (get().shuffle === PlayerShuffle.TRACK) { - const shuffledSongs = shuffle(queueSongs); - const foundIndex = queueSongs.findIndex( - (song) => song.uniqueId === shuffledSongs[0].uniqueId, + const shuffledQueue = get().queue.shuffled; + const shuffledIndex = get().current.shuffledIndex; + const current = queue.find( + (song) => song.uniqueId === shuffledQueue[shuffledIndex], + ) as QueueSong; + + let nextSongIndex: number | undefined; + let previousSongIndex: number | undefined; + if (repeat === PlayerRepeat.ALL) { + if (isLastTrack) nextSongIndex = 0; + else nextSongIndex = shuffledIndex + 1; + + if (isFirstTrack) previousSongIndex = queue.length - 1; + else previousSongIndex = shuffledIndex - 1; + } + + if (repeat === PlayerRepeat.ONE) { + nextSongIndex = shuffledIndex; + previousSongIndex = shuffledIndex; + } + + if (repeat === PlayerRepeat.NONE) { + if (isLastTrack) nextSongIndex = undefined; + else nextSongIndex = shuffledIndex + 1; + + if (isFirstTrack) previousSongIndex = undefined; + else previousSongIndex = shuffledIndex - 1; + } + + const next = nextSongIndex + ? (queue.find( + (song) => song.uniqueId === shuffledQueue[nextSongIndex as number], + ) as QueueSong) + : undefined; + + const previous = queue.find( + (song) => song.uniqueId === shuffledQueue[shuffledIndex - 1], + ) as QueueSong; + + player1 = currentPlayer === 1 ? current : next; + player2 = currentPlayer === 1 ? next : current; + + return { + current: { + index: get().current.index, + nextIndex: nextSongIndex, + player: get().current.player, + previousIndex: previousSongIndex, + shuffledIndex: get().current.shuffledIndex, + song: get().current.song, + status: get().current.status, + }, + player1, + player2, + queue: { + current, + next, + previous, + }, + }; + } + + const currentIndex = get().current.index; + + let nextSongIndex; + let previousSongIndex; + if (repeat === PlayerRepeat.ALL) { + if (isLastTrack) nextSongIndex = 0; + else nextSongIndex = currentIndex + 1; + + if (isFirstTrack) previousSongIndex = queue.length - 1; + else previousSongIndex = currentIndex - 1; + } + + if (repeat === PlayerRepeat.ONE) { + nextSongIndex = currentIndex; + previousSongIndex = currentIndex; + } + + if (repeat === PlayerRepeat.NONE) { + if (isLastTrack) nextSongIndex = undefined; + else nextSongIndex = currentIndex + 1; + + if (isFirstTrack) previousSongIndex = undefined; + else previousSongIndex = currentIndex - 1; + } + + player1 = + currentPlayer === 1 + ? queue[currentIndex] + : nextSongIndex !== undefined + ? queue[nextSongIndex] + : undefined; + + player2 = + currentPlayer === 1 + ? nextSongIndex !== undefined + ? queue[nextSongIndex] + : undefined + : queue[currentIndex]; + + return { + current: { + index: currentIndex, + nextIndex: nextSongIndex, + player: get().current.player, + previousIndex: previousSongIndex, + shuffledIndex: get().current.shuffledIndex, + song: get().current.song, + status: get().current.status, + }, + player1, + player2, + queue: { + current: queue[currentIndex], + next: nextSongIndex !== undefined ? queue[nextSongIndex] : undefined, + previous: queue[currentIndex - 1], + }, + }; + }, + getQueueData: () => { + const queue = get().queue.default; + return { + current: queue[get().current.index], + next: queue[get().current.index + 1], + previous: queue[get().current.index - 1], + }; + }, + next: () => { + const isLastTrack = get().actions.checkIsLastTrack(); + const repeat = get().repeat; + + if (get().shuffle === PlayerShuffle.TRACK) { + const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1; + + const nextSong = get().queue.default.find( + (song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex], + ); + + const nextSongIndex = get().queue.default.findIndex( + (song) => song.uniqueId === nextSong?.uniqueId, + ); + + set((state) => { + state.current.time = 0; + state.current.index = nextSongIndex!; + state.current.shuffledIndex = nextShuffleIndex; + state.current.player = 1; + state.current.song = nextSong!; + state.queue.previousNode = get().current.song; + }); + } else { + const nextIndex = + repeat === PlayerRepeat.ALL + ? isLastTrack + ? 0 + : get().current.index + 1 + : isLastTrack + ? get().current.index + : get().current.index + 1; + + set((state) => { + state.current.time = 0; + state.current.index = nextIndex; + state.current.player = 1; + state.current.song = get().queue.default[nextIndex]; + state.queue.previousNode = get().current.song; + }); + } + + return get().actions.getPlayerData(); + }, + pause: () => { + set((state) => { + state.current.status = PlayerStatus.PAUSED; + }); + }, + play: () => { + set((state) => { + state.current.status = PlayerStatus.PLAYING; + }); + }, + player1: () => { + return get().actions.getPlayerData().player1; + }, + player2: () => { + return get().actions.getPlayerData().player2; + }, + previous: () => { + const isFirstTrack = get().actions.checkIsFirstTrack(); + const repeat = get().repeat; + + if (get().shuffle === PlayerShuffle.TRACK) { + const prevShuffleIndex = isFirstTrack ? 0 : get().current.shuffledIndex - 1; + + const prevSong = get().queue.default.find( + (song) => song.uniqueId === get().queue.shuffled[prevShuffleIndex], + ); + + const prevIndex = get().queue.default.findIndex( + (song) => song.uniqueId === prevSong?.uniqueId, + ); + + set((state) => { + state.current.time = 0; + state.current.index = prevIndex!; + state.current.shuffledIndex = prevShuffleIndex; + state.current.player = 1; + state.current.song = prevSong!; + state.queue.previousNode = get().current.song; + }); + } else { + let prevIndex: number; + if (repeat === PlayerRepeat.ALL) { + prevIndex = isFirstTrack ? get().queue.default.length - 1 : get().current.index - 1; + } else { + prevIndex = isFirstTrack ? 0 : get().current.index - 1; + } + + set((state) => { + state.current.time = 0; + state.current.index = prevIndex; + state.current.player = 1; + state.current.song = state.queue.default[state.current.index]; + state.queue.previousNode = get().current.song; + }); + } + + return get().actions.getPlayerData(); + }, + reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => { + // Don't move if dropping on top of a selected row + if (afterUniqueId && rowUniqueIds.includes(afterUniqueId)) { + return get().actions.getPlayerData(); + } + + const queue = get().queue.default; + const currentSongUniqueId = get().current.song?.uniqueId; + const queueWithoutSelectedRows = queue.filter( + (song) => !rowUniqueIds.includes(song.uniqueId), + ); + + const moveBeforeIndex = queueWithoutSelectedRows.findIndex( + (song) => song.uniqueId === afterUniqueId, + ); + + // AG-Grid does not provide node data when a row is moved to the bottom of the list + const reorderedQueue = afterUniqueId + ? [ + ...queueWithoutSelectedRows.slice(0, moveBeforeIndex), + ...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)), + ...queueWithoutSelectedRows.slice(moveBeforeIndex), + ] + : [ + ...queueWithoutSelectedRows, + ...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)), + ]; + + const currentSongIndex = reorderedQueue.findIndex( + (song) => song.uniqueId === currentSongUniqueId, + ); + + set({ + current: { ...get().current, index: currentSongIndex }, + queue: { ...get().queue, default: reorderedQueue }, + }); + + return get().actions.getPlayerData(); + }, + setCurrentIndex: (index) => { + if (get().shuffle === PlayerShuffle.TRACK) { + const foundSong = get().queue.default.find( + (song) => song.uniqueId === get().queue.shuffled[index], + ); + const foundIndex = get().queue.default.findIndex( + (song) => song.uniqueId === foundSong?.uniqueId, ); set((state) => { - state.queue.shuffled = shuffledSongs.map((song) => song.uniqueId); - }); - - set((state) => { - state.queue.default = queueSongs; state.current.time = 0; + state.current.index = foundIndex!; + state.current.shuffledIndex = index; state.current.player = 1; - state.current.index = foundIndex; - state.current.shuffledIndex = 0; - state.current.song = shuffledSongs[0]; + state.current.song = foundSong!; + state.queue.previousNode = get().current.song; }); } else { set((state) => { - state.queue.default = queueSongs; state.current.time = 0; + state.current.index = index; state.current.player = 1; - state.current.index = 0; - state.current.shuffledIndex = 0; - state.current.song = queueSongs[0]; + state.current.song = state.queue.default[index]; + state.queue.previousNode = get().current.song; }); } - } else if (type === Play.LAST) { - // Shuffle the queue after the current track - const shuffledQueueWithNewSongs = - get().shuffle === PlayerShuffle.TRACK - ? [ - ...shuffledQueue.slice(0, shuffledIndex + 1), - ...shuffle([ - ...queueSongs.map((song) => song.uniqueId), - ...shuffledQueue.slice(shuffledIndex + 1), - ]), - ] - : []; + return get().actions.getPlayerData(); + }, + setCurrentTime: (time) => { set((state) => { - state.queue.default = [...get().queue.default, ...queueSongs]; - state.queue.shuffled = shuffledQueueWithNewSongs; + state.current.time = time; }); - } else if (type === Play.NEXT) { - const queue = get().queue.default; - const currentIndex = get().current.index; + }, + setCurrentTrack: (uniqueId) => { + if (get().shuffle === PlayerShuffle.TRACK) { + const defaultIndex = get().queue.default.findIndex( + (song) => song.uniqueId === uniqueId, + ); - // Shuffle the queue after the current track - const shuffledQueueWithNewSongs = - get().shuffle === PlayerShuffle.TRACK - ? [ - ...shuffledQueue.slice(0, shuffledIndex + 1), - ...shuffle([ - ...queueSongs.map((song) => song.uniqueId), - ...shuffledQueue.slice(shuffledIndex + 1), - ]), - ] - : []; + const shuffledIndex = get().queue.shuffled.findIndex((id) => id === uniqueId); + set((state) => { + state.current.time = 0; + state.current.index = defaultIndex; + state.current.shuffledIndex = shuffledIndex; + state.current.player = 1; + state.current.song = state.queue.default[defaultIndex]; + state.queue.previousNode = get().current.song; + }); + } else { + const defaultIndex = get().queue.default.findIndex( + (song) => song.uniqueId === uniqueId, + ); + + set((state) => { + state.current.time = 0; + state.current.index = defaultIndex; + state.current.player = 1; + state.current.song = state.queue.default[defaultIndex]; + state.queue.previousNode = get().current.song; + }); + } + + return get().actions.getPlayerData(); + }, + setMuted: (muted: boolean) => { set((state) => { - state.queue.default = [ - ...queue.slice(0, currentIndex + 1), - ...queueSongs, - ...queue.slice(currentIndex + 1), - ]; - state.queue.shuffled = shuffledQueueWithNewSongs; + state.muted = muted; }); - } - - return get().getPlayerData(); - }, - autoNext: () => { - const isLastTrack = get().checkIsLastTrack(); - const repeat = get().repeat; - - if (repeat === PlayerRepeat.ONE) { - const nextIndex = get().current.index; - + }, + setRepeat: (type: PlayerRepeat) => { set((state) => { - state.current.time = 0; - state.current.index = nextIndex; - state.current.shuffledIndex = get().current.shuffledIndex; - state.current.player = state.current.player === 1 ? 2 : 1; - state.current.song = get().queue.default[nextIndex]; - state.queue.previousNode = get().current.song; + state.repeat = type; }); - } else if (get().shuffle === PlayerShuffle.TRACK) { - const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1; - const nextSong = get().queue.default.find( - (song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex], + return get().actions.getPlayerData(); + }, + setShuffle: (type: PlayerShuffle) => { + if (type === PlayerShuffle.NONE) { + set((state) => { + state.shuffle = type; + state.queue.shuffled = []; + }); + + return get().actions.getPlayerData(); + } + + const currentSongId = get().current.song?.uniqueId; + + const queueWithoutCurrentSong = get().queue.default.filter( + (song) => song.uniqueId !== currentSongId, ); - const nextSongIndex = get().queue.default.findIndex( - (song) => song.uniqueId === nextSong!.uniqueId, - ); + const shuffledSongIds = shuffle(queueWithoutCurrentSong).map((song) => song.uniqueId); set((state) => { - state.current.time = 0; - state.current.index = nextSongIndex!; - state.current.shuffledIndex = nextShuffleIndex; - state.current.player = state.current.player === 1 ? 2 : 1; - state.current.song = nextSong!; - state.queue.previousNode = get().current.song; + state.shuffle = type; + state.current.shuffledIndex = 0; + state.queue.shuffled = [currentSongId!, ...shuffledSongIds]; }); - } else { - const nextIndex = isLastTrack ? 0 : get().current.index + 1; + return get().actions.getPlayerData(); + }, + setShuffledIndex: (index) => { set((state) => { state.current.time = 0; - state.current.index = nextIndex; - state.current.player = state.current.player === 1 ? 2 : 1; - state.current.song = get().queue.default[nextIndex]; + state.current.shuffledIndex = index; + state.current.player = 1; + state.current.song = state.queue.default[index]; state.queue.previousNode = get().current.song; }); - } - return get().getPlayerData(); - }, - checkIsFirstTrack: () => { - const currentIndex = - get().shuffle === PlayerShuffle.TRACK - ? get().current.shuffledIndex - : get().current.index; - - return currentIndex === 0; - }, - checkIsLastTrack: () => { - const currentIndex = - get().shuffle === PlayerShuffle.TRACK - ? get().current.shuffledIndex - : get().current.index; - - return currentIndex === get().queue.default.length - 1; + return get().actions.getPlayerData(); + }, + setStore: (data) => { + set({ ...get(), ...data }); + }, + setVolume: (volume: number) => { + set((state) => { + state.volume = volume; + }); + }, }, current: { index: 0, @@ -240,247 +644,7 @@ export const usePlayerStore = create()( status: PlayerStatus.PAUSED, time: 0, }, - getPlayerData: () => { - const queue = get().queue.default; - const currentPlayer = get().current.player; - const repeat = get().repeat; - const isLastTrack = get().checkIsLastTrack(); - const isFirstTrack = get().checkIsFirstTrack(); - - let player1; - let player2; - if (get().shuffle === PlayerShuffle.TRACK) { - const shuffledQueue = get().queue.shuffled; - const shuffledIndex = get().current.shuffledIndex; - const current = queue.find( - (song) => song.uniqueId === shuffledQueue[shuffledIndex], - ) as QueueSong; - - let nextSongIndex: number | undefined; - let previousSongIndex: number | undefined; - if (repeat === PlayerRepeat.ALL) { - if (isLastTrack) nextSongIndex = 0; - else nextSongIndex = shuffledIndex + 1; - - if (isFirstTrack) previousSongIndex = queue.length - 1; - else previousSongIndex = shuffledIndex - 1; - } - - if (repeat === PlayerRepeat.ONE) { - nextSongIndex = shuffledIndex; - previousSongIndex = shuffledIndex; - } - - if (repeat === PlayerRepeat.NONE) { - if (isLastTrack) nextSongIndex = undefined; - else nextSongIndex = shuffledIndex + 1; - - if (isFirstTrack) previousSongIndex = undefined; - else previousSongIndex = shuffledIndex - 1; - } - - const next = nextSongIndex - ? (queue.find( - (song) => song.uniqueId === shuffledQueue[nextSongIndex as number], - ) as QueueSong) - : undefined; - - const previous = queue.find( - (song) => song.uniqueId === shuffledQueue[shuffledIndex - 1], - ) as QueueSong; - - player1 = currentPlayer === 1 ? current : next; - player2 = currentPlayer === 1 ? next : current; - - return { - current: { - index: get().current.index, - nextIndex: nextSongIndex, - player: get().current.player, - previousIndex: previousSongIndex, - shuffledIndex: get().current.shuffledIndex, - song: get().current.song, - status: get().current.status, - }, - player1, - player2, - queue: { - current, - next, - previous, - }, - }; - } - - const currentIndex = get().current.index; - - let nextSongIndex; - let previousSongIndex; - if (repeat === PlayerRepeat.ALL) { - if (isLastTrack) nextSongIndex = 0; - else nextSongIndex = currentIndex + 1; - - if (isFirstTrack) previousSongIndex = queue.length - 1; - else previousSongIndex = currentIndex - 1; - } - - if (repeat === PlayerRepeat.ONE) { - nextSongIndex = currentIndex; - previousSongIndex = currentIndex; - } - - if (repeat === PlayerRepeat.NONE) { - if (isLastTrack) nextSongIndex = undefined; - else nextSongIndex = currentIndex + 1; - - if (isFirstTrack) previousSongIndex = undefined; - else previousSongIndex = currentIndex - 1; - } - - player1 = - currentPlayer === 1 - ? queue[currentIndex] - : nextSongIndex !== undefined - ? queue[nextSongIndex] - : undefined; - - player2 = - currentPlayer === 1 - ? nextSongIndex !== undefined - ? queue[nextSongIndex] - : undefined - : queue[currentIndex]; - - return { - current: { - index: currentIndex, - nextIndex: nextSongIndex, - player: get().current.player, - previousIndex: previousSongIndex, - shuffledIndex: get().current.shuffledIndex, - song: get().current.song, - status: get().current.status, - }, - player1, - player2, - queue: { - current: queue[currentIndex], - next: nextSongIndex !== undefined ? queue[nextSongIndex] : undefined, - previous: queue[currentIndex - 1], - }, - }; - }, - getQueueData: () => { - const queue = get().queue.default; - return { - current: queue[get().current.index], - next: queue[get().current.index + 1], - previous: queue[get().current.index - 1], - }; - }, muted: false, - next: () => { - const isLastTrack = get().checkIsLastTrack(); - const repeat = get().repeat; - - if (get().shuffle === PlayerShuffle.TRACK) { - const nextShuffleIndex = isLastTrack ? 0 : get().current.shuffledIndex + 1; - - const nextSong = get().queue.default.find( - (song) => song.uniqueId === get().queue.shuffled[nextShuffleIndex], - ); - - const nextSongIndex = get().queue.default.findIndex( - (song) => song.uniqueId === nextSong?.uniqueId, - ); - - set((state) => { - state.current.time = 0; - state.current.index = nextSongIndex!; - state.current.shuffledIndex = nextShuffleIndex; - state.current.player = 1; - state.current.song = nextSong!; - state.queue.previousNode = get().current.song; - }); - } else { - const nextIndex = - repeat === PlayerRepeat.ALL - ? isLastTrack - ? 0 - : get().current.index + 1 - : isLastTrack - ? get().current.index - : get().current.index + 1; - - set((state) => { - state.current.time = 0; - state.current.index = nextIndex; - state.current.player = 1; - state.current.song = get().queue.default[nextIndex]; - state.queue.previousNode = get().current.song; - }); - } - - return get().getPlayerData(); - }, - pause: () => { - set((state) => { - state.current.status = PlayerStatus.PAUSED; - }); - }, - play: () => { - set((state) => { - state.current.status = PlayerStatus.PLAYING; - }); - }, - player1: () => { - return get().getPlayerData().player1; - }, - player2: () => { - return get().getPlayerData().player2; - }, - prev: () => { - const isFirstTrack = get().checkIsFirstTrack(); - const repeat = get().repeat; - - if (get().shuffle === PlayerShuffle.TRACK) { - const prevShuffleIndex = isFirstTrack ? 0 : get().current.shuffledIndex - 1; - - const prevSong = get().queue.default.find( - (song) => song.uniqueId === get().queue.shuffled[prevShuffleIndex], - ); - - const prevIndex = get().queue.default.findIndex( - (song) => song.uniqueId === prevSong?.uniqueId, - ); - - set((state) => { - state.current.time = 0; - state.current.index = prevIndex!; - state.current.shuffledIndex = prevShuffleIndex; - state.current.player = 1; - state.current.song = prevSong!; - state.queue.previousNode = get().current.song; - }); - } else { - let prevIndex: number; - if (repeat === PlayerRepeat.ALL) { - prevIndex = isFirstTrack ? get().queue.default.length - 1 : get().current.index - 1; - } else { - prevIndex = isFirstTrack ? 0 : get().current.index - 1; - } - - set((state) => { - state.current.time = 0; - state.current.index = prevIndex; - state.current.player = 1; - state.current.song = state.queue.default[state.current.index]; - state.queue.previousNode = get().current.song; - }); - } - - return get().getPlayerData(); - }, queue: { default: [], played: [], @@ -488,168 +652,7 @@ export const usePlayerStore = create()( shuffled: [], sorted: [], }, - reorderQueue: (rowUniqueIds: string[], afterUniqueId?: string) => { - // Don't move if dropping on top of a selected row - if (afterUniqueId && rowUniqueIds.includes(afterUniqueId)) { - return get().getPlayerData(); - } - - const queue = get().queue.default; - const currentSongUniqueId = get().current.song?.uniqueId; - const queueWithoutSelectedRows = queue.filter( - (song) => !rowUniqueIds.includes(song.uniqueId), - ); - - const moveBeforeIndex = queueWithoutSelectedRows.findIndex( - (song) => song.uniqueId === afterUniqueId, - ); - - // AG-Grid does not provide node data when a row is moved to the bottom of the list - const reorderedQueue = afterUniqueId - ? [ - ...queueWithoutSelectedRows.slice(0, moveBeforeIndex), - ...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)), - ...queueWithoutSelectedRows.slice(moveBeforeIndex), - ] - : [ - ...queueWithoutSelectedRows, - ...queue.filter((song) => rowUniqueIds.includes(song.uniqueId)), - ]; - - const currentSongIndex = reorderedQueue.findIndex( - (song) => song.uniqueId === currentSongUniqueId, - ); - - set({ - current: { ...get().current, index: currentSongIndex }, - queue: { ...get().queue, default: reorderedQueue }, - }); - - return get().getPlayerData(); - }, repeat: PlayerRepeat.NONE, - setCurrentIndex: (index) => { - if (get().shuffle === PlayerShuffle.TRACK) { - const foundSong = get().queue.default.find( - (song) => song.uniqueId === get().queue.shuffled[index], - ); - const foundIndex = get().queue.default.findIndex( - (song) => song.uniqueId === foundSong?.uniqueId, - ); - set((state) => { - state.current.time = 0; - state.current.index = foundIndex!; - state.current.shuffledIndex = index; - state.current.player = 1; - state.current.song = foundSong!; - state.queue.previousNode = get().current.song; - }); - } else { - set((state) => { - state.current.time = 0; - state.current.index = index; - state.current.player = 1; - state.current.song = state.queue.default[index]; - state.queue.previousNode = get().current.song; - }); - } - - return get().getPlayerData(); - }, - setCurrentTime: (time) => { - set((state) => { - state.current.time = time; - }); - }, - setCurrentTrack: (uniqueId) => { - if (get().shuffle === PlayerShuffle.TRACK) { - const defaultIndex = get().queue.default.findIndex( - (song) => song.uniqueId === uniqueId, - ); - - const shuffledIndex = get().queue.shuffled.findIndex((id) => id === uniqueId); - - set((state) => { - state.current.time = 0; - state.current.index = defaultIndex; - state.current.shuffledIndex = shuffledIndex; - state.current.player = 1; - state.current.song = state.queue.default[defaultIndex]; - state.queue.previousNode = get().current.song; - }); - } else { - const defaultIndex = get().queue.default.findIndex( - (song) => song.uniqueId === uniqueId, - ); - - set((state) => { - state.current.time = 0; - state.current.index = defaultIndex; - state.current.player = 1; - state.current.song = state.queue.default[defaultIndex]; - state.queue.previousNode = get().current.song; - }); - } - - return get().getPlayerData(); - }, - setMuted: (muted: boolean) => { - set((state) => { - state.muted = muted; - }); - }, - setRepeat: (type: PlayerRepeat) => { - set((state) => { - state.repeat = type; - }); - - return get().getPlayerData(); - }, - setShuffle: (type: PlayerShuffle) => { - if (type === PlayerShuffle.NONE) { - set((state) => { - state.shuffle = type; - state.queue.shuffled = []; - }); - - return get().getPlayerData(); - } - - const currentSongId = get().current.song?.uniqueId; - - const queueWithoutCurrentSong = get().queue.default.filter( - (song) => song.uniqueId !== currentSongId, - ); - - const shuffledSongIds = shuffle(queueWithoutCurrentSong).map((song) => song.uniqueId); - - set((state) => { - state.shuffle = type; - state.current.shuffledIndex = 0; - state.queue.shuffled = [currentSongId!, ...shuffledSongIds]; - }); - - return get().getPlayerData(); - }, - setShuffledIndex: (index) => { - set((state) => { - state.current.time = 0; - state.current.shuffledIndex = index; - state.current.player = 1; - state.current.song = state.queue.default[index]; - state.queue.previousNode = get().current.song; - }); - - return get().getPlayerData(); - }, - setStore: (data) => { - set({ ...get(), ...data }); - }, - setVolume: (volume: number) => { - set((state) => { - state.volume = volume; - }); - }, shuffle: PlayerShuffle.NONE, volume: 50, })), @@ -658,3 +661,67 @@ export const usePlayerStore = create()( { name: 'store_player' }, ), ); + +export const usePlayerStoreActions = () => usePlayerStore((state) => state.actions); + +export const usePlayerControls = () => + usePlayerStore( + (state) => ({ + autoNext: state.actions.autoNext, + next: state.actions.next, + pause: state.actions.pause, + play: state.actions.play, + previous: state.actions.previous, + setCurrentIndex: state.actions.setCurrentIndex, + setMuted: state.actions.setMuted, + setRepeat: state.actions.setRepeat, + setShuffle: state.actions.setShuffle, + setShuffledIndex: state.actions.setShuffledIndex, + setVolume: state.actions.setVolume, + }), + shallow, + ); + +export const useQueueControls = () => + usePlayerStore( + (state) => ({ + addToQueue: state.actions.addToQueue, + reorderQueue: state.actions.reorderQueue, + setCurrentIndex: state.actions.setCurrentIndex, + setCurrentTrack: state.actions.setCurrentTrack, + setShuffledIndex: state.actions.setShuffledIndex, + }), + shallow, + ); + +export const useQueueData = () => usePlayerStore((state) => state.actions.getQueueData); + +export const usePlayer1Data = () => usePlayerStore((state) => state.actions.player1); + +export const usePlayer2Data = () => usePlayerStore((state) => state.actions.player2); + +export const useSetCurrentTime = () => usePlayerStore((state) => state.actions.setCurrentTime); + +export const useIsFirstTrack = () => usePlayerStore((state) => state.actions.checkIsFirstTrack); + +export const useIsLastTrack = () => usePlayerStore((state) => state.actions.checkIsLastTrack); + +export const useDefaultQueue = () => usePlayerStore((state) => state.queue.default); + +export const useCurrentSong = () => usePlayerStore((state) => state.current.song); + +export const useCurrentPlayer = () => usePlayerStore((state) => state.current.player); + +export const useCurrentStatus = () => usePlayerStore((state) => state.current.status); + +export const usePreviousSong = () => usePlayerStore((state) => state.queue.previousNode); + +export const useRepeatStatus = () => usePlayerStore((state) => state.repeat); + +export const useShuffleStatus = () => usePlayerStore((state) => state.shuffle); + +export const useCurrentTime = () => usePlayerStore((state) => state.current.time); + +export const useVolume = () => usePlayerStore((state) => state.volume); + +export const useMuted = () => usePlayerStore((state) => state.muted); diff --git a/packages/renderer/src/themes/default.scss b/packages/renderer/src/themes/default.scss index 2d9bf82cc..11324648c 100644 --- a/packages/renderer/src/themes/default.scss +++ b/packages/renderer/src/themes/default.scss @@ -2,7 +2,7 @@ --root-font-size: 12px; --icon-color: rgb(255, 255, 255); - --primary-color: rgb(49, 107, 224); + --primary-color: rgb(89, 141, 245); --secondary-color: rgb(255, 120, 120); --success-color: green; --warning-color: orange; @@ -20,7 +20,7 @@ --sidebar-btn-color: rgb(179, 179, 179); --sidebar-btn-color-hover: #dddddd; --sidebar-handle-bg: #4d4d4d; - --sidebar-border: 1px rgba(150, 150, 150, 0.1) solid; + --sidebar-border: none; --playerbar-bg: linear-gradient(rgb(20, 20, 20) 0%, rgb(30, 30, 30) 50%, rgb(20, 20, 20) 100%); --playerbar-btn-main-fg: rgb(0, 0, 0); @@ -38,7 +38,7 @@ --tooltip-bg: #ffffff; --tooltip-fg: #000000; - --scrollbar-track-bg: #181818; + --scrollbar-track-bg: transparent; --scrollbar-thumb-bg: rgba(90, 90, 90, 0.5); --btn-primary-bg: var(--primary-color); @@ -46,14 +46,14 @@ --btn-primary-fg: #ffffff; --btn-primary-fg-hover: #ffffff; - --btn-default-bg: rgb(39, 39, 39); + --btn-default-bg: rgb(31, 31, 32); --btn-default-bg-hover: rgb(63, 63, 63); --btn-default-fg: rgb(193, 193, 193); --btn-default-fg-hover: rgb(193, 193, 193); --btn-subtle-bg: transparent; --btn-subtle-bg-hover: transparent; - --btn-subtle-fg: rgb(150, 150, 150); + --btn-subtle-fg: rgb(224, 224, 224); --btn-subtle-fg-hover: rgb(240, 240, 240); --input-bg: rgb(35, 35, 35); @@ -107,7 +107,6 @@ --ag-foreground-color: rgb(179, 179, 179); --ag-background-color: var(--main-bg); - // --ag-odd-row-background-color: rgb(25, 25, 25); --ag-row-hover-color: rgba(100, 100, 100, 0.2); --ag-selected-row-background-color: rgba(100, 100, 100, 0.4); } diff --git a/packages/renderer/src/themes/light.scss b/packages/renderer/src/themes/light.scss index e1493d351..1d149de65 100644 --- a/packages/renderer/src/themes/light.scss +++ b/packages/renderer/src/themes/light.scss @@ -9,7 +9,7 @@ body[data-theme="defaultLight"] { --titlebar-fg: rgb(25, 25, 25); --titlebar-bg: rgb(227, 229, 232); - --sidebar-bg: rgb(255, 255, 255); + --sidebar-bg: rgb(240, 241, 242); --sidebar-btn-color: rgb(0, 0, 0); --sidebar-btn-color-hover: rgb(0, 0, 0); --sidebar-handle-bg: #4d4d4d; @@ -34,8 +34,8 @@ body[data-theme="defaultLight"] { --tooltip-bg: rgb(255, 255, 255); --tooltip-fg: rgb(0, 0, 0); - --scrollbar-track-bg: rgba(0, 0, 0, 0.2); - --scrollbar-thumb-bg: rgb(150, 150, 150); + --scrollbar-track-bg: transparent; + --scrollbar-thumb-bg: rgb(140, 140, 140); --btn-primary-bg: var(--primary-color); --btn-primary-bg-hover: rgb(47, 122, 237); @@ -109,6 +109,17 @@ body[data-theme="defaultLight"] { --ag-selected-row-background-color: rgba(100, 100, 100, 0.4); } + .ag-root ::-webkit-scrollbar-corner { + background: var(--scrollbar-track-bg); + } + .ag-root ::-webkit-scrollbar-track-piece { + background: var(--scrollbar-track-bg); + } + .ag-root ::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-bg); + // background: black; + } + .ag-cell-focus { border: 1px rgba(60, 60, 60, 0.3) solid !important; }