import { t } from 'i18next'; import { MouseEvent, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PlaylistQueryBuilder, PlaylistQueryBuilderRef, } from '/@/renderer/features/playlists/components/playlist-query-builder'; import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-to-playlist-mutation'; import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation'; import { convertQueryGroupToNDQuery } from '/@/renderer/features/playlists/utils'; import { useCurrentServer } from '/@/renderer/store'; import { hasFeature } from '/@/shared/api/utils'; import { Group } from '/@/shared/components/group/group'; import { closeAllModals, openModal } from '/@/shared/components/modal/modal'; import { ModalButton } from '/@/shared/components/modal/model-shared'; import { Stack } from '/@/shared/components/stack/stack'; import { Switch } from '/@/shared/components/switch/switch'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { Text } from '/@/shared/components/text/text'; import { Textarea } from '/@/shared/components/textarea/textarea'; import { toast } from '/@/shared/components/toast/toast'; import { useForm } from '/@/shared/hooks/use-form'; import { CreatePlaylistBody, ServerListItem, ServerType, Song, SongListSort, } from '/@/shared/types/domain-types'; import { ServerFeature } from '/@/shared/types/features-types'; interface CreatePlaylistFormProps { onCancel: () => void; songs?: Song[]; } export const CreatePlaylistForm = ({ onCancel, songs }: CreatePlaylistFormProps) => { const { t } = useTranslation(); const createPlaylistMutation = useCreatePlaylist({}); const addToPlaylistMutation = useAddToPlaylist({}); const server = useCurrentServer(); const queryBuilderRef = useRef(null); const form = useForm({ initialValues: { comment: '', name: '', queryBuilderRules: undefined, }, }); const [isSmartPlaylist, setIsSmartPlaylist] = useState(false); const [step, setStep] = useState<1 | 2>(1); const isPrefilledPlaylist = !!songs && songs.length > 0; const handleSubmit = form.onSubmit((values) => { if (!server) return; // If creating a smart playlist and we're on the first step, advance to step 2 // to configure the query instead of submitting immediately. if (isSmartPlaylist && step === 1) { setStep(2); return; } const smartPlaylist = queryBuilderRef.current?.getFilters(); // 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 = isSmartPlaylist && smartPlaylist?.extraFilters?.sortBy?.[0] ? smartPlaylist.extraFilters.sortBy[0] : undefined; const rules = isSmartPlaylist && smartPlaylist?.filters ? { ...convertQueryGroupToNDQuery(smartPlaylist.filters), limit: smartPlaylist.extraFilters.limit, limitPercent: smartPlaylist.extraFilters.limitPercent, // order field is now optional - sort direction is embedded in sort field sort: sortValue || '+dateAdded', } : undefined; createPlaylistMutation.mutate( { apiClientProps: { serverId: server.id }, body: { ...values, ...(rules ? { queryBuilderRules: rules } : {}), }, }, { onError: (err) => { toast.error({ message: err.message, title: t('error.genericError'), }); }, onSuccess: (data) => { toast.success({ message: t('form.createPlaylist.success'), }); handlePlaylistPrefilling(data?.id); onCancel(); }, }, ); }); const handlePlaylistPrefilling = (playlistId?: string) => { if (!songs || !playlistId) { return; } const allSongIds = songs.map((song) => song.id); addToPlaylistMutation.mutate( { apiClientProps: { serverId: server.id }, body: { songId: allSongIds }, query: { id: playlistId }, }, { onError: (err) => { toast.error({ message: `${err.message}`, title: t('error.genericError'), }); }, }, ); }; const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST); const isSubmitDisabled = !form.values.name || createPlaylistMutation.isPending; return (
{step === 1 && ( <> {server?.type === ServerType.NAVIDROME && (