Browse Source

add context menu star functionality

- resize buttons
- revert back to default api for star/playlist due to 200 responses
- add sleep util function
master
jeffvli 3 years ago
parent
commit
ce74feca69
  1. 34
      src/api/api.ts
  2. 84
      src/components/shared/ContextMenu.tsx
  3. 10
      src/components/shared/styled.ts
  4. 23
      src/shared/utils.ts

34
src/api/api.ts

@ -46,13 +46,30 @@ api.interceptors.response.use(
axiosRetry(api, { axiosRetry(api, {
retries: 3, retries: 3,
retryDelay: (retryCount) => {
return retryCount * 1000;
},
}); });
export const playlistApi = axios.create({ export const autoFailApi = axios.create({
baseURL: API_BASE_URL, 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) => { (res) => {
// Return the subsonic response directly // Return the subsonic response directly
res.data = res.data['subsonic-response']; res.data = res.data['subsonic-response'];
@ -63,10 +80,13 @@ playlistApi.interceptors.response.use(
} }
); );
axiosRetry(playlistApi, { axiosRetry(autoFailApi, {
retries: 3, retries: 5,
retryCondition: (e: any) => { 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 = ( data = (
await playlistApi.get(`/updatePlaylist`, { await api.get(`/updatePlaylist`, {
params, params,
}) })
).data; ).data;
@ -531,7 +551,7 @@ export const populatePlaylist = async (id: string, entry: any[]) => {
} }
data = ( data = (
await playlistApi.get(`/updatePlaylist`, { await api.get(`/updatePlaylist`, {
params, params,
}) })
).data; ).data;

84
src/components/shared/ContextMenu.tsx

@ -1,16 +1,17 @@
/* eslint-disable no-await-in-loop */
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { useQuery } from 'react-query'; import { useQuery, useQueryClient } from 'react-query';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { Popover, Whisper } from 'rsuite'; 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 { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { import {
addProcessingPlaylist, addProcessingPlaylist,
removeProcessingPlaylist, removeProcessingPlaylist,
setContextMenu, setContextMenu,
} from '../../redux/miscSlice'; } from '../../redux/miscSlice';
import { setStar } from '../../redux/playQueueSlice';
import { import {
ContextMenuTitle,
ContextMenuDivider, ContextMenuDivider,
ContextMenuWindow, ContextMenuWindow,
StyledContextMenuButton, StyledContextMenuButton,
@ -18,10 +19,11 @@ import {
StyledButton, StyledButton,
} from './styled'; } from './styled';
import { notifyToast } from './toast'; import { notifyToast } from './toast';
import { sleep } from '../../shared/utils';
export const ContextMenuButton = ({ children, ...rest }: any) => { export const ContextMenuButton = ({ children, ...rest }: any) => {
return ( return (
<StyledContextMenuButton {...rest} appearance="subtle" size="xs" block> <StyledContextMenuButton {...rest} appearance="subtle" size="sm" block>
{children} {children}
</StyledContextMenuButton> </StyledContextMenuButton>
); );
@ -53,6 +55,7 @@ export const ContextMenu = ({
export const GlobalContextMenu = () => { export const GlobalContextMenu = () => {
const history = useHistory(); const history = useHistory();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const queryClient = useQueryClient();
const misc = useAppSelector((state) => state.misc); const misc = useAppSelector((state) => state.misc);
const multiSelect = useAppSelector((state) => state.multiSelect); const multiSelect = useAppSelector((state) => state.multiSelect);
const playlistTriggerRef = useRef<any>(); const playlistTriggerRef = useRef<any>();
@ -111,6 +114,54 @@ export const GlobalContextMenu = () => {
dispatch(removeProcessingPlaylist(localSelectedPlaylistId)); 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 ( return (
<> <>
{misc.contextMenu.show && misc.contextMenu.type === 'nowPlaying' && ( {misc.contextMenu.show && misc.contextMenu.type === 'nowPlaying' && (
@ -118,14 +169,13 @@ export const GlobalContextMenu = () => {
<ContextMenu <ContextMenu
xPos={misc.contextMenu.xPos} xPos={misc.contextMenu.xPos}
yPos={misc.contextMenu.yPos} yPos={misc.contextMenu.yPos}
width={140} width={190}
numOfButtons={5} numOfButtons={7}
numOfDividers={3} numOfDividers={3}
hasTitle
> >
<ContextMenuTitle> <ContextMenuButton>
Selected: {multiSelect.selected.length} Selected: {multiSelect.selected.length}
</ContextMenuTitle> </ContextMenuButton>
<ContextMenuDivider /> <ContextMenuDivider />
<ContextMenuButton>Add to queue</ContextMenuButton> <ContextMenuButton>Add to queue</ContextMenuButton>
<ContextMenuButton>Remove from current</ContextMenuButton> <ContextMenuButton>Remove from current</ContextMenuButton>
@ -134,16 +184,17 @@ export const GlobalContextMenu = () => {
<Whisper <Whisper
ref={playlistTriggerRef} ref={playlistTriggerRef}
enterable enterable
placement="autoHorizontal" placement="autoHorizontalStart"
trigger="none" trigger="none"
speaker={ speaker={
<Popover> <Popover>
<StyledInputPicker <StyledInputPicker
data={playlists} data={playlists}
placement="autoVerticalStart"
virtualized virtualized
style={{ width: '150px' }}
labelKey="name" labelKey="name"
valueKey="id" valueKey="id"
width={200}
onChange={(e: any) => setSelectedPlaylistId(e)} onChange={(e: any) => setSelectedPlaylistId(e)}
/> />
<StyledButton <StyledButton
@ -173,8 +224,15 @@ export const GlobalContextMenu = () => {
</Whisper> </Whisper>
<ContextMenuDivider /> <ContextMenuDivider />
<ContextMenuButton>Add to favorites</ContextMenuButton> <ContextMenuButton onClick={() => handleFavorite(false)}>
<ContextMenuButton>Remove from favorites</ContextMenuButton> Add to favorites
</ContextMenuButton>
<ContextMenuButton onClick={() => handleFavorite(true)}>
Add to favorites (ordered)
</ContextMenuButton>
<ContextMenuButton onClick={handleUnfavorite}>
Remove from favorites
</ContextMenuButton>
</ContextMenu> </ContextMenu>
</> </>
)} )}

10
src/components/shared/styled.ts

@ -170,8 +170,8 @@ export const ContextMenuWindow = styled.div<{
left: ${(props) => `${props.xPos}px`}; left: ${(props) => `${props.xPos}px`};
height: ${(props) => height: ${(props) =>
`${ `${
props.numOfButtons * 30.5 + props.numOfButtons * 30 +
props.numOfDividers * 7 + props.numOfDividers * 2 +
(props.hasTitle ? 16 : 0) (props.hasTitle ? 16 : 0)
}px`}; }px`};
width: ${(props) => `${props.width}px`}; width: ${(props) => `${props.width}px`};
@ -186,15 +186,15 @@ export const ContextMenuWindow = styled.div<{
export const StyledContextMenuButton = styled(Button)` export const StyledContextMenuButton = styled(Button)`
text-align: left; text-align: left;
margin: 0px !important;
`; `;
export const ContextMenuDivider = styled.hr` export const ContextMenuDivider = styled.hr`
margin: 5px 0 5px 0; margin: 0px;
`; `;
export const ContextMenuTitle = styled.div` export const ContextMenuTitle = styled.div`
color: ${(props) => props.theme.primary.text}; color: ${(props) => props.theme.primary.text};
margin-left: 5px; margin: 5px 0 5px 5px;
margin-top: 5px;
user-select: none; user-select: none;
`; `;

23
src/shared/utils.ts

@ -265,3 +265,26 @@ export const moveToIndex = (
// Finally, return the modified list // Finally, return the modified list
return newList; 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));
};

Loading…
Cancel
Save