mirror of
https://github.com/jeffvli/sonixd.git
synced 2026-05-19 14:21:16 -04:00
add context menu star functionality
- resize buttons - revert back to default api for star/playlist due to 200 responses - add sleep util function
This commit is contained in:
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
playlistApi.interceptors.response.use(
|
||||
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;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
@@ -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 (
|
||||
<StyledContextMenuButton {...rest} appearance="subtle" size="xs" block>
|
||||
<StyledContextMenuButton {...rest} appearance="subtle" size="sm" block>
|
||||
{children}
|
||||
</StyledContextMenuButton>
|
||||
);
|
||||
@@ -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<any>();
|
||||
@@ -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 = () => {
|
||||
<ContextMenu
|
||||
xPos={misc.contextMenu.xPos}
|
||||
yPos={misc.contextMenu.yPos}
|
||||
width={140}
|
||||
numOfButtons={5}
|
||||
width={190}
|
||||
numOfButtons={7}
|
||||
numOfDividers={3}
|
||||
hasTitle
|
||||
>
|
||||
<ContextMenuTitle>
|
||||
<ContextMenuButton>
|
||||
Selected: {multiSelect.selected.length}
|
||||
</ContextMenuTitle>
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton>Add to queue</ContextMenuButton>
|
||||
<ContextMenuButton>Remove from current</ContextMenuButton>
|
||||
@@ -134,16 +184,17 @@ export const GlobalContextMenu = () => {
|
||||
<Whisper
|
||||
ref={playlistTriggerRef}
|
||||
enterable
|
||||
placement="autoHorizontal"
|
||||
placement="autoHorizontalStart"
|
||||
trigger="none"
|
||||
speaker={
|
||||
<Popover>
|
||||
<StyledInputPicker
|
||||
data={playlists}
|
||||
placement="autoVerticalStart"
|
||||
virtualized
|
||||
style={{ width: '150px' }}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
width={200}
|
||||
onChange={(e: any) => setSelectedPlaylistId(e)}
|
||||
/>
|
||||
<StyledButton
|
||||
@@ -173,8 +224,15 @@ export const GlobalContextMenu = () => {
|
||||
</Whisper>
|
||||
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton>Add to favorites</ContextMenuButton>
|
||||
<ContextMenuButton>Remove from favorites</ContextMenuButton>
|
||||
<ContextMenuButton onClick={() => handleFavorite(false)}>
|
||||
Add to favorites
|
||||
</ContextMenuButton>
|
||||
<ContextMenuButton onClick={() => handleFavorite(true)}>
|
||||
Add to favorites (ordered)
|
||||
</ContextMenuButton>
|
||||
<ContextMenuButton onClick={handleUnfavorite}>
|
||||
Remove from favorites
|
||||
</ContextMenuButton>
|
||||
</ContextMenu>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user