diff --git a/src/api/api.ts b/src/api/api.ts index cca4210..804ae5c 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -46,13 +46,30 @@ api.interceptors.response.use( axiosRetry(api, { retries: 3, + retryDelay: (retryCount) => { + return retryCount * 1000; + }, }); -export const playlistApi = axios.create({ +export const autoFailApi = axios.create({ baseURL: API_BASE_URL, + validateStatus: () => { + return false; + }, +}); + +autoFailApi.interceptors.request.use((config) => { + config.params = config.params || {}; + config.params.u = auth.username; + config.params.s = auth.salt; + config.params.t = auth.hash; + config.params.v = '1.15.0'; + config.params.c = 'sonixd'; + config.params.f = 'json'; + return config; }); -playlistApi.interceptors.response.use( +autoFailApi.interceptors.response.use( (res) => { // Return the subsonic response directly res.data = res.data['subsonic-response']; @@ -63,10 +80,13 @@ playlistApi.interceptors.response.use( } ); -axiosRetry(playlistApi, { - retries: 3, +axiosRetry(autoFailApi, { + retries: 5, retryCondition: (e: any) => { - return e.status !== 'ok'; + return e.response.data['subsonic-response'].status !== 'ok'; + }, + retryDelay: (retryCount) => { + return retryCount * 1000; }, }); @@ -501,7 +521,7 @@ export const clearPlaylist = async (id: string, entryCount: number) => { } data = ( - await playlistApi.get(`/updatePlaylist`, { + await api.get(`/updatePlaylist`, { params, }) ).data; @@ -531,7 +551,7 @@ export const populatePlaylist = async (id: string, entry: any[]) => { } data = ( - await playlistApi.get(`/updatePlaylist`, { + await api.get(`/updatePlaylist`, { params, }) ).data; diff --git a/src/components/shared/ContextMenu.tsx b/src/components/shared/ContextMenu.tsx index a206faa..16e52f8 100644 --- a/src/components/shared/ContextMenu.tsx +++ b/src/components/shared/ContextMenu.tsx @@ -1,16 +1,17 @@ +/* eslint-disable no-await-in-loop */ import React, { useRef, useState } from 'react'; -import { useQuery } from 'react-query'; +import { useQuery, useQueryClient } from 'react-query'; import { useHistory } from 'react-router'; import { Popover, Whisper } from 'rsuite'; -import { getPlaylists, populatePlaylist } from '../../api/api'; +import { getPlaylists, populatePlaylist, star, unstar } from '../../api/api'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { addProcessingPlaylist, removeProcessingPlaylist, setContextMenu, } from '../../redux/miscSlice'; +import { setStar } from '../../redux/playQueueSlice'; import { - ContextMenuTitle, ContextMenuDivider, ContextMenuWindow, StyledContextMenuButton, @@ -18,10 +19,11 @@ import { StyledButton, } from './styled'; import { notifyToast } from './toast'; +import { sleep } from '../../shared/utils'; export const ContextMenuButton = ({ children, ...rest }: any) => { return ( - + {children} ); @@ -53,6 +55,7 @@ export const ContextMenu = ({ export const GlobalContextMenu = () => { const history = useHistory(); const dispatch = useAppDispatch(); + const queryClient = useQueryClient(); const misc = useAppSelector((state) => state.misc); const multiSelect = useAppSelector((state) => state.multiSelect); const playlistTriggerRef = useRef(); @@ -111,6 +114,54 @@ export const GlobalContextMenu = () => { dispatch(removeProcessingPlaylist(localSelectedPlaylistId)); }; + const refetchAfterFavorite = async () => { + await queryClient.refetchQueries(['starred'], { + active: true, + }); + await queryClient.refetchQueries(['album'], { + active: true, + }); + await queryClient.refetchQueries(['albumList'], { + active: true, + }); + await queryClient.refetchQueries(['playlist'], { + active: true, + }); + }; + + const handleFavorite = async (ordered: boolean) => { + dispatch(setContextMenu({ show: false })); + + const sortedEntries = [...multiSelect.selected].sort( + (a: any, b: any) => a.rowIndex - b.rowIndex + ); + + for (let i = 0; i < sortedEntries.length; i += 1) { + await star(sortedEntries[i].id, sortedEntries[i].type); + dispatch(setStar({ id: sortedEntries[i].id, type: 'star' })); + if (ordered) { + await sleep(350); + } + } + + await refetchAfterFavorite(); + }; + + const handleUnfavorite = async () => { + dispatch(setContextMenu({ show: false })); + + const starredEntries = multiSelect.selected.filter( + (entry: any) => entry.starred + ); + + for (let i = 0; i < starredEntries.length; i += 1) { + await unstar(starredEntries[i].id, starredEntries[i].type); + dispatch(setStar({ id: starredEntries[i].id, type: 'unstar' })); + } + + await refetchAfterFavorite(); + }; + return ( <> {misc.contextMenu.show && misc.contextMenu.type === 'nowPlaying' && ( @@ -118,14 +169,13 @@ export const GlobalContextMenu = () => { - + Selected: {multiSelect.selected.length} - + Add to queue Remove from current @@ -134,16 +184,17 @@ export const GlobalContextMenu = () => { setSelectedPlaylistId(e)} /> { - Add to favorites - Remove from favorites + handleFavorite(false)}> + Add to favorites + + handleFavorite(true)}> + Add to favorites (ordered) + + + Remove from favorites + )} diff --git a/src/components/shared/styled.ts b/src/components/shared/styled.ts index a5d1ad6..d6d3980 100644 --- a/src/components/shared/styled.ts +++ b/src/components/shared/styled.ts @@ -170,8 +170,8 @@ export const ContextMenuWindow = styled.div<{ left: ${(props) => `${props.xPos}px`}; height: ${(props) => `${ - props.numOfButtons * 30.5 + - props.numOfDividers * 7 + + props.numOfButtons * 30 + + props.numOfDividers * 2 + (props.hasTitle ? 16 : 0) }px`}; width: ${(props) => `${props.width}px`}; @@ -186,15 +186,15 @@ export const ContextMenuWindow = styled.div<{ export const StyledContextMenuButton = styled(Button)` text-align: left; + margin: 0px !important; `; export const ContextMenuDivider = styled.hr` - margin: 5px 0 5px 0; + margin: 0px; `; export const ContextMenuTitle = styled.div` color: ${(props) => props.theme.primary.text}; - margin-left: 5px; - margin-top: 5px; + margin: 5px 0 5px 5px; user-select: none; `; diff --git a/src/shared/utils.ts b/src/shared/utils.ts index ad35b88..0606258 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -265,3 +265,26 @@ export const moveToIndex = ( // Finally, return the modified list return newList; }; + +export const getUpdatedEntryRowIndex = ( + selectedEntries: any, + entryData: any +) => { + const updatedEntries = selectedEntries.map((entry: any) => { + const findIndex = entryData.findIndex( + (item: any) => item.uniqueId === entry.uniqueId + ); + return { ...entry, rowIndex: findIndex }; + }); + + // Sort the entries by their rowIndex so that we can re-add them in the proper order + const sortedEntries = updatedEntries.sort( + (a: any, b: any) => a.rowIndex - b.rowIndex + ); + + return sortedEntries; +}; + +export const sleep = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +};