mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 04:50:12 +02:00
add new context menu implementation
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import Fuse from 'fuse.js';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
getAlbumArtistSongsById,
|
||||
getAlbumSongsById,
|
||||
getGenreSongsById,
|
||||
getPlaylistSongsById,
|
||||
} from '/@/renderer/features/player/utils';
|
||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||
import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-to-playlist-mutation';
|
||||
import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { LibraryItem, PlaylistListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
|
||||
interface AddToPlaylistActionProps {
|
||||
items: string[];
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
const serverId = useCurrentServerId();
|
||||
const queryClient = useQueryClient();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const addToPlaylistMutation = useAddToPlaylist({});
|
||||
|
||||
const playlistsQuery = useQuery(
|
||||
playlistsQueries.list({
|
||||
query: {
|
||||
_custom: {
|
||||
navidrome: {
|
||||
smart: false,
|
||||
},
|
||||
},
|
||||
sortBy: PlaylistListSort.NAME,
|
||||
sortOrder: SortOrder.ASC,
|
||||
startIndex: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const { recentPlaylistId } = useRecentPlaylists(serverId);
|
||||
|
||||
const fuse = useMemo(() => {
|
||||
if (!playlistsQuery.data?.items) return null;
|
||||
|
||||
return new Fuse(playlistsQuery.data.items, {
|
||||
fieldNormWeight: 1,
|
||||
ignoreLocation: true,
|
||||
keys: ['name'],
|
||||
threshold: 0.3,
|
||||
});
|
||||
}, [playlistsQuery.data?.items]);
|
||||
|
||||
const recentPlaylist = useMemo(() => {
|
||||
if (!playlistsQuery.data?.items || !recentPlaylistId) return null;
|
||||
|
||||
const playlist = playlistsQuery.data.items.find((p) => p.id === recentPlaylistId);
|
||||
if (!playlist) return null;
|
||||
|
||||
if (searchTerm && fuse) {
|
||||
const results = fuse.search(searchTerm);
|
||||
const found = results.find((result) => result.item.id === recentPlaylistId);
|
||||
if (!found) return null;
|
||||
}
|
||||
|
||||
return playlist;
|
||||
}, [playlistsQuery.data?.items, recentPlaylistId, searchTerm, fuse]);
|
||||
|
||||
const filteredPlaylists = useMemo(() => {
|
||||
if (!playlistsQuery.data?.items) return [];
|
||||
if (!searchTerm || !fuse) {
|
||||
// Exclude recent playlist from the list if it exists
|
||||
return recentPlaylistId
|
||||
? playlistsQuery.data.items.filter((p) => p.id !== recentPlaylistId)
|
||||
: playlistsQuery.data.items;
|
||||
}
|
||||
|
||||
const results = fuse.search(searchTerm);
|
||||
const filtered = results.map((result) => result.item);
|
||||
// Exclude recent playlist from the filtered results if it exists
|
||||
return recentPlaylistId ? filtered.filter((p) => p.id !== recentPlaylistId) : filtered;
|
||||
}, [playlistsQuery.data?.items, searchTerm, fuse, recentPlaylistId]);
|
||||
|
||||
const getSongsByAlbum = useCallback(
|
||||
async (albumId: string) => {
|
||||
if (!server) return null;
|
||||
return getAlbumSongsById({
|
||||
id: [albumId],
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
|
||||
const getSongsByArtist = useCallback(
|
||||
async (artistId: string) => {
|
||||
if (!server) return null;
|
||||
return getAlbumArtistSongsById({
|
||||
id: [artistId],
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
|
||||
const getSongsByGenre = useCallback(
|
||||
async (genreIds: string[]) => {
|
||||
if (!server) return null;
|
||||
return getGenreSongsById({
|
||||
id: genreIds,
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
|
||||
const getSongsByPlaylist = useCallback(
|
||||
async (playlistId: string) => {
|
||||
if (!server) return null;
|
||||
return getPlaylistSongsById({
|
||||
id: playlistId,
|
||||
queryClient,
|
||||
server,
|
||||
});
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
|
||||
const handleAddToPlaylist = useCallback(
|
||||
async (playlistId: string) => {
|
||||
if (items.length === 0 || !serverId) return;
|
||||
|
||||
try {
|
||||
let allSongIds: string[] = [];
|
||||
|
||||
if (itemType === LibraryItem.SONG) {
|
||||
allSongIds = items;
|
||||
} else if (itemType === LibraryItem.ALBUM) {
|
||||
for (const id of items) {
|
||||
const songs = await getSongsByAlbum(id);
|
||||
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||
}
|
||||
} else if (
|
||||
itemType === LibraryItem.ALBUM_ARTIST ||
|
||||
itemType === LibraryItem.ARTIST
|
||||
) {
|
||||
for (const id of items) {
|
||||
const songs = await getSongsByArtist(id);
|
||||
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||
}
|
||||
} else if (itemType === LibraryItem.GENRE) {
|
||||
const songs = await getSongsByGenre(items);
|
||||
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||
} else if (itemType === LibraryItem.PLAYLIST) {
|
||||
for (const id of items) {
|
||||
const songs = await getSongsByPlaylist(id);
|
||||
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||
}
|
||||
}
|
||||
|
||||
if (allSongIds.length === 0) {
|
||||
toast.error({
|
||||
message: t('error.noItemsSelected', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
addToPlaylistMutation.mutate(
|
||||
{
|
||||
apiClientProps: { serverId },
|
||||
body: {
|
||||
songId: allSongIds,
|
||||
},
|
||||
query: {
|
||||
id: playlistId,
|
||||
},
|
||||
},
|
||||
{
|
||||
onError: (err) => {
|
||||
toast.error({
|
||||
message: err.message,
|
||||
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
},
|
||||
onSuccess: () => {},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error({
|
||||
message: (error as Error).message,
|
||||
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
addToPlaylistMutation,
|
||||
getSongsByAlbum,
|
||||
getSongsByArtist,
|
||||
getSongsByGenre,
|
||||
getSongsByPlaylist,
|
||||
itemType,
|
||||
items,
|
||||
serverId,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
const handleOpenModal = useCallback(() => {
|
||||
const modalProps: {
|
||||
albumId?: string[];
|
||||
artistId?: string[];
|
||||
genreId?: string[];
|
||||
initialSelectedIds?: string[];
|
||||
playlistId?: string[];
|
||||
songId?: string[];
|
||||
} = {};
|
||||
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
modalProps.albumId = items;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
case LibraryItem.ARTIST:
|
||||
modalProps.artistId = items;
|
||||
break;
|
||||
case LibraryItem.GENRE:
|
||||
modalProps.genreId = items;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST:
|
||||
modalProps.playlistId = items;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST_SONG:
|
||||
case LibraryItem.QUEUE_SONG:
|
||||
case LibraryItem.SONG:
|
||||
modalProps.songId = items;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
openContextModal({
|
||||
innerProps: {
|
||||
itemIds: items,
|
||||
resourceType: itemType,
|
||||
},
|
||||
modal: 'addToPlaylist',
|
||||
title: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}, [itemType, items, t]);
|
||||
|
||||
if (items.length === 0) return null;
|
||||
|
||||
const searchInput = (
|
||||
<TextInput
|
||||
autoFocus
|
||||
leftSection={<Icon icon="search" />}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
pb="xs"
|
||||
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||
size="sm"
|
||||
value={searchTerm}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu isCloseDisabled>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="playlist"
|
||||
onSelect={handleOpenModal}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent stickyContent={searchInput}>
|
||||
{playlistsQuery.isLoading && (
|
||||
<ContextMenu.Item disabled>
|
||||
<Spinner container />
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
{playlistsQuery.isError && (
|
||||
<ContextMenu.Item disabled>
|
||||
{t('error.genericError', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
{recentPlaylist && (
|
||||
<>
|
||||
<ContextMenu.Item
|
||||
key={recentPlaylist.id}
|
||||
onSelect={() => handleAddToPlaylist(recentPlaylist.id)}
|
||||
>
|
||||
{recentPlaylist.name}
|
||||
</ContextMenu.Item>
|
||||
{filteredPlaylists.length > 0 && <ContextMenu.Divider />}
|
||||
</>
|
||||
)}
|
||||
{filteredPlaylists.length === 0 && !playlistsQuery.isLoading && (
|
||||
<ContextMenu.Item disabled>
|
||||
{t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
{filteredPlaylists.map((playlist) => (
|
||||
<ContextMenu.Item
|
||||
key={playlist.id}
|
||||
onSelect={() => handleAddToPlaylist(playlist.id)}
|
||||
>
|
||||
{playlist.name}
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { ConfirmModal } from '/@/shared/components/modal/modal';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { Playlist } from '/@/shared/types/domain-types';
|
||||
|
||||
interface DeletePlaylistActionProps {
|
||||
items: Playlist[];
|
||||
}
|
||||
|
||||
export const DeletePlaylistAction = ({ items }: DeletePlaylistActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const serverId = useCurrentServerId();
|
||||
const deletePlaylistMutation = useDeletePlaylist({});
|
||||
|
||||
const handleDeletePlaylist = useCallback(() => {
|
||||
if (items.length === 0 || !serverId) return;
|
||||
|
||||
const playlist = items[0];
|
||||
|
||||
deletePlaylistMutation.mutate(
|
||||
{
|
||||
apiClientProps: { serverId },
|
||||
query: { id: playlist.id },
|
||||
},
|
||||
{
|
||||
onError: (err) => {
|
||||
toast.error({
|
||||
message: err.message,
|
||||
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
navigate(AppRoute.PLAYLISTS, { replace: true });
|
||||
toast.success({
|
||||
message: t('action.deletePlaylist', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
closeAllModals();
|
||||
}, [deletePlaylistMutation, items, navigate, serverId, t]);
|
||||
|
||||
const openDeletePlaylistModal = useCallback(() => {
|
||||
if (items.length === 0) return;
|
||||
|
||||
openModal({
|
||||
children: (
|
||||
<ConfirmModal onConfirm={handleDeletePlaylist}>
|
||||
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
|
||||
</ConfirmModal>
|
||||
),
|
||||
title: t('form.deletePlaylist.title', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}, [handleDeletePlaylist, items.length, t]);
|
||||
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<ContextMenu.Item leftIcon="remove" onSelect={openDeletePlaylistModal}>
|
||||
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
|
||||
interface DownloadActionProps {
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
const utils = isElectron() ? window.api.utils : null;
|
||||
|
||||
export const DownloadAction = ({ ids }: DownloadActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const onSelect = useCallback(async () => {
|
||||
if (!utils) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const id of ids) {
|
||||
const downloadUrl = api.controller.getDownloadUrl({
|
||||
apiClientProps: { serverId: server.id },
|
||||
query: { id },
|
||||
});
|
||||
|
||||
utils.download(downloadUrl);
|
||||
}
|
||||
|
||||
toast.success({
|
||||
message: t('action.downloadStarted', {
|
||||
count: ids.length,
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to download items:', error);
|
||||
}
|
||||
}, [ids, server, t]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Item disabled={ids.length > 1} leftIcon="download" onSelect={onSelect}>
|
||||
{t('page.contextMenu.download', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { openModal } from '@mantine/modals';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
ItemDetailsModal,
|
||||
ItemDetailsModalProps,
|
||||
} from '/@/renderer/features/item-details/components/item-details-modal';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
|
||||
interface GetInfoActionProps {
|
||||
disabled?: boolean;
|
||||
item: ItemDetailsModalProps['item'];
|
||||
}
|
||||
|
||||
export const GetInfoAction = ({ disabled, item }: GetInfoActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const onSelect = useCallback(async () => {
|
||||
if (!server) return;
|
||||
|
||||
openModal({
|
||||
children: <ItemDetailsModal item={item} />,
|
||||
size: 'lg',
|
||||
title: item.name || t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}, [item, server, t]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}>
|
||||
{t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { generatePath, useNavigate } from 'react-router';
|
||||
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
LibraryItem,
|
||||
QueueSong,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
interface GoToActionProps {
|
||||
items: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[];
|
||||
}
|
||||
|
||||
export const GoToAction = ({ items }: GoToActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { albumArtists, albumId } = useMemo(() => {
|
||||
const firstItem = items[0];
|
||||
|
||||
if (firstItem._itemType === LibraryItem.ALBUM) {
|
||||
return {
|
||||
albumArtists: firstItem.albumArtists || [],
|
||||
albumId: firstItem.id,
|
||||
};
|
||||
} else if (firstItem._itemType === LibraryItem.SONG) {
|
||||
return {
|
||||
albumArtists: firstItem.albumArtists || [],
|
||||
albumId: firstItem.albumId,
|
||||
};
|
||||
} else if (
|
||||
firstItem._itemType === LibraryItem.ARTIST ||
|
||||
firstItem._itemType === LibraryItem.ALBUM_ARTIST
|
||||
) {
|
||||
return {
|
||||
albumArtists: [{ id: firstItem.id, name: firstItem.name }],
|
||||
albumId: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
albumArtists: [],
|
||||
albumId: null,
|
||||
};
|
||||
}, [items]);
|
||||
|
||||
const handleGoToAlbum = useCallback(() => {
|
||||
if (!albumId) return;
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId }));
|
||||
}, [albumId, navigate]);
|
||||
|
||||
const handleGoToAlbumArtist = useCallback(
|
||||
(albumArtistId: string) => {
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId }));
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const hasAlbum = !!albumId;
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu disabled={items.length !== 1}>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="externalLink"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
{hasAlbum && (
|
||||
<ContextMenu.Item leftIcon="album" onSelect={handleGoToAlbum}>
|
||||
{t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
)}
|
||||
{albumArtists.map((albumArtist) => (
|
||||
<ContextMenu.Item
|
||||
key={albumArtist.id}
|
||||
leftIcon="artist"
|
||||
onSelect={() => handleGoToAlbumArtist(albumArtist.id)}
|
||||
>
|
||||
{`${t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })} ${albumArtist.name}`}
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { QueueSong } from '/@/shared/types/domain-types';
|
||||
|
||||
interface MoveQueueItemsActionProps {
|
||||
items: QueueSong[];
|
||||
}
|
||||
|
||||
export const MoveQueueItemsAction = ({ items }: MoveQueueItemsActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const player = usePlayer();
|
||||
|
||||
const handleMoveToTop = useCallback(() => {
|
||||
player.moveSelectedToTop(items);
|
||||
}, [items, player]);
|
||||
|
||||
const handleMoveToNext = useCallback(() => {
|
||||
player.moveSelectedToNext(items);
|
||||
}, [items, player]);
|
||||
|
||||
const handleMoveToBottom = useCallback(() => {
|
||||
player.moveSelectedToBottom(items);
|
||||
}, [items, player]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="dragVertical"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('page.contextMenu.moveItems', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
<ContextMenu.Item leftIcon="arrowUpToLine" onSelect={handleMoveToTop}>
|
||||
{t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handleMoveToNext}>
|
||||
{t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="arrowDownToLine" onSelect={handleMoveToBottom}>
|
||||
{t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
interface PlayActionProps {
|
||||
ids: string[];
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const PlayAction = ({ ids, itemType }: PlayActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const player = usePlayer();
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const handlePlay = useCallback(
|
||||
(playType: Play) => {
|
||||
if (ids.length === 0 || !serverId) return;
|
||||
player.addToQueueByFetch(serverId, ids, itemType, playType);
|
||||
},
|
||||
[ids, itemType, player, serverId],
|
||||
);
|
||||
|
||||
const handlePlayNow = useCallback(() => {
|
||||
handlePlay(Play.NOW);
|
||||
}, [handlePlay]);
|
||||
|
||||
const handlePlayNext = useCallback(() => {
|
||||
handlePlay(Play.NEXT);
|
||||
}, [handlePlay]);
|
||||
|
||||
const handlePlayLast = useCallback(() => {
|
||||
handlePlay(Play.LAST);
|
||||
}, [handlePlay]);
|
||||
|
||||
const handlePlayShuffled = useCallback(() => {
|
||||
handlePlay(Play.SHUFFLE);
|
||||
}, [handlePlay]);
|
||||
|
||||
if (ids.length === 0) return null;
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="mediaPlay"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayNow}>
|
||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNext}>
|
||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLast}>
|
||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="mediaShuffle" onSelect={handlePlayShuffled}>
|
||||
{t('player.shuffle', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { Song } from '/@/shared/types/domain-types';
|
||||
|
||||
interface RemoveFromPlaylistActionProps {
|
||||
items: Song[];
|
||||
}
|
||||
|
||||
export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const serverId = useCurrentServerId();
|
||||
const { playlistId } = useParams() as { playlistId?: string };
|
||||
const removeFromPlaylistMutation = useRemoveFromPlaylist();
|
||||
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
const handleRemoveFromPlaylist = useCallback(() => {
|
||||
if (ids.length === 0 || !serverId || !playlistId) return;
|
||||
|
||||
removeFromPlaylistMutation.mutate(
|
||||
{
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id: playlistId,
|
||||
songId: ids,
|
||||
},
|
||||
},
|
||||
{
|
||||
onError: (err) => {
|
||||
toast.error({
|
||||
message: err.message,
|
||||
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success({
|
||||
message: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}, [ids, playlistId, removeFromPlaylistMutation, serverId, t]);
|
||||
|
||||
if (ids.length === 0 || !playlistId) return null;
|
||||
|
||||
return (
|
||||
<ContextMenu.Item leftIcon="remove" onSelect={handleRemoveFromPlaylist}>
|
||||
{t('action.removeFromPlaylist', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { QueueSong } from '/@/shared/types/domain-types';
|
||||
|
||||
interface RemoveFromQueueActionProps {
|
||||
items: QueueSong[];
|
||||
}
|
||||
|
||||
export const RemoveFromQueueAction = ({ items }: RemoveFromQueueActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const player = usePlayer();
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
player.clearSelected(items);
|
||||
}, [items, player]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Item leftIcon="remove" onSelect={onSelect}>
|
||||
{t('action.removeFromQueue', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface SetFavoriteActionProps {
|
||||
ids: string[];
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const createFavoriteMutation = useCreateFavorite({});
|
||||
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||
|
||||
const handleAddToFavorites = useCallback(() => {
|
||||
if (ids.length === 0 || !serverId) return;
|
||||
|
||||
createFavoriteMutation.mutate({
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id: ids,
|
||||
type: itemType,
|
||||
},
|
||||
});
|
||||
}, [createFavoriteMutation, ids, itemType, serverId]);
|
||||
|
||||
const handleRemoveFromFavorites = useCallback(() => {
|
||||
if (ids.length === 0 || !serverId) return;
|
||||
|
||||
deleteFavoriteMutation.mutate({
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id: ids,
|
||||
type: itemType,
|
||||
},
|
||||
});
|
||||
}, [deleteFavoriteMutation, ids, itemType, serverId]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="favorite"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('common.favorite', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
<ContextMenu.Item leftIcon="favorite" onSelect={handleAddToFavorites}>
|
||||
{t('action.addToFavorites', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item leftIcon="unfavorite" onSelect={handleRemoveFromFavorites}>
|
||||
{t('action.removeFromFavorites', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface SetRatingActionProps {
|
||||
ids: string[];
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const SetRatingAction = ({ ids, itemType }: SetRatingActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const setRatingMutation = useSetRating({});
|
||||
|
||||
const onRating = (rating: number) => {
|
||||
setRatingMutation.mutate({
|
||||
apiClientProps: { serverId },
|
||||
query: {
|
||||
id: ids,
|
||||
rating,
|
||||
type: itemType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="star"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('action.setRating', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
<ContextMenu.Item onSelect={() => onRating(0)}>
|
||||
<Rating readOnly value={0} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => onRating(1)}>
|
||||
<Rating readOnly value={1} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => onRating(2)}>
|
||||
<Rating readOnly value={2} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => onRating(3)}>
|
||||
<Rating readOnly value={3} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => onRating(4)}>
|
||||
<Rating readOnly value={4} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => onRating(5)}>
|
||||
<Rating readOnly value={5} />
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface ShareActionProps {
|
||||
ids: string[];
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const ShareAction = ({ ids, itemType }: ShareActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const resourceType = useMemo(() => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
return 'album';
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return 'albumArtist';
|
||||
case LibraryItem.PLAYLIST:
|
||||
return 'playlist';
|
||||
case LibraryItem.SONG:
|
||||
return 'song';
|
||||
default:
|
||||
return 'song';
|
||||
}
|
||||
}, [itemType]);
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
openContextModal({
|
||||
innerProps: {
|
||||
itemIds: ids,
|
||||
resourceType,
|
||||
},
|
||||
modal: 'shareItem',
|
||||
title: t('page.contextMenu.shareItem', { postProcess: 'titleCase' }),
|
||||
});
|
||||
}, [ids, resourceType, t]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Item leftIcon="share" onSelect={onSelect}>
|
||||
{t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { QueueSong } from '/@/shared/types/domain-types';
|
||||
|
||||
interface ShuffleItemsActionProps {
|
||||
items: QueueSong[];
|
||||
}
|
||||
|
||||
export const ShuffleItemsAction = ({ items }: ShuffleItemsActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const player = usePlayer();
|
||||
|
||||
const handleShuffleSelected = useCallback(() => {
|
||||
player.shuffleSelected(items);
|
||||
}, [items, player]);
|
||||
|
||||
const handleShuffleAll = useCallback(() => {
|
||||
player.shuffleAll();
|
||||
}, [player]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.Item
|
||||
leftIcon="mediaShuffle"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
rightIcon="arrowRightS"
|
||||
>
|
||||
{t('action.shuffle', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuTarget>
|
||||
<ContextMenu.SubmenuContent>
|
||||
<ContextMenu.Item onSelect={handleShuffleSelected}>
|
||||
{t('action.shuffleSelected', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={handleShuffleAll}>
|
||||
{t('action.shuffleAll', { postProcess: 'sentenceCase' })}
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.SubmenuContent>
|
||||
</ContextMenu.Submenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { AlbumArtistContextMenu } from '/@/renderer/features/context-menu/menus/album-artist-context-menu';
|
||||
import { AlbumContextMenu } from '/@/renderer/features/context-menu/menus/album-context-menu';
|
||||
import { ArtistContextMenu } from '/@/renderer/features/context-menu/menus/artist-context-menu';
|
||||
import { GenreContextMenu } from '/@/renderer/features/context-menu/menus/genre-context-menu';
|
||||
import { PlaylistContextMenu } from '/@/renderer/features/context-menu/menus/playlist-context-menu';
|
||||
import { PlaylistSongContextMenu } from '/@/renderer/features/context-menu/menus/playlist-song-context-menu';
|
||||
import { QueueContextMenu } from '/@/renderer/features/context-menu/menus/queue-context-menu';
|
||||
import { SongContextMenu } from '/@/renderer/features/context-menu/menus/song-context-menu';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
Genre,
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
QueueSong,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
interface ContextMenuControllerProps {
|
||||
cmd: ContextMenuCommand;
|
||||
event: React.MouseEvent<unknown>;
|
||||
}
|
||||
|
||||
export const ContextMenuController = createCallable<ContextMenuControllerProps, void>(
|
||||
({ call, cmd, event }) => {
|
||||
const { libraryId } = useParams() as { libraryId: string };
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
const isExecuted = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExecuted.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!triggerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleContextMenu = () => {
|
||||
event.preventDefault();
|
||||
|
||||
triggerRef.current?.dispatchEvent(
|
||||
new MouseEvent('contextmenu', {
|
||||
bubbles: true,
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
isExecuted.current = true;
|
||||
|
||||
handleContextMenu();
|
||||
}, [call, cmd, event, event.clientX, event.clientY, libraryId, queryClient]);
|
||||
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Target>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
style={{
|
||||
height: 0,
|
||||
left: 0,
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
userSelect: 'none',
|
||||
width: 0,
|
||||
}}
|
||||
/>
|
||||
</ContextMenu.Target>
|
||||
{cmd.type === LibraryItem.QUEUE_SONG && <QueueContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.ALBUM && <AlbumContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.ALBUM_ARTIST && <AlbumArtistContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.ARTIST && <ArtistContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.GENRE && <GenreContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.PLAYLIST && <PlaylistContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.PLAYLIST_SONG && <PlaylistSongContextMenu {...cmd} />}
|
||||
{cmd.type === LibraryItem.SONG && <SongContextMenu {...cmd} />}
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export type ContextMenuCommand =
|
||||
| AlbumArtistContextMenuProps
|
||||
| AlbumContextMenuProps
|
||||
| ArtistContextMenuProps
|
||||
| GenreContextMenuProps
|
||||
| PlaylistContextMenuProps
|
||||
| PlaylistSongContextMenuProps
|
||||
| QueueSongContextMenuProps
|
||||
| SongContextMenuProps;
|
||||
|
||||
type AlbumArtistContextMenuProps = {
|
||||
items: AlbumArtist[];
|
||||
type: LibraryItem.ALBUM_ARTIST;
|
||||
};
|
||||
|
||||
type AlbumContextMenuProps = {
|
||||
items: Album[];
|
||||
type: LibraryItem.ALBUM;
|
||||
};
|
||||
|
||||
type ArtistContextMenuProps = {
|
||||
items: Artist[];
|
||||
type: LibraryItem.ARTIST;
|
||||
};
|
||||
|
||||
type GenreContextMenuProps = {
|
||||
items: Genre[];
|
||||
type: LibraryItem.GENRE;
|
||||
};
|
||||
|
||||
type PlaylistContextMenuProps = {
|
||||
items: Playlist[];
|
||||
type: LibraryItem.PLAYLIST;
|
||||
};
|
||||
|
||||
type PlaylistSongContextMenuProps = {
|
||||
items: Song[];
|
||||
type: LibraryItem.PLAYLIST_SONG;
|
||||
};
|
||||
|
||||
type QueueSongContextMenuProps = {
|
||||
items: QueueSong[];
|
||||
type: LibraryItem.QUEUE_SONG;
|
||||
};
|
||||
|
||||
type SongContextMenuProps = {
|
||||
items: Song[];
|
||||
type: LibraryItem.SONG;
|
||||
};
|
||||
@@ -1,123 +1,364 @@
|
||||
import { SetContextMenuItems } from '/@/renderer/features/context-menu/events';
|
||||
import React from 'react';
|
||||
|
||||
export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ divider: true, id: 'removeFromQueue' },
|
||||
{ id: 'moveToNextOfQueue' },
|
||||
{ id: 'moveToBottomOfQueue' },
|
||||
{ divider: true, id: 'moveToTopOfQueue' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ divider: true, id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, id: 'setRating' },
|
||||
{ disabled: false, divider: true, id: 'deselectAll' },
|
||||
{ id: 'download' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ id: 'goToAlbum' },
|
||||
{ id: 'goToAlbumArtist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
import { AppIconSelection } from '/@/shared/components/icon/icon';
|
||||
|
||||
export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ id: 'playShuffled' },
|
||||
{ divider: true, id: 'playSimilarSongs' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ divider: true, id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, divider: true, id: 'setRating' },
|
||||
{ id: 'download' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ id: 'goToAlbum' },
|
||||
{ id: 'goToAlbumArtist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
export enum ContextMenuItemKey {
|
||||
ADD_TO_FAVORITES = 'addToFavorites',
|
||||
ADD_TO_PLAYLIST = 'addToPlaylist',
|
||||
CREATE_PLAYLIST = 'createPlaylist',
|
||||
DELETE_PLAYLIST = 'deletePlaylist',
|
||||
DESELECT_ALL = 'deselectAll',
|
||||
DOWNLOAD = 'download',
|
||||
GO_TO_ALBUM = 'goToAlbum',
|
||||
GO_TO_ALBUM_ARTIST = 'goToAlbumArtist',
|
||||
MOVE_TO_BOTTOM_OF_QUEUE = 'moveToBottomOfQueue',
|
||||
MOVE_TO_NEXT_OF_QUEUE = 'moveToNextOfQueue',
|
||||
MOVE_TO_TOP_OF_QUEUE = 'moveToTopOfQueue',
|
||||
PLAY = 'play',
|
||||
PLAY_LAST = 'playLast',
|
||||
PLAY_NEXT = 'playNext',
|
||||
PLAY_SHUFFLED = 'playShuffled',
|
||||
PLAY_SIMILAR_SONGS = 'playSimilarSongs',
|
||||
REMOVE_FROM_FAVORITES = 'removeFromFavorites',
|
||||
REMOVE_FROM_PLAYLIST = 'removeFromPlaylist',
|
||||
REMOVE_FROM_QUEUE = 'removeFromQueue',
|
||||
SET_RATING = 'setRating',
|
||||
SET_RATING_1 = 'setRating1',
|
||||
SET_RATING_2 = 'setRating2',
|
||||
SET_RATING_3 = 'setRating3',
|
||||
SET_RATING_4 = 'setRating4',
|
||||
SET_RATING_5 = 'setRating5',
|
||||
SHARE_ITEM = 'shareItem',
|
||||
SHOW_DETAILS = 'showDetails',
|
||||
}
|
||||
|
||||
export const SONG_ALBUM_PAGE: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
];
|
||||
export type ContextMenuHandlers = Partial<Record<ContextMenuItemKeys, () => void>>;
|
||||
|
||||
export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ id: 'playShuffled' },
|
||||
{ divider: true, id: 'playSimilarSongs' },
|
||||
{ id: 'addToPlaylist' },
|
||||
{ divider: true, id: 'removeFromPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ divider: true, id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, id: 'setRating' },
|
||||
{ id: 'download' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ id: 'goToAlbum' },
|
||||
{ id: 'goToAlbumArtist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
export interface ContextMenuItem {
|
||||
disabled?: boolean;
|
||||
hidden?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
items?: ContextMenuItem[];
|
||||
key: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'playSimilarSongs' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ divider: true, id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, id: 'setRating' },
|
||||
{ id: 'download' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ id: 'goToAlbum' },
|
||||
{ id: 'goToAlbumArtist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
export type ContextMenuItemDefinition = {
|
||||
children?: ContextMenuItemDefinition[];
|
||||
disabled?: boolean;
|
||||
key: ContextMenuItemKeys;
|
||||
};
|
||||
|
||||
export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, divider: true, id: 'setRating' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ id: 'goToAlbumArtist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
export type ContextMenuItemKeys =
|
||||
| ContextMenuItemKey.ADD_TO_FAVORITES
|
||||
| ContextMenuItemKey.ADD_TO_PLAYLIST
|
||||
| ContextMenuItemKey.CREATE_PLAYLIST
|
||||
| ContextMenuItemKey.DELETE_PLAYLIST
|
||||
| ContextMenuItemKey.DESELECT_ALL
|
||||
| ContextMenuItemKey.DOWNLOAD
|
||||
| ContextMenuItemKey.GO_TO_ALBUM
|
||||
| ContextMenuItemKey.GO_TO_ALBUM_ARTIST
|
||||
| ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE
|
||||
| ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE
|
||||
| ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE
|
||||
| ContextMenuItemKey.PLAY
|
||||
| ContextMenuItemKey.PLAY_LAST
|
||||
| ContextMenuItemKey.PLAY_NEXT
|
||||
| ContextMenuItemKey.PLAY_SHUFFLED
|
||||
| ContextMenuItemKey.PLAY_SIMILAR_SONGS
|
||||
| ContextMenuItemKey.REMOVE_FROM_FAVORITES
|
||||
| ContextMenuItemKey.REMOVE_FROM_PLAYLIST
|
||||
| ContextMenuItemKey.REMOVE_FROM_QUEUE
|
||||
| ContextMenuItemKey.SET_RATING
|
||||
| ContextMenuItemKey.SET_RATING_1
|
||||
| ContextMenuItemKey.SET_RATING_2
|
||||
| ContextMenuItemKey.SET_RATING_3
|
||||
| ContextMenuItemKey.SET_RATING_4
|
||||
| ContextMenuItemKey.SET_RATING_5
|
||||
| ContextMenuItemKey.SHARE_ITEM
|
||||
| ContextMenuItemKey.SHOW_DETAILS;
|
||||
|
||||
export const GENRE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
];
|
||||
export type ContextMenuItems = Array<ContextMenuItem>;
|
||||
|
||||
export const ARTIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'addToPlaylist' },
|
||||
{ id: 'addToFavorites' },
|
||||
{ divider: true, id: 'removeFromFavorites' },
|
||||
{ children: true, disabled: false, id: 'setRating' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
const ICON_MAP: Partial<Record<ContextMenuItemKeys, AppIconSelection>> = {
|
||||
[ContextMenuItemKey.ADD_TO_FAVORITES]: 'favorite',
|
||||
[ContextMenuItemKey.ADD_TO_PLAYLIST]: 'playlistAdd',
|
||||
[ContextMenuItemKey.DELETE_PLAYLIST]: 'playlistDelete',
|
||||
[ContextMenuItemKey.DESELECT_ALL]: 'remove',
|
||||
[ContextMenuItemKey.DOWNLOAD]: 'download',
|
||||
[ContextMenuItemKey.GO_TO_ALBUM]: 'album',
|
||||
[ContextMenuItemKey.GO_TO_ALBUM_ARTIST]: 'artist',
|
||||
[ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE]: 'arrowDownToLine',
|
||||
[ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE]: 'mediaPlayNext',
|
||||
[ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE]: 'arrowUpToLine',
|
||||
[ContextMenuItemKey.PLAY]: 'mediaPlay',
|
||||
[ContextMenuItemKey.PLAY_LAST]: 'mediaPlayLast',
|
||||
[ContextMenuItemKey.PLAY_NEXT]: 'mediaPlayNext',
|
||||
[ContextMenuItemKey.PLAY_SHUFFLED]: 'mediaShuffle',
|
||||
[ContextMenuItemKey.PLAY_SIMILAR_SONGS]: 'radio',
|
||||
[ContextMenuItemKey.REMOVE_FROM_FAVORITES]: 'unfavorite',
|
||||
[ContextMenuItemKey.REMOVE_FROM_PLAYLIST]: 'playlistDelete',
|
||||
[ContextMenuItemKey.REMOVE_FROM_QUEUE]: 'delete',
|
||||
[ContextMenuItemKey.SET_RATING]: 'star',
|
||||
[ContextMenuItemKey.SHARE_ITEM]: 'share',
|
||||
[ContextMenuItemKey.SHOW_DETAILS]: 'info',
|
||||
};
|
||||
|
||||
export const PLAYLIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||
{ id: 'play' },
|
||||
{ id: 'playLast' },
|
||||
{ id: 'playNext' },
|
||||
{ divider: true, id: 'playShuffled' },
|
||||
{ divider: true, id: 'shareItem' },
|
||||
{ divider: true, id: 'deletePlaylist' },
|
||||
{ divider: true, id: 'showDetails' },
|
||||
];
|
||||
// export const convertToContextMenuItems = (
|
||||
// definitions: ContextMenuItemDefinition[],
|
||||
// handlers: ContextMenuHandlers,
|
||||
// ): ContextMenuItemOptions[] => {
|
||||
// const items: ContextMenuItemOptions[] = [];
|
||||
|
||||
// for (const def of definitions) {
|
||||
// if ('divider' in def && def.divider) {
|
||||
// items.push({ key: 'divider' });
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (!('key' in def)) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const handler = handlers[def.key];
|
||||
|
||||
// if (!handler) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const icon = ICON_MAP[def.key];
|
||||
// const menuItem: ContextMenuItemOptions = {
|
||||
// disabled: def.disabled,
|
||||
// icon: icon ? <Icon icon={icon} /> : undefined,
|
||||
// key: def.key,
|
||||
// onClick: handler,
|
||||
// };
|
||||
|
||||
// if (def.children) {
|
||||
// menuItem.items = undefined;
|
||||
// }
|
||||
|
||||
// items.push(menuItem);
|
||||
// }
|
||||
|
||||
// // Remove trailing divider
|
||||
// const lastItem = items[items.length - 1];
|
||||
// if (items.length > 0 && lastItem && 'type' in lastItem && lastItem.type === 'divider') {
|
||||
// items.pop();
|
||||
// }
|
||||
|
||||
// return items;
|
||||
// };
|
||||
|
||||
// export const QUEUE_CONTEXT_MENU_ITEMS = (): ContextMenuItemOptions[] => {
|
||||
// return [
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_QUEUE },
|
||||
// // { key: ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE },
|
||||
// // { key: ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE },
|
||||
// // { key: 'divider_1' },
|
||||
// // { key: ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE },
|
||||
// // { key: 'divider_2' },
|
||||
// // { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// // { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// // { key: 'divider_3' },
|
||||
// // { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// // { key: ContextMenuItemKey.SET_RATING },
|
||||
// // { key: ContextMenuItemKey.DESELECT_ALL },
|
||||
// // { key: 'divider_4' },
|
||||
// // { key: ContextMenuItemKey.DOWNLOAD },
|
||||
// // { key: 'divider_5' },
|
||||
// // { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// // { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||
// // { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||
// // { key: 'divider_6' },
|
||||
// // { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
// };
|
||||
|
||||
// export const SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.PLAY,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.SET_RATING,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
// export const SONG_ALBUM_PAGE: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// ];
|
||||
|
||||
// export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.SET_RATING,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
// export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.SET_RATING,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
// export const ALBUM_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.SET_RATING,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
// export const GENRE_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// ];
|
||||
|
||||
// export const ARTIST_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||
// {
|
||||
// children: [
|
||||
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||
// ],
|
||||
// key: ContextMenuItemKey.SET_RATING,
|
||||
// },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
// export const PLAYLIST_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||
// { key: ContextMenuItemKey.PLAY },
|
||||
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.DELETE_PLAYLIST },
|
||||
// { key: ContextMenuItemKey.DIVIDER },
|
||||
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||
// ];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,74 +8,18 @@ export type ContextMenuEvents = {
|
||||
openContextMenu: (args: OpenContextMenuProps) => void;
|
||||
};
|
||||
|
||||
export type ContextMenuItemType =
|
||||
| 'addToFavorites'
|
||||
| 'addToPlaylist'
|
||||
| 'createPlaylist'
|
||||
| 'deletePlaylist'
|
||||
| 'deselectAll'
|
||||
| 'download'
|
||||
| 'goToAlbum'
|
||||
| 'goToAlbumArtist'
|
||||
| 'moveToBottomOfQueue'
|
||||
| 'moveToNextOfQueue'
|
||||
| 'moveToTopOfQueue'
|
||||
| 'play'
|
||||
| 'playLast'
|
||||
| 'playNext'
|
||||
| 'playShuffled'
|
||||
| 'playSimilarSongs'
|
||||
| 'removeFromFavorites'
|
||||
| 'removeFromPlaylist'
|
||||
| 'removeFromQueue'
|
||||
| 'setRating'
|
||||
| 'shareItem'
|
||||
| 'showDetails';
|
||||
|
||||
export type OpenContextMenuProps = {
|
||||
context?: any;
|
||||
data: any[];
|
||||
dataNodes?: RowNode[];
|
||||
menuItems: SetContextMenuItems;
|
||||
resetGridCache?: () => void;
|
||||
tableApi?: GridOptions['api'];
|
||||
type: LibraryItem;
|
||||
xPos: number;
|
||||
yPos: number;
|
||||
};
|
||||
|
||||
export const CONFIGURABLE_CONTEXT_MENU_ITEMS: ContextMenuItemType[] = [
|
||||
'moveToBottomOfQueue',
|
||||
'moveToTopOfQueue',
|
||||
'play',
|
||||
'playLast',
|
||||
'playNext',
|
||||
'playShuffled',
|
||||
'playSimilarSongs',
|
||||
'addToPlaylist',
|
||||
'removeFromPlaylist',
|
||||
'addToFavorites',
|
||||
'removeFromFavorites',
|
||||
'setRating',
|
||||
'download',
|
||||
'shareItem',
|
||||
'goToAlbum',
|
||||
'goToAlbumArtist',
|
||||
'showDetails',
|
||||
];
|
||||
|
||||
export const CONTEXT_MENU_ITEM_MAPPING: { [k in ContextMenuItemType]?: string } = {
|
||||
moveToBottomOfQueue: 'moveToBottom',
|
||||
moveToTopOfQueue: 'moveToTop',
|
||||
playLast: 'addLast',
|
||||
playNext: 'addNext',
|
||||
export const CONTEXT_MENU_ITEM_MAPPING: { [k in ContextMenuItemKeys]?: string } = {
|
||||
[ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE]: 'moveToBottom',
|
||||
[ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE]: 'moveToTop',
|
||||
[ContextMenuItemKey.PLAY_LAST]: 'addLast',
|
||||
[ContextMenuItemKey.PLAY_NEXT]: 'addNext',
|
||||
};
|
||||
|
||||
export type SetContextMenuItems = {
|
||||
children?: boolean;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
id: ContextMenuItemType;
|
||||
id: ContextMenuItemKeys;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { CellContextMenuEvent, GridApi } from '@ag-grid-community/core';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
|
||||
import { openContextMenu, SetContextMenuItems } from '/@/renderer/features/context-menu/events';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
LibraryItem,
|
||||
QueueSong,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
export const useHandleTableContextMenu = (
|
||||
itemType: LibraryItem,
|
||||
contextMenuItems: SetContextMenuItems,
|
||||
context?: any,
|
||||
) => {
|
||||
const handleContextMenu = (
|
||||
e?: CellContextMenuEvent,
|
||||
gridApi?: GridApi<any>,
|
||||
click?: MouseEvent,
|
||||
) => {
|
||||
let clickEvent: MouseEvent | undefined = click;
|
||||
if (e) {
|
||||
if (!e?.event) return;
|
||||
clickEvent = e?.event as MouseEvent;
|
||||
clickEvent.preventDefault();
|
||||
}
|
||||
|
||||
const api = gridApi || e?.api;
|
||||
|
||||
if (!api) return;
|
||||
|
||||
let selectedNodes = sortBy(api.getSelectedNodes(), ['rowIndex']);
|
||||
let selectedRows = selectedNodes.map((node) => node.data);
|
||||
|
||||
if (e) {
|
||||
if (!e.data?.id) return;
|
||||
|
||||
const shouldReplaceSelected = !selectedNodes
|
||||
.map((node) => node.data.id)
|
||||
.includes(e.data.id);
|
||||
|
||||
if (shouldReplaceSelected) {
|
||||
e.api.deselectAll();
|
||||
e.node.setSelected(true);
|
||||
selectedRows = [e.data];
|
||||
selectedNodes = e.api.getSelectedNodes();
|
||||
}
|
||||
}
|
||||
|
||||
openContextMenu({
|
||||
context,
|
||||
data: selectedRows,
|
||||
dataNodes: selectedNodes,
|
||||
menuItems: contextMenuItems,
|
||||
tableApi: api,
|
||||
type: itemType,
|
||||
xPos: clickEvent?.clientX || 0,
|
||||
yPos: clickEvent?.clientY || 0,
|
||||
});
|
||||
};
|
||||
|
||||
return handleContextMenu;
|
||||
};
|
||||
|
||||
export const useHandleGeneralContextMenu = (
|
||||
itemType: LibraryItem,
|
||||
contextMenuItems: SetContextMenuItems,
|
||||
context?: any,
|
||||
) => {
|
||||
const handleContextMenu = (
|
||||
e: any,
|
||||
data: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[],
|
||||
) => {
|
||||
if (!e) return;
|
||||
const clickEvent = e as MouseEvent;
|
||||
clickEvent.preventDefault();
|
||||
|
||||
openContextMenu({
|
||||
context,
|
||||
data,
|
||||
dataNodes: undefined,
|
||||
menuItems: contextMenuItems,
|
||||
type: itemType,
|
||||
xPos: clickEvent.clientX + 15,
|
||||
yPos: clickEvent.clientY + 5,
|
||||
});
|
||||
};
|
||||
|
||||
return handleContextMenu;
|
||||
};
|
||||
|
||||
export const useHandleGridContextMenu = (
|
||||
itemType: LibraryItem,
|
||||
contextMenuItems: SetContextMenuItems,
|
||||
resetGridCache?: () => void,
|
||||
context?: any,
|
||||
) => {
|
||||
const handleContextMenu = (
|
||||
e: any,
|
||||
data: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[],
|
||||
) => {
|
||||
if (!e) return;
|
||||
const clickEvent = e as MouseEvent;
|
||||
clickEvent.preventDefault();
|
||||
|
||||
openContextMenu({
|
||||
context,
|
||||
data,
|
||||
dataNodes: undefined,
|
||||
menuItems: contextMenuItems,
|
||||
resetGridCache,
|
||||
type: itemType,
|
||||
xPos: clickEvent.clientX + 15,
|
||||
yPos: clickEvent.clientY + 5,
|
||||
});
|
||||
};
|
||||
|
||||
return handleContextMenu;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { AlbumArtist, LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface AlbumArtistContextMenuProps {
|
||||
items: AlbumArtist[];
|
||||
}
|
||||
|
||||
export const AlbumArtistContextMenu = ({ items }: AlbumArtistContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Album, LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface AlbumContextMenuProps {
|
||||
items: Album[];
|
||||
}
|
||||
|
||||
export const AlbumContextMenu = ({ items }: AlbumContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Artist, LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface ArtistContextMenuProps {
|
||||
items: Artist[];
|
||||
}
|
||||
|
||||
export const ArtistContextMenu = ({ items }: ArtistContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Genre, LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface GenreContextMenuProps {
|
||||
items: Genre[];
|
||||
}
|
||||
|
||||
export const GenreContextMenu = ({ items }: GenreContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DeletePlaylistAction } from '/@/renderer/features/context-menu/actions/delete-playlist-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem, Playlist } from '/@/shared/types/domain-types';
|
||||
|
||||
interface PlaylistContextMenuProps {
|
||||
items: Playlist[];
|
||||
}
|
||||
|
||||
export const PlaylistContextMenu = ({ items }: PlaylistContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
<ContextMenu.Divider />
|
||||
<DeletePlaylistAction items={items} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { RemoveFromPlaylistAction } from '/@/renderer/features/context-menu/actions/remove-from-playlist-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
|
||||
interface PlaylistSongContextMenuProps {
|
||||
items: Song[];
|
||||
}
|
||||
|
||||
export const PlaylistSongContextMenu = ({ items }: PlaylistSongContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<RemoveFromPlaylistAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { MoveQueueItemsAction } from '/@/renderer/features/context-menu/actions/move-queue-items-action';
|
||||
import { RemoveFromQueueAction } from '/@/renderer/features/context-menu/actions/remove-from-queue-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ShuffleItemsAction } from '/@/renderer/features/context-menu/actions/shuffle-items-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
||||
|
||||
interface QueueContextMenuProps {
|
||||
items: QueueSong[];
|
||||
}
|
||||
|
||||
export const QueueContextMenu = ({ items }: QueueContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<RemoveFromQueueAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<MoveQueueItemsAction items={items} />
|
||||
<ShuffleItemsAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
|
||||
interface SongContextMenuProps {
|
||||
items: Song[];
|
||||
}
|
||||
|
||||
export const SongContextMenu = ({ items }: SongContextMenuProps) => {
|
||||
const { ids } = useMemo(() => {
|
||||
const ids = items.map((item) => item.id);
|
||||
return { ids };
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<ContextMenu.Content>
|
||||
<PlayAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<DownloadAction ids={ids} />
|
||||
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||
<ContextMenu.Divider />
|
||||
<GoToAction items={items} />
|
||||
<ContextMenu.Divider />
|
||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||
</ContextMenu.Content>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user