feat: radarr release type to calendar widget (#1256)
* feat: add release type * fix: type check * fix: deepSource * fix: new approach * fix: deepSource * fix: typecheck * fix: reviewed changes
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
|
export const radarrReleaseTypes = ["inCinemas", "digitalRelease", "physicalRelease"] as const;
|
||||||
|
type ReleaseType = (typeof radarrReleaseTypes)[number];
|
||||||
|
|
||||||
export interface CalendarEvent {
|
export interface CalendarEvent {
|
||||||
name: string;
|
name: string;
|
||||||
subName: string;
|
subName: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
|
dates?: { type: ReleaseType; date: Date }[];
|
||||||
description?: string;
|
description?: string;
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
mediaInformation?: {
|
mediaInformation?: {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import type { AtLeastOneOf } from "@homarr/common/types";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import { z } from "@homarr/validation";
|
import { z } from "@homarr/validation";
|
||||||
|
|
||||||
import { Integration } from "../../base/integration";
|
import { Integration } from "../../base/integration";
|
||||||
import type { CalendarEvent } from "../../calendar-types";
|
import type { CalendarEvent } from "../../calendar-types";
|
||||||
|
import { radarrReleaseTypes } from "../../calendar-types";
|
||||||
|
|
||||||
export class RadarrIntegration extends Integration {
|
export class RadarrIntegration extends Integration {
|
||||||
/**
|
/**
|
||||||
@@ -37,19 +39,23 @@ export class RadarrIntegration extends Integration {
|
|||||||
});
|
});
|
||||||
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
||||||
|
|
||||||
return radarrCalendarEvents.map(
|
return radarrCalendarEvents.map((radarrCalendarEvent): CalendarEvent => {
|
||||||
(radarrCalendarEvent): CalendarEvent => ({
|
const dates = radarrReleaseTypes
|
||||||
|
.map((type) => (radarrCalendarEvent[type] ? { type, date: radarrCalendarEvent[type] } : undefined))
|
||||||
|
.filter((date) => date) as AtLeastOneOf<Exclude<CalendarEvent["dates"], undefined>[number]>;
|
||||||
|
return {
|
||||||
name: radarrCalendarEvent.title,
|
name: radarrCalendarEvent.title,
|
||||||
subName: radarrCalendarEvent.originalTitle,
|
subName: radarrCalendarEvent.originalTitle,
|
||||||
description: radarrCalendarEvent.overview,
|
description: radarrCalendarEvent.overview,
|
||||||
thumbnail: this.chooseBestImageAsURL(radarrCalendarEvent),
|
thumbnail: this.chooseBestImageAsURL(radarrCalendarEvent),
|
||||||
date: radarrCalendarEvent.inCinemas,
|
date: dates[0].date,
|
||||||
|
dates,
|
||||||
mediaInformation: {
|
mediaInformation: {
|
||||||
type: "movie",
|
type: "movie",
|
||||||
},
|
},
|
||||||
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
||||||
}),
|
};
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
|
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
|
||||||
@@ -118,7 +124,18 @@ const radarrCalendarEventImageSchema = z.array(
|
|||||||
const radarrCalendarEventSchema = z.object({
|
const radarrCalendarEventSchema = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
originalTitle: z.string(),
|
originalTitle: z.string(),
|
||||||
inCinemas: z.string().transform((value) => new Date(value)),
|
inCinemas: z
|
||||||
|
.string()
|
||||||
|
.transform((value) => new Date(value))
|
||||||
|
.optional(),
|
||||||
|
physicalRelease: z
|
||||||
|
.string()
|
||||||
|
.transform((value) => new Date(value))
|
||||||
|
.optional(),
|
||||||
|
digitalRelease: z
|
||||||
|
.string()
|
||||||
|
.transform((value) => new Date(value))
|
||||||
|
.optional(),
|
||||||
overview: z.string().optional(),
|
overview: z.string().optional(),
|
||||||
titleSlug: z.string(),
|
titleSlug: z.string(),
|
||||||
images: radarrCalendarEventImageSchema,
|
images: radarrCalendarEventImageSchema,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const optionMapping: OptionMapping = {
|
|||||||
},
|
},
|
||||||
"mediaRequests-requestStats": {},
|
"mediaRequests-requestStats": {},
|
||||||
calendar: {
|
calendar: {
|
||||||
|
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
|
||||||
filterFutureMonths: () => undefined,
|
filterFutureMonths: () => undefined,
|
||||||
filterPastMonths: () => undefined,
|
filterPastMonths: () => undefined,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1031,6 +1031,14 @@ export default {
|
|||||||
name: "Calendar",
|
name: "Calendar",
|
||||||
description: "Display events from your integrations in a calendar view within a certain relative time period",
|
description: "Display events from your integrations in a calendar view within a certain relative time period",
|
||||||
option: {
|
option: {
|
||||||
|
releaseType: {
|
||||||
|
label: "Radarr release type",
|
||||||
|
options: {
|
||||||
|
inCinemas: "In cinemas",
|
||||||
|
digitalRelease: "Digital release",
|
||||||
|
physicalRelease: "Physical release",
|
||||||
|
},
|
||||||
|
},
|
||||||
filterPastMonths: {
|
filterPastMonths: {
|
||||||
label: "Start from",
|
label: "Start from",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { IconClock } from "@tabler/icons-react";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import type { CalendarEvent } from "@homarr/integrations/types";
|
import type { CalendarEvent } from "@homarr/integrations/types";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import classes from "./calendar-event-list.module.css";
|
import classes from "./calendar-event-list.module.css";
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ interface CalendarEventListProps {
|
|||||||
|
|
||||||
export const CalendarEventList = ({ events }: CalendarEventListProps) => {
|
export const CalendarEventList = ({ events }: CalendarEventListProps) => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
offsetScrollbars
|
offsetScrollbars
|
||||||
@@ -57,14 +59,24 @@ export const CalendarEventList = ({ events }: CalendarEventListProps) => {
|
|||||||
{event.subName}
|
{event.subName}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text fw={"bold"} lineClamp={1}>
|
<Text fw={"bold"} lineClamp={1} size="sm">
|
||||||
{event.name}
|
{event.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Group gap={3} wrap="nowrap">
|
{event.dates ? (
|
||||||
<IconClock opacity={0.7} size={"1rem"} />
|
<Group wrap="nowrap">
|
||||||
<Text c={"dimmed"}>{dayjs(event.date.toString()).format("HH:mm")}</Text>
|
<Text c="dimmed" size="sm">
|
||||||
</Group>
|
{t(
|
||||||
|
`widget.calendar.option.releaseType.options.${event.dates.find(({ date }) => event.date === date)?.type ?? "inCinemas"}`,
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
<Group gap={3} wrap="nowrap">
|
||||||
|
<IconClock opacity={0.7} size={"1rem"} />
|
||||||
|
<Text c={"dimmed"}>{dayjs(event.date).format("HH:mm")}</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
{event.description && (
|
{event.description && (
|
||||||
<Text size={"xs"} c={"dimmed"} lineClamp={2}>
|
<Text size={"xs"} c={"dimmed"} lineClamp={2}>
|
||||||
|
|||||||
@@ -6,12 +6,18 @@ import { Calendar } from "@mantine/dates";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import type { CalendarEvent } from "@homarr/integrations/types";
|
||||||
|
|
||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
import { CalendarDay } from "./calender-day";
|
import { CalendarDay } from "./calender-day";
|
||||||
import classes from "./component.module.css";
|
import classes from "./component.module.css";
|
||||||
|
|
||||||
export default function CalendarWidget({ isEditMode, integrationIds, itemId }: WidgetComponentProps<"calendar">) {
|
export default function CalendarWidget({
|
||||||
|
isEditMode,
|
||||||
|
integrationIds,
|
||||||
|
itemId,
|
||||||
|
options,
|
||||||
|
}: WidgetComponentProps<"calendar">) {
|
||||||
const [events] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery(
|
const [events] = clientApi.widget.calendar.findAllEvents.useSuspenseQuery(
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
@@ -80,9 +86,16 @@ export default function CalendarWidget({ isEditMode, integrationIds, itemId }: W
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
renderDay={(date) => {
|
renderDay={(tileDate) => {
|
||||||
const eventsForDate = events.filter((event) => dayjs(event.date).isSame(date, "day"));
|
const eventsForDate = events
|
||||||
return <CalendarDay date={date} events={eventsForDate} disabled={isEditMode} />;
|
.map((event) => ({
|
||||||
|
...event,
|
||||||
|
date: (event.dates?.filter(({ type }) => options.releaseType.includes(type)) ?? [event]).find(({ date }) =>
|
||||||
|
dayjs(date).isSame(tileDate, "day"),
|
||||||
|
)?.date,
|
||||||
|
}))
|
||||||
|
.filter((event): event is CalendarEvent => Boolean(event.date));
|
||||||
|
return <CalendarDay date={tileDate} events={eventsForDate} disabled={isEditMode} />;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IconCalendar } from "@tabler/icons-react";
|
import { IconCalendar } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
|
import { radarrReleaseTypes } from "@homarr/integrations/types";
|
||||||
import { z } from "@homarr/validation";
|
import { z } from "@homarr/validation";
|
||||||
|
|
||||||
import { createWidgetDefinition } from "../definition";
|
import { createWidgetDefinition } from "../definition";
|
||||||
@@ -9,6 +10,13 @@ import { optionsBuilder } from "../options";
|
|||||||
export const { definition, componentLoader } = createWidgetDefinition("calendar", {
|
export const { definition, componentLoader } = createWidgetDefinition("calendar", {
|
||||||
icon: IconCalendar,
|
icon: IconCalendar,
|
||||||
options: optionsBuilder.from((factory) => ({
|
options: optionsBuilder.from((factory) => ({
|
||||||
|
releaseType: factory.multiSelect({
|
||||||
|
defaultValue: ["inCinemas", "digitalRelease"],
|
||||||
|
options: radarrReleaseTypes.map((value) => ({
|
||||||
|
value,
|
||||||
|
label: (t) => t(`widget.calendar.option.releaseType.options.${value}`),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
filterPastMonths: factory.number({
|
filterPastMonths: factory.number({
|
||||||
validate: z.number().min(2).max(9999),
|
validate: z.number().min(2).max(9999),
|
||||||
defaultValue: 2,
|
defaultValue: 2,
|
||||||
|
|||||||
Reference in New Issue
Block a user