mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
directly replace playlist rules on save and replace
This commit is contained in:
+30
-9
@@ -44,7 +44,13 @@ import { Modal } from '/@/shared/components/modal/modal';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||
import { LibraryItem, SongListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
import {
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
SongListSort,
|
||||
SortOrder,
|
||||
UpdatePlaylistBody,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
interface PlaylistDetailSongListHeaderFiltersProps {
|
||||
@@ -193,7 +199,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
<MoreButton onClick={handleMore} />
|
||||
</Group>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
{isViewEditMode && <SaveAndReplaceButton mode={mode} />}
|
||||
{isViewEditMode && <SaveAndReplaceButton mode={mode} playlist={detailQuery.data} />}
|
||||
{isViewEditMode && (
|
||||
<Button
|
||||
onClick={() => setMode?.(mode === 'edit' ? 'view' : 'edit')}
|
||||
@@ -242,24 +248,39 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const openSaveAndReplaceModal = (playlistId: string, listData: unknown[]) => {
|
||||
export const openSaveAndReplaceModal = (playlistId: string, updateBody: UpdatePlaylistBody) => {
|
||||
openContextModal({
|
||||
innerProps: { listData, playlistId },
|
||||
innerProps: { playlistId, updateBody },
|
||||
modalKey: 'saveAndReplace',
|
||||
size: 'sm',
|
||||
title: i18n.t('common.saveAndReplace', { postProcess: 'titleCase' }) as string,
|
||||
});
|
||||
};
|
||||
|
||||
const SaveAndReplaceButton = ({ mode }: { mode: 'edit' | 'view' | undefined }) => {
|
||||
const SaveAndReplaceButton = ({
|
||||
mode,
|
||||
playlist,
|
||||
}: {
|
||||
mode: 'edit' | 'view' | undefined;
|
||||
playlist: Playlist | undefined;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
const { listData } = useListContext();
|
||||
|
||||
const handleOpenModal = useCallback(() => {
|
||||
if (!playlistId || !listData) return;
|
||||
openSaveAndReplaceModal(playlistId, listData);
|
||||
}, [playlistId, listData]);
|
||||
if (!playlistId || !playlist) return;
|
||||
|
||||
const updateBody: UpdatePlaylistBody = {
|
||||
comment: playlist.description ?? '',
|
||||
name: playlist.name,
|
||||
ownerId: playlist.ownerId ?? '',
|
||||
public: playlist.public ?? false,
|
||||
queryBuilderRules: playlist.rules ?? undefined,
|
||||
sync: playlist.sync ?? false,
|
||||
};
|
||||
|
||||
openSaveAndReplaceModal(playlistId, updateBody);
|
||||
}, [playlistId, playlist]);
|
||||
|
||||
if (mode === 'view') {
|
||||
return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PlaylistQueryBuilder,
|
||||
PlaylistQueryBuilderRef,
|
||||
} from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
|
||||
import { convertQueryGroupToNDQuery } from '/@/renderer/features/playlists/utils';
|
||||
import { JsonPreview } from '/@/renderer/features/shared/components/json-preview';
|
||||
import { Box } from '/@/shared/components/box/box';
|
||||
@@ -25,7 +25,6 @@ import { toast } from '/@/shared/components/toast/toast';
|
||||
import { SongListSort } from '/@/shared/types/domain-types';
|
||||
|
||||
export interface PlaylistQueryEditorProps {
|
||||
createPlaylistMutation: ReturnType<typeof useCreatePlaylist>;
|
||||
detailQuery: ReturnType<typeof useQuery<any>>;
|
||||
handleSave: (
|
||||
filter: Record<string, any>,
|
||||
@@ -39,6 +38,7 @@ export interface PlaylistQueryEditorProps {
|
||||
onToggleExpand: () => void;
|
||||
playlistId: string;
|
||||
queryBuilderRef: React.RefObject<null | PlaylistQueryBuilderRef>;
|
||||
updatePlaylistMutation: ReturnType<typeof useUpdatePlaylist>;
|
||||
}
|
||||
|
||||
type AppliedJsonState = {
|
||||
@@ -77,7 +77,6 @@ const parseRulesJsonToSaveArgs = (
|
||||
};
|
||||
|
||||
export const PlaylistQueryEditor = ({
|
||||
createPlaylistMutation,
|
||||
detailQuery,
|
||||
handleSave,
|
||||
handleSaveAs,
|
||||
@@ -85,6 +84,7 @@ export const PlaylistQueryEditor = ({
|
||||
onToggleExpand,
|
||||
playlistId,
|
||||
queryBuilderRef,
|
||||
updatePlaylistMutation,
|
||||
}: PlaylistQueryEditorProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -322,7 +322,7 @@ export const PlaylistQueryEditor = ({
|
||||
<Button
|
||||
disabled={!isQueryBuilderExpanded}
|
||||
leftSection={<Icon icon="save" />}
|
||||
loading={createPlaylistMutation?.isPending}
|
||||
loading={updatePlaylistMutation?.isPending}
|
||||
onClick={() => {
|
||||
if (!isQueryBuilderExpanded) return;
|
||||
const payload = getFiltersForSave();
|
||||
|
||||
@@ -1,40 +1,22 @@
|
||||
import { closeAllModals, ContextModalProps } from '@mantine/modals';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useReplacePlaylist } from '/@/renderer/features/playlists/mutations/replace-playlist-mutation';
|
||||
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ConfirmModal } from '/@/shared/components/modal/modal';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { Song } from '/@/shared/types/domain-types';
|
||||
import { UpdatePlaylistBody } from '/@/shared/types/domain-types';
|
||||
|
||||
export const SaveAndReplaceContextModal = ({
|
||||
innerProps,
|
||||
}: ContextModalProps<{ listData: unknown[]; playlistId: string }>) => {
|
||||
}: ContextModalProps<{ playlistId: string; updateBody: UpdatePlaylistBody }>) => {
|
||||
const { t } = useTranslation();
|
||||
const { listData, playlistId } = innerProps;
|
||||
const { playlistId, updateBody } = innerProps;
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const replacePlaylistMutation = useReplacePlaylist({});
|
||||
|
||||
// Get current songs from list data
|
||||
const currentSongIds = useMemo(() => {
|
||||
if (!listData || !Array.isArray(listData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return listData
|
||||
.filter((item): item is Song => {
|
||||
return (
|
||||
typeof item === 'object' &&
|
||||
item !== null &&
|
||||
'id' in item &&
|
||||
typeof (item as any).id === 'string'
|
||||
);
|
||||
})
|
||||
.map((song) => song.id);
|
||||
}, [listData]);
|
||||
const updatePlaylistMutation = useUpdatePlaylist({});
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (!serverId || !playlistId) {
|
||||
@@ -42,24 +24,11 @@ export const SaveAndReplaceContextModal = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentSongIds.length === 0) {
|
||||
console.error('currentSongIds is empty');
|
||||
toast.error({
|
||||
message: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
replacePlaylistMutation.mutate(
|
||||
updatePlaylistMutation.mutate(
|
||||
{
|
||||
apiClientProps: { serverId },
|
||||
body: {
|
||||
songId: currentSongIds,
|
||||
},
|
||||
query: {
|
||||
id: playlistId,
|
||||
},
|
||||
body: updateBody,
|
||||
query: { id: playlistId },
|
||||
},
|
||||
{
|
||||
onError: (err) => {
|
||||
@@ -81,10 +50,10 @@ export const SaveAndReplaceContextModal = ({
|
||||
},
|
||||
},
|
||||
);
|
||||
}, [t, currentSongIds, serverId, playlistId, replacePlaylistMutation]);
|
||||
}, [t, serverId, playlistId, updateBody, updatePlaylistMutation]);
|
||||
|
||||
return (
|
||||
<ConfirmModal loading={replacePlaylistMutation.isPending} onConfirm={handleConfirm}>
|
||||
<ConfirmModal loading={updatePlaylistMutation.isPending} onConfirm={handleConfirm}>
|
||||
<Text>{t('form.editPlaylist.editNote', { postProcess: 'sentenceCase' })}</Text>
|
||||
</ConfirmModal>
|
||||
);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ReplacePlaylistArgs, ReplacePlaylistResponse } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useReplacePlaylist = (args: MutationHookArgs) => {
|
||||
const { options } = args || {};
|
||||
const queryClient = useQueryClient();
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const { addRecentPlaylist } = useRecentPlaylists(serverId);
|
||||
|
||||
return useMutation<ReplacePlaylistResponse, AxiosError, ReplacePlaylistArgs, null>({
|
||||
mutationFn: (args) => {
|
||||
return api.controller.replacePlaylist({
|
||||
...args,
|
||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||
});
|
||||
},
|
||||
onSuccess: (_data, variables, context) => {
|
||||
const { apiClientProps } = variables;
|
||||
const serverId = apiClientProps.serverId;
|
||||
|
||||
if (!serverId) return;
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
exact: false,
|
||||
queryKey: queryKeys.playlists.list(serverId),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.playlists.detail(serverId, variables.query.id),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.playlists.songList(serverId, variables.query.id),
|
||||
});
|
||||
|
||||
addRecentPlaylist(variables.query.id);
|
||||
|
||||
options?.onSuccess?.(_data, variables, context);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
@@ -31,6 +31,9 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.playlists.detail(serverId, query.id),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.playlists.songList(serverId, query.id),
|
||||
});
|
||||
}
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -13,8 +13,8 @@ import { PlaylistQueryBuilderRef } from '/@/renderer/features/playlists/componen
|
||||
import { PlaylistQueryEditor } from '/@/renderer/features/playlists/components/playlist-query-editor';
|
||||
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
||||
import { usePlaylistSongListFilters } from '/@/renderer/features/playlists/hooks/use-playlist-song-list-filters';
|
||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||
import { ListWithSidebarContainer } from '/@/renderer/features/shared/components/list-with-sidebar-container';
|
||||
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
||||
@@ -80,8 +80,8 @@ const PlaylistDetailSongListRoute = () => {
|
||||
...playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||
placeholderData: location.state?.item,
|
||||
});
|
||||
const createPlaylistMutation = useCreatePlaylist({});
|
||||
const deletePlaylistMutation = useDeletePlaylist({});
|
||||
const updatePlaylistMutation = useUpdatePlaylist({});
|
||||
|
||||
const handleSave = (
|
||||
filter: Record<string, any>,
|
||||
@@ -89,8 +89,6 @@ const PlaylistDetailSongListRoute = () => {
|
||||
) => {
|
||||
if (!detailQuery?.data) return;
|
||||
|
||||
// New syntax: sortBy is now a single string with comma-separated fields and +/- prefix
|
||||
// e.g., "+album,-year" means sort by album ascending, then year descending
|
||||
const sortValue =
|
||||
extraFilters.sortBy && extraFilters.sortBy.length > 0
|
||||
? extraFilters.sortBy[0]
|
||||
@@ -99,11 +97,10 @@ const PlaylistDetailSongListRoute = () => {
|
||||
const rules = {
|
||||
...filter,
|
||||
limit: extraFilters.limit || undefined,
|
||||
// order field is now optional - sort direction is embedded in sort field
|
||||
sort: sortValue,
|
||||
};
|
||||
|
||||
createPlaylistMutation.mutate(
|
||||
updatePlaylistMutation.mutate(
|
||||
{
|
||||
apiClientProps: { serverId: detailQuery?.data?._serverId },
|
||||
body: {
|
||||
@@ -114,22 +111,11 @@ const PlaylistDetailSongListRoute = () => {
|
||||
queryBuilderRules: rules,
|
||||
sync: detailQuery?.data?.sync || false,
|
||||
},
|
||||
query: { id: playlistId },
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
onSuccess: () => {
|
||||
toast.success({ message: 'Playlist has been saved' });
|
||||
navigate(
|
||||
generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, {
|
||||
playlistId: data?.id || '',
|
||||
}),
|
||||
{
|
||||
replace: true,
|
||||
},
|
||||
);
|
||||
deletePlaylistMutation.mutate({
|
||||
apiClientProps: { serverId: detailQuery?.data?._serverId },
|
||||
query: { id: playlistId },
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -297,7 +283,6 @@ const PlaylistDetailSongListRoute = () => {
|
||||
</ListWithSidebarContainer>
|
||||
{(isSmartPlaylist || showQueryBuilder) && (
|
||||
<PlaylistQueryEditor
|
||||
createPlaylistMutation={createPlaylistMutation}
|
||||
detailQuery={detailQuery}
|
||||
handleSave={handleSave}
|
||||
handleSaveAs={handleSaveAs}
|
||||
@@ -305,6 +290,7 @@ const PlaylistDetailSongListRoute = () => {
|
||||
onToggleExpand={handleToggleExpand}
|
||||
playlistId={playlistId}
|
||||
queryBuilderRef={queryBuilderRef}
|
||||
updatePlaylistMutation={updatePlaylistMutation}
|
||||
/>
|
||||
)}
|
||||
</ListContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user