From 23c7d0bbf01215b680f346e7eef8072d8b9db09e Mon Sep 17 00:00:00 2001 From: Yossi Hillali Date: Sun, 29 Dec 2024 19:24:38 +0200 Subject: [PATCH] fix(media-server): redesign widget (#1775) * fix: redisign media server widget * fix: reviewed changes * fix: reviewed chenges * fix: add icon title * fix: text resize --- .../api/src/router/widgets/media-server.ts | 1 + packages/translation/src/lang/en.json | 7 +- .../widgets/src/media-server/component.tsx | 181 +++++++++++++++--- 3 files changed, 166 insertions(+), 23 deletions(-) diff --git a/packages/api/src/router/widgets/media-server.ts b/packages/api/src/router/widgets/media-server.ts index c567c21a2..8d3fe4537 100644 --- a/packages/api/src/router/widgets/media-server.ts +++ b/packages/api/src/router/widgets/media-server.ts @@ -21,6 +21,7 @@ export const mediaServerRouter = createTRPCRouter({ const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); return { integrationId: integration.id, + integrationKind: integration.kind, sessions: data, }; }), diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 734919e96..7cc99348b 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1457,7 +1457,12 @@ "mediaServer": { "name": "Current media server streams", "description": "Show the current streams on your media servers", - "option": {} + "option": {}, + "items": { + "user": "User", + "name": "Name", + "id": "Id" + } }, "downloads": { "name": "Download Client", diff --git a/packages/widgets/src/media-server/component.tsx b/packages/widgets/src/media-server/component.tsx index b68b7f09c..6c56a98bc 100644 --- a/packages/widgets/src/media-server/component.tsx +++ b/packages/widgets/src/media-server/component.tsx @@ -1,12 +1,17 @@ "use client"; import { useMemo } from "react"; -import { Avatar, Box, Group, Text } from "@mantine/core"; +import type { MantineStyleProp } from "@mantine/core"; +import { Avatar, Box, Flex, Group, Stack, Text, Title } from "@mantine/core"; +import { IconDeviceAudioTape, IconDeviceTv, IconMovie, IconVideo } from "@tabler/icons-react"; import type { MRT_ColumnDef } from "mantine-react-table"; import { MantineReactTable } from "mantine-react-table"; import { clientApi } from "@homarr/api/client"; +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 { useTranslatedMantineReactTable } from "@homarr/ui/hooks"; import type { WidgetComponentProps } from "../definition"; @@ -29,26 +34,54 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget { accessorKey: "sessionName", header: "Name", + mantineTableHeadCellProps: { + style: { + fontSize: "7cqmin", + padding: "2cqmin", + width: "30%", + }, + }, + Cell: ({ row }) => ( + + {row.original.sessionName} + + ), }, { accessorKey: "user.username", header: "User", + mantineTableHeadCellProps: { + style: { + fontSize: "7cqmin", + padding: "2cqmin", + width: "25%", + }, + }, 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: { + fontSize: "7cqmin", + padding: "2cqmin", + width: "45%", + }, + }, Cell: ({ row }) => { if (row.original.currentlyPlaying) { return ( -
- {row.original.currentlyPlaying.name} -
+ + + {row.original.currentlyPlaying.name} + + ); } @@ -83,49 +116,153 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget // Only render the flat list of sessions when the currentStreams change // Otherwise it will always create a new array reference and cause the table to re-render - const flatSessions = useMemo(() => currentStreams.flatMap((pair) => pair.sessions), [currentStreams]); + const flatSessions = useMemo( + () => + currentStreams.flatMap((pair) => + pair.sessions.map((session) => ({ + ...session, + integrationKind: pair.integrationKind, + integrationName: integrationDefs[pair.integrationKind].name, + integrationIcon: getIconUrl(pair.integrationKind), + })), + ), + [currentStreams], + ); + const baseStyle: MantineStyleProp = { + "--total-width": "calc(100cqw / var(--total-width))", + "--ratio-width": "calc(100cqw / var(--total-width))", + "--space-size": "calc(var(--ratio-width) * 0.1)", //Standard gap and spacing value + "--text-fz": "calc(var(--ratio-width) * 0.45)", //General Font Size + "--icon-size": "calc(var(--ratio-width) * 2 / 3)", //Normal icon size + "--mrt-base-background-color": "transparent", + }; + const { openModal } = useModalAction(itemInfoModal); const table = useTranslatedMantineReactTable({ columns, data: flatSessions, - enableRowSelection: false, + enablePagination: false, + enableTopToolbar: false, + enableBottomToolbar: false, + enableSorting: false, + enableColumnActions: false, + enableStickyHeader: false, enableColumnOrdering: false, + enableRowSelection: false, enableFullScreenToggle: false, enableGlobalFilter: false, enableDensityToggle: false, enableFilters: false, - enablePagination: true, - enableSorting: true, enableHiding: false, - enableTopToolbar: false, - enableColumnActions: false, - enableStickyHeader: true, initialState: { density: "xs", }, mantinePaperProps: { - display: "flex", - h: "100%", + flex: 1, withBorder: false, - style: { - flexDirection: "column", - }, + shadow: undefined, }, mantineTableProps: { + className: "media-server-widget-table", style: { tableLayout: "fixed", }, }, mantineTableContainerProps: { style: { - flexGrow: 5, + height: "100%", }, }, + mantineTableBodyCellProps: ({ row }) => ({ + onClick: () => { + openModal({ + item: row.original, + title: + row.original.currentlyPlaying?.type === "movie" ? ( + + ) : row.original.currentlyPlaying?.type === "tv" ? ( + + ) : row.original.currentlyPlaying?.type === "video" ? ( + + ) : ( + + ), + }); + }, + }), + }); + + const uniqueIntegrations = Array.from(new Set(flatSessions.map((session) => session.integrationKind))).map((kind) => { + const session = flatSessions.find((session) => session.integrationKind === kind); + return { + integrationKind: kind, + integrationIcon: session?.integrationIcon, + integrationName: session?.integrationName, + }; }); return ( - + - + + {uniqueIntegrations.map((integration) => ( + + + + {integration.integrationName} + + + ))} + + ); } + +const itemInfoModal = createModal<{ item: StreamSession; title: React.ReactNode }>(({ innerProps }) => { + const t = useScopedI18n("widget.mediaServer.items"); + + return ( + + + {innerProps.title} + {innerProps.item.currentlyPlaying?.name} + + {innerProps.item.currentlyPlaying?.episodeName} + {innerProps.item.currentlyPlaying?.seasonName && ( + <> + {" - "} + {innerProps.item.currentlyPlaying.seasonName} + + )} + + + + + + + ); +}).withOptions({ + defaultTitle() { + return ""; + }, + size: "auto", + centered: true, +}); + +const NormalizedLine = ({ itemKey, value }: { itemKey: string; value: string }) => { + return ( + + {itemKey}: + {value} + + ); +};