diff --git a/src/renderer/components/query-builder/index.tsx b/src/renderer/components/query-builder/index.tsx index 2941752eb..f774d06e4 100644 --- a/src/renderer/components/query-builder/index.tsx +++ b/src/renderer/components/query-builder/index.tsx @@ -117,11 +117,10 @@ export const QueryBuilder = ({ ; + detailQuery: ReturnType>; + handleSave: ( + filter: Record, + extraFilters: { limit?: number; sortBy?: string[]; sortOrder?: string }, + ) => void; + handleSaveAs: ( + filter: Record, + extraFilters: { limit?: number; sortBy?: string[]; sortOrder?: string }, + ) => void; + isQueryBuilderExpanded: boolean; + onToggleExpand: () => void; + playlistId: string; + queryBuilderRef: React.RefObject; +} + +const PlaylistQueryEditor = ({ + createPlaylistMutation, + detailQuery, + handleSave, + handleSaveAs, + isQueryBuilderExpanded, + onToggleExpand, + playlistId, + queryBuilderRef, +}: PlaylistQueryEditorProps) => { + const { t } = useTranslation(); + + const openPreviewModal = useCallback(() => { + if (!isQueryBuilderExpanded) { + return; + } + + const filters = queryBuilderRef.current?.getFilters(); + + if (!filters) { + return; + } + + const queryValue = convertQueryGroupToNDQuery(filters.filters); + const sortString = filters.extraFilters.sortBy?.[0]; + + const previewValue = { + ...queryValue, + ...(filters.extraFilters.limit && { limit: filters.extraFilters.limit }), + ...(sortString && { sort: sortString }), + }; + + openModal({ + children: , + size: 'xl', + title: t('common.preview', { postProcess: 'titleCase' }), + }); + }, [isQueryBuilderExpanded, queryBuilderRef, t]); + + const openSaveAndReplaceModal = useCallback(() => { + if (!isQueryBuilderExpanded) { + return; + } + + const filters = queryBuilderRef.current?.getFilters(); + + if (!filters) { + return; + } + + openModal({ + children: ( + { + handleSave( + convertQueryGroupToNDQuery(filters.filters), + filters.extraFilters, + ); + closeAllModals(); + }} + > + {t('common.areYouSure', { postProcess: 'sentenceCase' })} + + ), + title: t('common.saveAndReplace', { postProcess: 'sentenceCase' }), + }); + }, [isQueryBuilderExpanded, queryBuilderRef, handleSave, t]); + + const parseSortBy = useCallback((): string[] => { + const sort = detailQuery?.data?.rules?.sort; + // Handle new syntax: comma-separated with +/- prefix + // e.g., "+album,-year" -> return as single string in array + if (typeof sort === 'string') { + // Check if it's new syntax (has +/- prefix or commas) + if (sort.includes(',') || sort.startsWith('+') || sort.startsWith('-')) { + return [sort]; + } + // Old syntax: single field, convert to new format with default order + const order = detailQuery?.data?.rules?.order || 'asc'; + const prefix = order === 'desc' ? '-' : '+'; + return [`${prefix}${sort}`]; + } + if (Array.isArray(sort)) { + // If array, check if first item has +/- prefix + if ( + sort.length > 0 && + typeof sort[0] === 'string' && + (sort[0].startsWith('+') || sort[0].startsWith('-')) + ) { + return sort; + } + // Old array format, convert to new format + const order = detailQuery?.data?.rules?.order || 'asc'; + const prefix = order === 'desc' ? '-' : '+'; + return sort.map((s) => `${prefix}${s}`); + } + return ['+dateAdded']; + }, [detailQuery?.data?.rules?.order, detailQuery?.data?.rules?.sort]); + + const parseSortOrder = useCallback((): 'asc' | 'desc' => { + const sort = detailQuery?.data?.rules?.sort; + if (typeof sort === 'string' && sort.startsWith('-')) { + return 'desc'; + } + // Fall back to old order field or default + return detailQuery?.data?.rules?.order || 'asc'; + }, [detailQuery?.data?.rules?.order, detailQuery?.data?.rules?.sort]); + + return ( + + + + + + + {t('form.queryEditor.title', { + postProcess: 'titleCase', + })} + + + + + + + + +
+ +
+
+
+ ); +}; + const PlaylistDetailSongListRoute = () => { const { t } = useTranslation(); const navigate = useNavigate(); @@ -186,35 +393,6 @@ const PlaylistDetailSongListRoute = () => { }); }; - const openSaveAndReplaceModal = () => { - if (!isQueryBuilderExpanded) { - return; - } - - const filters = queryBuilderRef.current?.getFilters(); - - if (!filters) { - return; - } - - openModal({ - children: ( - { - handleSave( - convertQueryGroupToNDQuery(filters.filters), - filters.extraFilters, - ); - closeAllModals(); - }} - > - {t('common.areYouSure', { postProcess: 'sentenceCase' })} - - ), - title: t('common.saveAndReplace', { postProcess: 'sentenceCase' }), - }); - }; - const isSmartPlaylist = !detailQuery?.isLoading && detailQuery?.data?.rules && @@ -233,34 +411,6 @@ const PlaylistDetailSongListRoute = () => { setIsQueryBuilderExpanded(true); }; - const openPreviewModal = () => { - if (!isQueryBuilderExpanded) return; - const filters = queryBuilderRef.current?.getFilters(); - if (!filters) { - toast.error({ - message: - t('error.queryBuilderNotReady', { postProcess: 'sentenceCase' }) || - 'Query builder is not ready. Please expand it first.', - }); - return; - } - - const queryValue = convertQueryGroupToNDQuery(filters.filters); - const sortString = filters.extraFilters.sortBy?.[0]; - - const previewValue = { - ...queryValue, - ...(filters.extraFilters.limit && { limit: filters.extraFilters.limit }), - ...(sortString && { sort: sortString }), - }; - - openModal({ - children: , - size: 'xl', - title: t('common.preview', { postProcess: 'titleCase' }), - }); - }; - const playlistSongs = useQuery( playlistsQueries.songList({ query: { @@ -294,139 +444,32 @@ const PlaylistDetailSongListRoute = () => { return ( - - { - if (!isSmartPlaylist) { - setShowQueryBuilder(true); - setIsQueryBuilderExpanded(true); - } - }} - onDelete={() => openDeletePlaylistModal()} - onToggleQueryBuilder={handleToggleShowQueryBuilder} - /> - {(isSmartPlaylist || showQueryBuilder) && ( - - - - - - - {t('form.queryEditor.title', { - postProcess: 'titleCase', - })} - - - - - - - - -
- { - const sort = detailQuery?.data?.rules?.sort; - // Handle new syntax: comma-separated with +/- prefix - // e.g., "+album,-year" -> return as single string in array - if (typeof sort === 'string') { - // Check if it's new syntax (has +/- prefix or commas) - if ( - sort.includes(',') || - sort.startsWith('+') || - sort.startsWith('-') - ) { - return [sort]; - } - // Old syntax: single field, convert to new format with default order - const order = - detailQuery?.data?.rules?.order || 'asc'; - const prefix = order === 'desc' ? '-' : '+'; - return [`${prefix}${sort}`]; - } - if (Array.isArray(sort)) { - // If array, check if first item has +/- prefix - if ( - sort.length > 0 && - typeof sort[0] === 'string' && - (sort[0].startsWith('+') || - sort[0].startsWith('-')) - ) { - return sort; - } - // Old array format, convert to new format - const order = - detailQuery?.data?.rules?.order || 'asc'; - const prefix = order === 'desc' ? '-' : '+'; - return sort.map((s) => `${prefix}${s}`); - } - return ['+dateAdded']; - })()} - sortOrder={(() => { - const sort = detailQuery?.data?.rules?.sort; - if (typeof sort === 'string' && sort.startsWith('-')) { - return 'desc'; - } - // Fall back to old order field or default - return detailQuery?.data?.rules?.order || 'asc'; - })()} - /> -
-
-
- )} - -
+ { + if (!isSmartPlaylist) { + setShowQueryBuilder(true); + setIsQueryBuilderExpanded(true); + } + }} + onDelete={() => openDeletePlaylistModal()} + onToggleQueryBuilder={handleToggleShowQueryBuilder} + /> + {(isSmartPlaylist || showQueryBuilder) && ( + + + + )} +
);