From e58063f45720a59bebf34318d3aed1fff4c74d0e Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:10:58 +0300 Subject: [PATCH] Add actions context menu --- .../features/livetv/api/useDeleteProvider.ts | 22 +++ .../features/livetv/components/Provider.tsx | 138 ++++++++++++++++++ src/apps/dashboard/routes/livetv/index.tsx | 43 ++---- 3 files changed, 171 insertions(+), 32 deletions(-) create mode 100644 src/apps/dashboard/features/livetv/api/useDeleteProvider.ts create mode 100644 src/apps/dashboard/features/livetv/components/Provider.tsx diff --git a/src/apps/dashboard/features/livetv/api/useDeleteProvider.ts b/src/apps/dashboard/features/livetv/api/useDeleteProvider.ts new file mode 100644 index 000000000..826dae5c9 --- /dev/null +++ b/src/apps/dashboard/features/livetv/api/useDeleteProvider.ts @@ -0,0 +1,22 @@ +import { useMutation } from '@tanstack/react-query'; + +import { useApi } from 'hooks/useApi'; +import { queryClient } from 'utils/query/queryClient'; +import { getLiveTvApi } from '@jellyfin/sdk/lib/utils/api/live-tv-api'; +import { LiveTvApiDeleteListingProviderRequest } from '@jellyfin/sdk/lib/generated-client/api/live-tv-api'; + +export const useDeleteProvider = () => { + const { api } = useApi(); + + return useMutation({ + mutationFn: (params: LiveTvApiDeleteListingProviderRequest) => ( + getLiveTvApi(api!) + .deleteListingProvider(params) + ), + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: [ 'NamedConfiguration', 'livetv' ] + }); + } + }); +}; diff --git a/src/apps/dashboard/features/livetv/components/Provider.tsx b/src/apps/dashboard/features/livetv/components/Provider.tsx new file mode 100644 index 000000000..715934742 --- /dev/null +++ b/src/apps/dashboard/features/livetv/components/Provider.tsx @@ -0,0 +1,138 @@ +import React, { useCallback, useRef, useState } from 'react'; +import type { ListingsProviderInfo } from '@jellyfin/sdk/lib/generated-client/models/listings-provider-info'; +import Avatar from '@mui/material/Avatar'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemLink from 'components/ListItemLink'; +import DvrIcon from '@mui/icons-material/Dvr'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import getProviderConfigurationUrl from '../utils/getProviderConfigurationUrl'; +import ListItemText from '@mui/material/ListItemText'; +import getProviderName from '../utils/getProviderName'; +import IconButton from '@mui/material/IconButton'; +import ConfirmDialog from 'components/ConfirmDialog'; +import globalize from 'lib/globalize'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import LocationSearchingIcon from '@mui/icons-material/LocationSearching'; +import DeleteIcon from '@mui/icons-material/Delete'; +import ChannelMapper from 'components/channelMapper/channelMapper'; +import { ServerConnections } from 'lib/jellyfin-apiclient'; +import { useDeleteProvider } from '../api/useDeleteProvider'; + +interface ProviderProps { + provider: ListingsProviderInfo +} + +const Provider = ({ provider }: ProviderProps) => { + const [ isDeleteProviderDialogOpen, setIsDeleteProviderDialogOpen ] = useState(false); + const actionsRef = useRef(null); + const [ anchorEl, setAnchorEl ] = useState(null); + const [ isMenuOpen, setIsMenuOpen ] = useState(false); + const deleteProvider = useDeleteProvider(); + + const showChannelMapper = useCallback(() => { + setAnchorEl(null); + setIsMenuOpen(false); + void new ChannelMapper({ + serverId: ServerConnections.currentApiClient()?.serverId(), + providerId: provider.Id + }).show(); + }, [ provider ]); + + const showContextMenu = useCallback(() => { + setAnchorEl(actionsRef.current); + setIsMenuOpen(true); + }, []); + + const showDeleteDialog = useCallback(() => { + setAnchorEl(null); + setIsMenuOpen(false); + setIsDeleteProviderDialogOpen(true); + }, []); + + const onDeleteProviderDialogCancel = useCallback(() => { + setIsDeleteProviderDialogOpen(false); + }, []); + + const onMenuClose = useCallback(() => { + setAnchorEl(null); + setIsMenuOpen(false); + }, []); + + const onConfirmDelete = useCallback(() => { + if (provider.Id) { + deleteProvider.mutate({ + id: provider.Id + }, { + onSettled: () => { + setIsDeleteProviderDialogOpen(false); + } + }); + } + }, [ deleteProvider, provider ]); + + return ( + <> + + + + + } + > + + + + + + + + + + + + + + {globalize.translate('MapChannels')} + + + + + + {globalize.translate('Delete')} + + + + ); +}; + +export default Provider; diff --git a/src/apps/dashboard/routes/livetv/index.tsx b/src/apps/dashboard/routes/livetv/index.tsx index f8416105d..e0a2566b4 100644 --- a/src/apps/dashboard/routes/livetv/index.tsx +++ b/src/apps/dashboard/routes/livetv/index.tsx @@ -12,7 +12,6 @@ import useLiveTasks from 'apps/dashboard/features/tasks/hooks/useLiveTasks'; import Button from '@mui/material/Button'; import RefreshIcon from '@mui/icons-material/Refresh'; import AddIcon from '@mui/icons-material/Add'; -import DvrIcon from '@mui/icons-material/Dvr'; import { Form, Link, useNavigate } from 'react-router-dom'; import { useStartTask } from 'apps/dashboard/features/tasks/api/useStartTask'; import { TaskState } from '@jellyfin/sdk/lib/generated-client/models/task-state'; @@ -22,12 +21,7 @@ import MenuItem from '@mui/material/MenuItem'; import ListItemText from '@mui/material/ListItemText'; import Alert from '@mui/material/Alert'; import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemLink from 'components/ListItemLink'; -import ListItemAvatar from '@mui/material/ListItemAvatar'; -import Avatar from '@mui/material/Avatar'; -import getProviderConfigurationUrl from 'apps/dashboard/features/livetv/utils/getProviderConfigurationUrl'; -import getProviderName from 'apps/dashboard/features/livetv/utils/getProviderName'; +import Provider from 'apps/dashboard/features/livetv/components/Provider'; const CONFIG_KEY = 'livetv'; @@ -149,31 +143,16 @@ export const Component = () => { - - {config.ListingProviders?.map(provider => ( - - - - - ))} - + {(config.ListingProviders && config.ListingProviders?.length > 0) && ( + + {config.ListingProviders?.map(provider => ( + + ))} + + )} )}