fix(media-server): improve responsive styles (#2548)
* fix(media-server): improve responsive styles * fix(media-server): translate table headers
This commit is contained in:
@@ -1665,6 +1665,7 @@
|
|||||||
"description": "Show the current streams on your media servers",
|
"description": "Show the current streams on your media servers",
|
||||||
"option": {},
|
"option": {},
|
||||||
"items": {
|
"items": {
|
||||||
|
"currentlyPlaying": "Currently playing",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"id": "Id"
|
"id": "Id"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Avatar, Box, Flex, Group, Stack, Text, Title } from "@mantine/core";
|
import { Avatar, Flex, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
import { IconDeviceAudioTape, IconDeviceTv, IconMovie, IconVideo } from "@tabler/icons-react";
|
import { IconDeviceTv, IconHeadphones, IconMovie, IconVideo } from "@tabler/icons-react";
|
||||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||||
import { MantineReactTable } 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 type { StreamSession } from "@homarr/integrations";
|
||||||
import { createModal, useModalAction } from "@homarr/modals";
|
import { createModal, useModalAction } from "@homarr/modals";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
import type { TablerIcon } from "@homarr/ui";
|
||||||
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
||||||
|
|
||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
@@ -28,59 +30,51 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget
|
|||||||
);
|
);
|
||||||
const utils = clientApi.useUtils();
|
const utils = clientApi.useUtils();
|
||||||
|
|
||||||
|
const t = useScopedI18n("widget.mediaServer");
|
||||||
const columns = useMemo<MRT_ColumnDef<StreamSession>[]>(
|
const columns = useMemo<MRT_ColumnDef<StreamSession>[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: "sessionName",
|
accessorKey: "sessionName",
|
||||||
header: "Name",
|
header: t("items.name"),
|
||||||
mantineTableHeadCellProps: {
|
|
||||||
style: {
|
|
||||||
width: "30%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
<Text size="md" style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
<Text size="xs" style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
{row.original.sessionName}
|
{row.original.sessionName}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "user.username",
|
accessorKey: "user.username",
|
||||||
header: "User",
|
header: t("items.user"),
|
||||||
mantineTableHeadCellProps: {
|
|
||||||
style: {
|
|
||||||
width: "25%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
<Group gap={"sm"}>
|
<Group gap="xs">
|
||||||
<Avatar src={row.original.user.profilePictureUrl} size={30} />
|
<Avatar size={20} src={row.original.user.profilePictureUrl} />
|
||||||
<Text size="md">{row.original.user.username}</Text>
|
<Text size="xs">{row.original.user.username}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "currentlyPlaying", // currentlyPlaying.name can be undefined which results in a warning. This is why we use currentlyPlaying instead of currentlyPlaying.name
|
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",
|
header: t("items.currentlyPlaying"),
|
||||||
mantineTableHeadCellProps: {
|
|
||||||
style: {
|
|
||||||
width: "45%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Cell: ({ row }) => {
|
|
||||||
if (row.original.currentlyPlaying) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Text lineClamp={1}>{row.original.currentlyPlaying.name}</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
Cell: ({ row }) => {
|
||||||
|
if (!row.original.currentlyPlaying) return null;
|
||||||
|
|
||||||
|
const Icon = mediaTypeIconMap[row.original.currentlyPlaying.type];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<Icon size={16} />
|
||||||
|
<Text size="xs" lineClamp={1}>
|
||||||
|
{row.original.currentlyPlaying.name}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
clientApi.widget.mediaServer.subscribeToCurrentStreams.useSubscription(
|
clientApi.widget.mediaServer.subscribeToCurrentStreams.useSubscription(
|
||||||
@@ -137,8 +131,18 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget
|
|||||||
enableDensityToggle: false,
|
enableDensityToggle: false,
|
||||||
enableFilters: false,
|
enableFilters: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
|
enableColumnPinning: true,
|
||||||
initialState: {
|
initialState: {
|
||||||
density: "xs",
|
density: "xs",
|
||||||
|
columnPinning: {
|
||||||
|
right: ["currentlyPlaying"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mantineTableHeadProps: {
|
||||||
|
fz: "xs",
|
||||||
|
},
|
||||||
|
mantineTableHeadCellProps: {
|
||||||
|
py: 4,
|
||||||
},
|
},
|
||||||
mantinePaperProps: {
|
mantinePaperProps: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -158,20 +162,16 @@ export default function MediaServerWidget({ integrationIds, isEditMode }: Widget
|
|||||||
},
|
},
|
||||||
mantineTableBodyCellProps: ({ row }) => ({
|
mantineTableBodyCellProps: ({ row }) => ({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
openModal({
|
openModal(
|
||||||
item: row.original,
|
{
|
||||||
title:
|
item: row.original,
|
||||||
row.original.currentlyPlaying?.type === "movie" ? (
|
},
|
||||||
<IconMovie size={36} />
|
{
|
||||||
) : row.original.currentlyPlaying?.type === "tv" ? (
|
title: row.original.sessionName,
|
||||||
<IconDeviceTv size={36} />
|
},
|
||||||
) : row.original.currentlyPlaying?.type === "video" ? (
|
);
|
||||||
<IconVideo size={36} />
|
|
||||||
) : (
|
|
||||||
<IconDeviceAudioTape size={36} />
|
|
||||||
),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
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 t = useScopedI18n("widget.mediaServer.items");
|
||||||
|
const Icon = innerProps.item.currentlyPlaying ? mediaTypeIconMap[innerProps.item.currentlyPlaying.type] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
<Flex direction="column" gap="xs" align="center">
|
<Flex direction="column" gap="xs" align="center">
|
||||||
<Title>{innerProps.title}</Title>
|
{Icon && innerProps.item.currentlyPlaying !== null && (
|
||||||
<Title>{innerProps.item.currentlyPlaying?.name}</Title>
|
<Group gap="sm" align="center">
|
||||||
<Group display="flex">
|
<Icon size={24} />
|
||||||
<Title order={3}>{innerProps.item.currentlyPlaying?.episodeName}</Title>
|
<Title order={2}>{innerProps.item.currentlyPlaying.name}</Title>
|
||||||
{innerProps.item.currentlyPlaying?.seasonName && (
|
</Group>
|
||||||
<>
|
)}
|
||||||
{" - "}
|
{innerProps.item.currentlyPlaying?.episodeName && (
|
||||||
<Title order={3}>{innerProps.item.currentlyPlaying.seasonName}</Title>
|
<Group>
|
||||||
</>
|
<Title order={4}>{innerProps.item.currentlyPlaying.episodeName}</Title>
|
||||||
)}
|
{innerProps.item.currentlyPlaying.seasonName && (
|
||||||
</Group>
|
<>
|
||||||
|
{" - "}
|
||||||
|
<Title order={4}>{innerProps.item.currentlyPlaying.seasonName}</Title>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<NormalizedLine itemKey={t("user")} value={innerProps.item.user.username} />
|
<NormalizedLine
|
||||||
<NormalizedLine itemKey={t("name")} value={innerProps.item.sessionName} />
|
itemKey={t("user")}
|
||||||
<NormalizedLine itemKey={t("id")} value={innerProps.item.sessionId} />
|
value={
|
||||||
|
<Group gap="sm" align="center">
|
||||||
|
<Avatar size="sm" src={innerProps.item.user.profilePictureUrl} />{" "}
|
||||||
|
<Text>{innerProps.item.user.username}</Text>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NormalizedLine itemKey={t("name")} value={<Text>{innerProps.item.sessionName}</Text>} />
|
||||||
|
<NormalizedLine itemKey={t("id")} value={<Text>{innerProps.item.sessionId}</Text>} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}).withOptions({
|
}).withOptions({
|
||||||
defaultTitle() {
|
defaultTitle() {
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
size: "auto",
|
size: "lg",
|
||||||
centered: true,
|
centered: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const NormalizedLine = ({ itemKey, value }: { itemKey: string; value: string }) => {
|
const NormalizedLine = ({ itemKey, value }: { itemKey: string; value: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<Group w="100%" align="top" justify="space-between">
|
<Group w="100%" align="top" justify="space-between">
|
||||||
<Text>{itemKey}:</Text>
|
<Text>{itemKey}:</Text>
|
||||||
<Text>{value}</Text>
|
{value}
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mediaTypeIconMap = {
|
||||||
|
movie: IconMovie,
|
||||||
|
tv: IconDeviceTv,
|
||||||
|
video: IconVideo,
|
||||||
|
audio: IconHeadphones,
|
||||||
|
} satisfies Record<Exclude<StreamSession["currentlyPlaying"], null>["type"], TablerIcon>;
|
||||||
|
|||||||
Reference in New Issue
Block a user