diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index d48ff1482..9836b4ac5 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -9,6 +9,7 @@
"collapseAllFolders": "Collapse all folders",
"expandAllFolders": "Expand all folders",
"createPlaylist": "Create $t(entity.playlist, {\"count\": 1})",
+ "createPlaylistFromQueue": "Create $t(entity.playlist, {\"count\": 1}) from queue",
"createRadioStation": "Create $t(entity.radioStation, {\"count\": 1})",
"deletePlaylist": "Delete $t(entity.playlist, {\"count\": 1})",
"deleteRadioStation": "Delete $t(entity.radioStation, {\"count\": 1})",
@@ -365,6 +366,9 @@
"success": "$t(entity.playlist, {\"count\": 1}) created successfully",
"title": "Create $t(entity.playlist, {\"count\": 1})"
},
+ "createPrefilledPlaylist": {
+ "title": "Create $t(entity.playlist, {\"count\": 1}) from selected songs"
+ },
"createRadioStation": {
"success": "Radio station created successfully",
"title": "Create radio station",
diff --git a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx
index d1e4a53aa..719b2ce31 100644
--- a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx
+++ b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx
@@ -1,6 +1,6 @@
import { useIsFetching } from '@tanstack/react-query';
import { t } from 'i18next';
-import { RefObject, useCallback } from 'react';
+import { MouseEvent, RefObject, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './play-queue-list-controls.module.css';
@@ -10,6 +10,7 @@ import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-
import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { useRestoreQueue, useSaveQueue } from '/@/renderer/features/player/hooks/use-queue-restore';
+import { openCreatePrefilledPlaylistModal } from '/@/renderer/features/playlists/components/create-playlist-form';
import {
ListConfigMenu,
SONG_DISPLAY_TYPES,
@@ -53,6 +54,7 @@ export const PlayQueueListControls = ({
+
@@ -84,6 +86,29 @@ export const PlayQueueListControls = ({
);
};
+const QueuePlaylistIcons = () => {
+ const server = useCurrentServer();
+ const player = usePlayer();
+
+ const handleCreatePlaylistFromQueue = (e: MouseEvent) => {
+ const queueSongs = player.getQueue();
+
+ openCreatePrefilledPlaylistModal(server, queueSongs, e);
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
const QueuePlaybackIcons = ({ tableRef }: { tableRef: RefObject }) => {
const { t } = useTranslation();
const player = usePlayer();
diff --git a/src/renderer/features/player/context/player-context.tsx b/src/renderer/features/player/context/player-context.tsx
index e59c5fa12..88b3dec81 100644
--- a/src/renderer/features/player/context/player-context.tsx
+++ b/src/renderer/features/player/context/player-context.tsx
@@ -60,6 +60,7 @@ export interface PlayerContext {
clearQueue: () => void;
clearSelected: (items: QueueSong[]) => void;
decreaseVolume: (amount: number) => void;
+ getQueue: () => QueueSong[];
increaseVolume: (amount: number) => void;
mediaNext: (toNextAlbum: boolean) => void;
mediaPause: () => void;
@@ -95,6 +96,7 @@ export const PlayerContext = createContext({
clearQueue: () => {},
clearSelected: () => {},
decreaseVolume: () => {},
+ getQueue: () => [],
increaseVolume: () => {},
mediaNext: () => {},
mediaPause: () => {},
@@ -567,6 +569,15 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
[storeActions],
);
+ const getQueue = useCallback(() => {
+ logFn.debug(logMsg[LogCategory.PLAYER].clearQueue, {
+ category: LogCategory.PLAYER,
+ });
+
+ const queue = storeActions.getQueue();
+ return queue.items;
+ }, [storeActions]);
+
const increaseVolume = useCallback(
(amount: number) => {
logFn.debug(logMsg[LogCategory.PLAYER].increaseVolume, {
@@ -853,6 +864,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
clearQueue,
clearSelected,
decreaseVolume,
+ getQueue,
increaseVolume,
mediaNext,
mediaPause,
@@ -887,6 +899,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
clearQueue,
clearSelected,
decreaseVolume,
+ getQueue,
increaseVolume,
mediaNext,
mediaPause,
diff --git a/src/renderer/features/playlists/components/create-playlist-form.tsx b/src/renderer/features/playlists/components/create-playlist-form.tsx
index 462cb0226..d82936567 100644
--- a/src/renderer/features/playlists/components/create-playlist-form.tsx
+++ b/src/renderer/features/playlists/components/create-playlist-form.tsx
@@ -6,6 +6,7 @@ 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';
@@ -24,17 +25,22 @@ 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 }: CreatePlaylistFormProps) => {
+export const CreatePlaylistForm = ({ onCancel, songs }: CreatePlaylistFormProps) => {
const { t } = useTranslation();
- const mutation = useCreatePlaylist({});
+
+ const createPlaylistMutation = useCreatePlaylist({});
+ const addToPlaylistMutation = useAddToPlaylist({});
+
const server = useCurrentServer();
const queryBuilderRef = useRef(null);
@@ -48,6 +54,8 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
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;
@@ -78,7 +86,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
}
: undefined;
- mutation.mutate(
+ createPlaylistMutation.mutate(
{
apiClientProps: { serverId: server.id },
body: {
@@ -93,18 +101,45 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
title: t('error.genericError'),
});
},
- onSuccess: () => {
+ 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 || mutation.isPending;
+ const isSubmitDisabled = !form.values.name || createPlaylistMutation.isPending;
return (