Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8d12c19e |
No files matched your search
@@ -16,6 +16,7 @@ import TabItem from "@theme/TabItem";
|
|||||||
import { mediaRequestListWidget } from ".";
|
import { mediaRequestListWidget } from ".";
|
||||||
import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr";
|
import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr";
|
||||||
import { overseerrIntegration } from "@site/docs/integrations/overseerr";
|
import { overseerrIntegration } from "@site/docs/integrations/overseerr";
|
||||||
|
import { seerrIntegration } from "@site/docs/integrations/seerr";
|
||||||
|
|
||||||
<WidgetHeader widget={mediaRequestListWidget} categories={["Media requests"]} />
|
<WidgetHeader widget={mediaRequestListWidget} categories={["Media requests"]} />
|
||||||
|
|
||||||
@@ -39,6 +40,9 @@ import mediaRequestList from "./img/list.png";
|
|||||||
{
|
{
|
||||||
integration: overseerrIntegration,
|
integration: overseerrIntegration,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
integration: seerrIntegration,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -46,6 +50,7 @@ import mediaRequestList from "./img/list.png";
|
|||||||
|
|
||||||
- Approve / decline a request by clicking on the thumbs up or down icon.
|
- Approve / decline a request by clicking on the thumbs up or down icon.
|
||||||
- Hover over the widget to reveal a **search button** that opens Spotlight in media request search mode, scoped to the widget's integrations.
|
- Hover over the widget to reveal a **search button** that opens Spotlight in media request search mode, scoped to the widget's integrations.
|
||||||
|
- When no requests exist yet, the widget offers the same media search action instead of reporting an integration error.
|
||||||
- A warning indicator identifies unavailable integrations without hiding requests returned by healthy integrations.
|
- A warning indicator identifies unavailable integrations without hiding requests returned by healthy integrations.
|
||||||
|
|
||||||
<WidgetInteractionGuide advancedView={false} />
|
<WidgetInteractionGuide advancedView={false} />
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import TabItem from "@theme/TabItem";
|
|||||||
import { mediaRequestStatsWidget } from ".";
|
import { mediaRequestStatsWidget } from ".";
|
||||||
import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr";
|
import { jellyseerrIntegration } from "@site/docs/integrations/jellyseerr";
|
||||||
import { overseerrIntegration } from "@site/docs/integrations/overseerr";
|
import { overseerrIntegration } from "@site/docs/integrations/overseerr";
|
||||||
|
import { seerrIntegration } from "@site/docs/integrations/seerr";
|
||||||
|
|
||||||
<WidgetHeader widget={mediaRequestStatsWidget} categories={["Media requests"]} />
|
<WidgetHeader widget={mediaRequestStatsWidget} categories={["Media requests"]} />
|
||||||
|
|
||||||
@@ -31,6 +32,9 @@ Provides an overview of media requests, including totals, availability, approval
|
|||||||
{
|
{
|
||||||
integration: overseerrIntegration,
|
integration: overseerrIntegration,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
integration: seerrIntegration,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -43,6 +47,7 @@ import mediaRequestStats from "./img/stats.png";
|
|||||||
### Interactions
|
### Interactions
|
||||||
|
|
||||||
- Hover over the widget to reveal a **search button** that opens Spotlight in media request search mode, scoped to the widget's integrations.
|
- Hover over the widget to reveal a **search button** that opens Spotlight in media request search mode, scoped to the widget's integrations.
|
||||||
|
- When no requests exist yet, the widget offers media search and explains when statistics will appear.
|
||||||
- A warning indicator identifies unavailable integrations while totals from healthy integrations remain visible.
|
- A warning indicator identifies unavailable integrations while totals from healthy integrations remain visible.
|
||||||
|
|
||||||
<WidgetInteractionGuide advancedView={false} />
|
<WidgetInteractionGuide advancedView={false} />
|
||||||
|
|||||||
@@ -63,13 +63,19 @@ export type WidgetKind = (typeof widgetKinds)[number];
|
|||||||
|
|
||||||
export const widgetDefaultSizes: Partial<Record<WidgetKind, { width: number; height: number }>> = {
|
export const widgetDefaultSizes: Partial<Record<WidgetKind, { width: number; height: number }>> = {
|
||||||
airQuality: { width: 2, height: 1 },
|
airQuality: { width: 2, height: 1 },
|
||||||
|
calendar: { width: 2, height: 2 },
|
||||||
countdown: { width: 2, height: 1 },
|
countdown: { width: 2, height: 1 },
|
||||||
|
downloads: { width: 4, height: 2 },
|
||||||
|
indexerManager: { width: 2, height: 3 },
|
||||||
timer: { width: 2, height: 1 },
|
timer: { width: 2, height: 1 },
|
||||||
uptimeKuma: { width: 2, height: 3 },
|
uptimeKuma: { width: 2, height: 3 },
|
||||||
audioStats: { width: 2, height: 2 },
|
audioStats: { width: 2, height: 2 },
|
||||||
paperlessNgx: { width: 2, height: 2 },
|
paperlessNgx: { width: 2, height: 2 },
|
||||||
patchmon: { width: 2, height: 2 },
|
patchmon: { width: 2, height: 2 },
|
||||||
mediaMissing: { width: 4, height: 3 },
|
mediaServer: { width: 3, height: 2 },
|
||||||
|
"mediaRequests-requestList": { width: 3, height: 2 },
|
||||||
|
"mediaRequests-requestStats": { width: 2, height: 2 },
|
||||||
|
mediaMissing: { width: 2, height: 2 },
|
||||||
bazarr: { width: 2, height: 2 },
|
bazarr: { width: 2, height: 2 },
|
||||||
assistant: { width: 10, height: 4 },
|
assistant: { width: 10, height: 4 },
|
||||||
};
|
};
|
||||||
@@ -4227,7 +4227,11 @@
|
|||||||
},
|
},
|
||||||
"mediaRequests-requestList": {
|
"mediaRequests-requestList": {
|
||||||
"name": "Media Requests List",
|
"name": "Media Requests List",
|
||||||
"description": "See a list of all media requests from your Overseerr or Jellyseerr instance",
|
"description": "See a list of all media requests from your Seerr, Overseerr, or Jellyseerr instance",
|
||||||
|
"empty": {
|
||||||
|
"title": "No media requests yet",
|
||||||
|
"description": "Search for a movie or show to start a request."
|
||||||
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"linksTargetNewTab": {
|
"linksTargetNewTab": {
|
||||||
"label": "Open links in new tab"
|
"label": "Open links in new tab"
|
||||||
@@ -4267,6 +4271,10 @@
|
|||||||
"mediaRequests-requestStats": {
|
"mediaRequests-requestStats": {
|
||||||
"name": "Media Requests Stats",
|
"name": "Media Requests Stats",
|
||||||
"description": "Statistics about your media requests",
|
"description": "Statistics about your media requests",
|
||||||
|
"empty": {
|
||||||
|
"title": "No request activity yet",
|
||||||
|
"description": "Stats will appear after the first media request."
|
||||||
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
"stats": {
|
"stats": {
|
||||||
"main": "Media Stats",
|
"main": "Media Stats",
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, Stack, Text, ThemeIcon } from "@mantine/core";
|
||||||
|
import { IconMovieOff, IconSearch } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
import { openMediaRequestSearch } from "@homarr/spotlight";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
|
interface MediaRequestsEmptyStateProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
integrationIds: string[];
|
||||||
|
isEditMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MediaRequestsEmptyState = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
integrationIds,
|
||||||
|
isEditMode,
|
||||||
|
}: MediaRequestsEmptyStateProps) => {
|
||||||
|
const tSearch = useI18n("search.mode.media");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack h="100%" align="center" justify="center" gap="xs" p="md">
|
||||||
|
<ThemeIcon variant="light" size="xl" radius="xl">
|
||||||
|
<IconMovieOff />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Stack gap={2} align="center">
|
||||||
|
<Text fw={600} ta="center">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text c="dimmed" size="sm" ta="center" maw="28ch">
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
{!isEditMode ? (
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
size="compact-sm"
|
||||||
|
leftSection={<IconSearch size="var(--mantine-font-size-md)" />}
|
||||||
|
onClick={() => openMediaRequestSearch({ integrationIds })}
|
||||||
|
>
|
||||||
|
{tSearch("action.search.label")}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@ import { WidgetQueryLoadingState } from "../../common/query-state-indicator";
|
|||||||
import actionTargetClasses from "../../common/action-target.module.css";
|
import actionTargetClasses from "../../common/action-target.module.css";
|
||||||
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
|
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
|
||||||
import type { WidgetComponentProps } from "../../definition";
|
import type { WidgetComponentProps } from "../../definition";
|
||||||
import { NoIntegrationDataError } from "../../errors/no-data-integration";
|
import { MediaRequestsEmptyState } from "../empty-state";
|
||||||
import classes from "./component.module.css";
|
import classes from "./component.module.css";
|
||||||
import searchClasses from "../search-button.module.css";
|
import searchClasses from "../search-button.module.css";
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ export default function MediaServerWidget({
|
|||||||
options,
|
options,
|
||||||
width,
|
width,
|
||||||
}: WidgetComponentProps<"mediaRequests-requestList">) {
|
}: WidgetComponentProps<"mediaRequests-requestList">) {
|
||||||
|
const t = useI18n("widget.mediaRequests-requestList");
|
||||||
const interactIntegrationIds = new Set(
|
const interactIntegrationIds = new Set(
|
||||||
useIntegrationsWithInteractAccess()
|
useIntegrationsWithInteractAccess()
|
||||||
.filter(({ id }) => integrationIds.includes(id))
|
.filter(({ id }) => integrationIds.includes(id))
|
||||||
@@ -48,7 +49,16 @@ export default function MediaServerWidget({
|
|||||||
if (isInitialWidgetQueryPending(mediaRequestQuery)) return <WidgetQueryLoadingState />;
|
if (isInitialWidgetQueryPending(mediaRequestQuery)) return <WidgetQueryLoadingState />;
|
||||||
if (!mediaRequestData) return <WidgetEmptyState />;
|
if (!mediaRequestData) return <WidgetEmptyState />;
|
||||||
const { requests: mediaRequests, failedIntegrations } = mediaRequestData;
|
const { requests: mediaRequests, failedIntegrations } = mediaRequestData;
|
||||||
if (mediaRequests.length === 0 && failedIntegrations.length === 0) throw new NoIntegrationDataError();
|
if (mediaRequests.length === 0 && failedIntegrations.length === 0) {
|
||||||
|
return (
|
||||||
|
<MediaRequestsEmptyState
|
||||||
|
title={t("empty.title")}
|
||||||
|
description={t("empty.description")}
|
||||||
|
integrationIds={integrationIds}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
const showIntegrationSource = new Set(mediaRequests.map(({ integrationId }) => integrationId)).size > 1;
|
const showIntegrationSource = new Set(mediaRequests.map(({ integrationId }) => integrationId)).size > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { WidgetQueryLoadingState } from "../../common/query-state-indicator";
|
|||||||
import actionTargetClasses from "../../common/action-target.module.css";
|
import actionTargetClasses from "../../common/action-target.module.css";
|
||||||
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
|
import { IntegrationErrorIndicator } from "../../common/integration-error-indicator";
|
||||||
import type { WidgetComponentProps } from "../../definition";
|
import type { WidgetComponentProps } from "../../definition";
|
||||||
import { NoIntegrationDataError } from "../../errors/no-data-integration";
|
import { MediaRequestsEmptyState } from "../empty-state";
|
||||||
import classes from "./component.module.css";
|
import classes from "./component.module.css";
|
||||||
import searchClasses from "../search-button.module.css";
|
import searchClasses from "../search-button.module.css";
|
||||||
|
|
||||||
@@ -54,8 +54,16 @@ export default function MediaServerWidget({
|
|||||||
requestStats.users.length === 0 &&
|
requestStats.users.length === 0 &&
|
||||||
requestStats.stats.length === 0 &&
|
requestStats.stats.length === 0 &&
|
||||||
requestStats.failedIntegrations.length === 0
|
requestStats.failedIntegrations.length === 0
|
||||||
)
|
) {
|
||||||
throw new NoIntegrationDataError();
|
return (
|
||||||
|
<MediaRequestsEmptyState
|
||||||
|
title={t("empty.title")}
|
||||||
|
description={t("empty.description")}
|
||||||
|
integrationIds={integrationIds}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user