diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 7c664d97d..8a7cdd148 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1665,6 +1665,7 @@ "description": "Show the current streams on your media servers", "option": {}, "items": { + "currentlyPlaying": "Currently playing", "user": "User", "name": "Name", "id": "Id" diff --git a/packages/widgets/src/media-server/component.tsx b/packages/widgets/src/media-server/component.tsx index a52c0ee8f..146f7915f 100644 --- a/packages/widgets/src/media-server/component.tsx +++ b/packages/widgets/src/media-server/component.tsx @@ -1,8 +1,9 @@ "use client"; +import type { ReactNode } from "react"; import { useMemo } from "react"; -import { Avatar, Box, Flex, Group, Stack, Text, Title } from "@mantine/core"; -import { IconDeviceAudioTape, IconDeviceTv, IconMovie, IconVideo } from "@tabler/icons-react"; +import { Avatar, Flex, Group, Stack, Text, Title } from "@mantine/core"; +import { IconDeviceTv, IconHeadphones, IconMovie, IconVideo } from "@tabler/icons-react"; import type { MRT_ColumnDef } from "mantine-react-table"; import { MantineReactTable } from "mantine-react-table"; @@ -11,6 +12,7 @@ import { getIconUrl, integrationDefs } from "@homarr/definitions"; import type { StreamSession } from "@homarr/integrations"; import { createModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; +import type { TablerIcon } from "@homarr/ui"; import { useTranslatedMantineReactTable } from "@homarr/ui/hooks"; import type { WidgetComponentProps } from "../definition"; @@ -28,59 +30,51 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget ); const utils = clientApi.useUtils(); + const t = useScopedI18n("widget.mediaServer"); const columns = useMemo[]>( () => [ { accessorKey: "sessionName", - header: "Name", - mantineTableHeadCellProps: { - style: { - width: "30%", - }, - }, + header: t("items.name"), + Cell: ({ row }) => ( - + {row.original.sessionName} ), }, { accessorKey: "user.username", - header: "User", - mantineTableHeadCellProps: { - style: { - width: "25%", - }, - }, + header: t("items.user"), + Cell: ({ row }) => ( - - - {row.original.user.username} + + + {row.original.user.username} ), }, { accessorKey: "currentlyPlaying", // currentlyPlaying.name can be undefined which results in a warning. This is why we use currentlyPlaying instead of currentlyPlaying.name - header: "Currently playing", - mantineTableHeadCellProps: { - style: { - width: "45%", - }, - }, - Cell: ({ row }) => { - if (row.original.currentlyPlaying) { - return ( - - {row.original.currentlyPlaying.name} - - ); - } + header: t("items.currentlyPlaying"), - return null; + Cell: ({ row }) => { + if (!row.original.currentlyPlaying) return null; + + const Icon = mediaTypeIconMap[row.original.currentlyPlaying.type]; + + return ( + + + + {row.original.currentlyPlaying.name} + + + ); }, }, ], - [], + [t], ); clientApi.widget.mediaServer.subscribeToCurrentStreams.useSubscription( @@ -137,8 +131,18 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget enableDensityToggle: false, enableFilters: false, enableHiding: false, + enableColumnPinning: true, initialState: { density: "xs", + columnPinning: { + right: ["currentlyPlaying"], + }, + }, + mantineTableHeadProps: { + fz: "xs", + }, + mantineTableHeadCellProps: { + py: 4, }, mantinePaperProps: { flex: 1, @@ -158,20 +162,16 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget }, mantineTableBodyCellProps: ({ row }) => ({ onClick: () => { - openModal({ - item: row.original, - title: - row.original.currentlyPlaying?.type === "movie" ? ( - - ) : row.original.currentlyPlaying?.type === "tv" ? ( - - ) : row.original.currentlyPlaying?.type === "video" ? ( - - ) : ( - - ), - }); + openModal( + { + item: row.original, + }, + { + title: row.original.sessionName, + }, + ); }, + py: 4, }), }); @@ -210,42 +210,64 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget ); } -const itemInfoModal = createModal<{ item: StreamSession; title: React.ReactNode }>(({ innerProps }) => { +const itemInfoModal = createModal<{ item: StreamSession }>(({ innerProps }) => { const t = useScopedI18n("widget.mediaServer.items"); + const Icon = innerProps.item.currentlyPlaying ? mediaTypeIconMap[innerProps.item.currentlyPlaying.type] : null; return ( - {innerProps.title} - {innerProps.item.currentlyPlaying?.name} - - {innerProps.item.currentlyPlaying?.episodeName} - {innerProps.item.currentlyPlaying?.seasonName && ( - <> - {" - "} - {innerProps.item.currentlyPlaying.seasonName} - - )} - + {Icon && innerProps.item.currentlyPlaying !== null && ( + + + {innerProps.item.currentlyPlaying.name} + + )} + {innerProps.item.currentlyPlaying?.episodeName && ( + + {innerProps.item.currentlyPlaying.episodeName} + {innerProps.item.currentlyPlaying.seasonName && ( + <> + {" - "} + {innerProps.item.currentlyPlaying.seasonName} + + )} + + )} - - - + + {" "} + {innerProps.item.user.username} + + } + /> + {innerProps.item.sessionName}} /> + {innerProps.item.sessionId}} /> ); }).withOptions({ defaultTitle() { return ""; }, - size: "auto", + size: "lg", centered: true, }); -const NormalizedLine = ({ itemKey, value }: { itemKey: string; value: string }) => { +const NormalizedLine = ({ itemKey, value }: { itemKey: string; value: ReactNode }) => { return ( {itemKey}: - {value} + {value} ); }; + +const mediaTypeIconMap = { + movie: IconMovie, + tv: IconDeviceTv, + video: IconVideo, + audio: IconHeadphones, +} satisfies Record["type"], TablerIcon>;