mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-08 06:25:48 +01:00
🏗️ 💥 Change the whole folder structure.
Now using src as a subfolder to the source files
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import CalendarComponent from './CalendarModule';
|
||||
|
||||
export default {
|
||||
title: 'Calendar component',
|
||||
};
|
||||
|
||||
export const Default = (args: any) => <CalendarComponent {...args} />;
|
||||
152
src/components/modules/calendar/CalendarModule.tsx
Normal file
152
src/components/modules/calendar/CalendarModule.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
/* eslint-disable react/no-children-prop */
|
||||
import { Popover, Box, ScrollArea, Divider, Indicator } from '@mantine/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Calendar } from '@mantine/dates';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { Calendar as CalendarIcon, Check } from 'tabler-icons-react';
|
||||
import { RadarrMediaDisplay, SonarrMediaDisplay } from './MediaDisplay';
|
||||
import { useConfig } from '../../../tools/state';
|
||||
import { IModule } from '../modules';
|
||||
|
||||
export const CalendarModule: IModule = {
|
||||
title: 'Calendar',
|
||||
description:
|
||||
'A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.',
|
||||
icon: CalendarIcon,
|
||||
component: CalendarComponent,
|
||||
};
|
||||
|
||||
export default function CalendarComponent(props: any) {
|
||||
const { config } = useConfig();
|
||||
const [sonarrMedias, setSonarrMedias] = useState([] as any);
|
||||
const [radarrMedias, setRadarrMedias] = useState([] as any);
|
||||
|
||||
useEffect(() => {
|
||||
// Filter only sonarr and radarr services
|
||||
const filtered = config.services.filter(
|
||||
(service) => service.type === 'Sonarr' || service.type === 'Radarr'
|
||||
);
|
||||
|
||||
// Get the url and apiKey for all Sonarr and Radarr services
|
||||
const sonarrService = filtered.filter((service) => service.type === 'Sonarr').at(0);
|
||||
const radarrService = filtered.filter((service) => service.type === 'Radarr').at(0);
|
||||
const nextMonth = new Date(new Date().setMonth(new Date().getMonth() + 2)).toISOString();
|
||||
if (sonarrService && sonarrService.apiKey) {
|
||||
fetch(
|
||||
`${sonarrService?.url}api/calendar?apikey=${sonarrService?.apiKey}&end=${nextMonth}`
|
||||
).then((response) => {
|
||||
response.ok &&
|
||||
response.json().then((data) => {
|
||||
setSonarrMedias(data);
|
||||
showNotification({
|
||||
title: 'Sonarr',
|
||||
icon: <Check />,
|
||||
color: 'green',
|
||||
autoClose: 1500,
|
||||
radius: 'md',
|
||||
message: `Loaded ${data.length} releases`,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if (radarrService && radarrService.apiKey) {
|
||||
fetch(
|
||||
`${radarrService?.url}api/v3/calendar?apikey=${radarrService?.apiKey}&end=${nextMonth}`
|
||||
).then((response) => {
|
||||
response.ok &&
|
||||
response.json().then((data) => {
|
||||
setRadarrMedias(data);
|
||||
showNotification({
|
||||
title: 'Radarr',
|
||||
icon: <Check />,
|
||||
color: 'green',
|
||||
autoClose: 1500,
|
||||
radius: 'md',
|
||||
message: `Loaded ${data.length} releases`,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [config.services]);
|
||||
|
||||
if (sonarrMedias === undefined && radarrMedias === undefined) {
|
||||
return <Calendar />;
|
||||
}
|
||||
return (
|
||||
<Calendar
|
||||
onChange={(day: any) => {}}
|
||||
renderDay={(renderdate) => (
|
||||
<DayComponent
|
||||
renderdate={renderdate}
|
||||
sonarrmedias={sonarrMedias}
|
||||
radarrmedias={radarrMedias}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DayComponent(props: any) {
|
||||
const {
|
||||
renderdate,
|
||||
sonarrmedias,
|
||||
radarrmedias,
|
||||
}: { renderdate: Date; sonarrmedias: []; radarrmedias: [] } = props;
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const day = renderdate.getDate();
|
||||
// Itterate over the medias and filter the ones that are on the same day
|
||||
const sonarrFiltered = sonarrmedias.filter((media: any) => {
|
||||
const date = new Date(media.airDate);
|
||||
// Return true if the date is renerdate without counting hours and minutes
|
||||
return date.getDate() === day && date.getMonth() === renderdate.getMonth();
|
||||
});
|
||||
const radarrFiltered = radarrmedias.filter((media: any) => {
|
||||
const date = new Date(media.inCinemas);
|
||||
// Return true if the date is renerdate without counting hours and minutes
|
||||
return date.getDate() === day && date.getMonth() === renderdate.getMonth();
|
||||
});
|
||||
if (sonarrFiltered.length === 0 && radarrFiltered.length === 0) {
|
||||
return <div>{day}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
onClick={() => {
|
||||
setOpened(true);
|
||||
}}
|
||||
>
|
||||
{radarrFiltered.length > 0 && <Indicator size={7} color="yellow" children={null} />}
|
||||
{sonarrFiltered.length > 0 && <Indicator size={7} offset={8} color="blue" children={null} />}
|
||||
<Popover
|
||||
position="left"
|
||||
radius="lg"
|
||||
shadow="xl"
|
||||
transition="pop"
|
||||
width={700}
|
||||
onClose={() => setOpened(false)}
|
||||
opened={opened}
|
||||
// TODO: Fix this !! WTF ?
|
||||
target={` ${day}`}
|
||||
>
|
||||
<ScrollArea style={{ height: 400 }}>
|
||||
{sonarrFiltered.map((media: any, index: number) => (
|
||||
<React.Fragment key={index}>
|
||||
<SonarrMediaDisplay media={media} />
|
||||
{index < sonarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
{radarrFiltered.length > 0 && sonarrFiltered.length > 0 && (
|
||||
<Divider variant="dashed" my="xl" />
|
||||
)}
|
||||
{radarrFiltered.map((media: any, index: number) => (
|
||||
<React.Fragment key={index}>
|
||||
<RadarrMediaDisplay media={media} />
|
||||
{index < radarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</Popover>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
67
src/components/modules/calendar/MediaDisplay.story.tsx
Normal file
67
src/components/modules/calendar/MediaDisplay.story.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { RadarrMediaDisplay } from './MediaDisplay';
|
||||
|
||||
export default {
|
||||
title: 'Media display component',
|
||||
args: {
|
||||
media: {
|
||||
title: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalTitle: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalLanguage: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
secondaryYearSourceId: 0,
|
||||
sortTitle: 'doctor strange in multiverse madness',
|
||||
sizeOnDisk: 0,
|
||||
status: 'announced',
|
||||
overview:
|
||||
'Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.',
|
||||
inCinemas: '2022-05-04T00:00:00Z',
|
||||
images: [
|
||||
{
|
||||
coverType: 'poster',
|
||||
url: 'https://image.tmdb.org/t/p/original/wRnbWt44nKjsFPrqSmwYki5vZtF.jpg',
|
||||
},
|
||||
{
|
||||
coverType: 'fanart',
|
||||
url: 'https://image.tmdb.org/t/p/original/ndCSoasjIZAMMDIuMxuGnNWu4DU.jpg',
|
||||
},
|
||||
],
|
||||
website: 'https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness',
|
||||
year: 2022,
|
||||
hasFile: false,
|
||||
youTubeTrailerId: 'aWzlQ2N6qqg',
|
||||
studio: 'Marvel Studios',
|
||||
path: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
qualityProfileId: 1,
|
||||
monitored: true,
|
||||
minimumAvailability: 'announced',
|
||||
isAvailable: true,
|
||||
folderName: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
runtime: 126,
|
||||
cleanTitle: 'doctorstrangeinmultiversemadness',
|
||||
imdbId: 'tt9419884',
|
||||
tmdbId: 453395,
|
||||
titleSlug: '453395',
|
||||
certification: 'PG-13',
|
||||
genres: ['Fantasy', 'Action', 'Adventure'],
|
||||
tags: [],
|
||||
added: '2022-04-29T20:52:33Z',
|
||||
ratings: {
|
||||
tmdb: {
|
||||
votes: 0,
|
||||
value: 0,
|
||||
type: 'user',
|
||||
},
|
||||
},
|
||||
collection: {
|
||||
name: 'Doctor Strange Collection',
|
||||
tmdbId: 618529,
|
||||
images: [],
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = (args: any) => <RadarrMediaDisplay {...args} />;
|
||||
100
src/components/modules/calendar/MediaDisplay.tsx
Normal file
100
src/components/modules/calendar/MediaDisplay.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Stack, Image, Group, Title, Badge, Text, ActionIcon, Anchor } from '@mantine/core';
|
||||
import { Link } from 'tabler-icons-react';
|
||||
|
||||
export interface IMedia {
|
||||
overview: string;
|
||||
imdbId: any;
|
||||
title: string;
|
||||
poster: string;
|
||||
genres: string[];
|
||||
seasonNumber?: number;
|
||||
episodeNumber?: number;
|
||||
}
|
||||
|
||||
function MediaDisplay(props: { media: IMedia }) {
|
||||
const { media }: { media: IMedia } = props;
|
||||
return (
|
||||
<Group noWrap align="self-start" mr={15}>
|
||||
<Image
|
||||
radius="md"
|
||||
fit="cover"
|
||||
src={media.poster}
|
||||
alt={media.title}
|
||||
width={300}
|
||||
height={400}
|
||||
/>
|
||||
<Stack
|
||||
justify="space-between"
|
||||
sx={(theme) => ({
|
||||
height: 400,
|
||||
})}
|
||||
>
|
||||
<Group direction="column">
|
||||
<Group>
|
||||
<Title order={3}>{media.title}</Title>
|
||||
<Anchor href={`https://www.imdb.com/title/${media.imdbId}`} target="_blank">
|
||||
<ActionIcon>
|
||||
<Link />
|
||||
</ActionIcon>
|
||||
</Anchor>
|
||||
</Group>
|
||||
{media.episodeNumber && media.seasonNumber && (
|
||||
<Text
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#a0aec0',
|
||||
}}
|
||||
>
|
||||
Season {media.seasonNumber} episode {media.episodeNumber}
|
||||
</Text>
|
||||
)}
|
||||
<Text align="justify">{media.overview}</Text>
|
||||
</Group>
|
||||
{/*Add the genres at the bottom of the poster*/}
|
||||
<Group>
|
||||
{media.genres.map((genre: string, i: number) => (
|
||||
<Badge key={i}>{genre}</Badge>
|
||||
))}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
export function RadarrMediaDisplay(props: any) {
|
||||
const { media }: { media: any } = props;
|
||||
// Find a poster CoverType
|
||||
const poster = media.images.find((image: any) => image.coverType === 'poster');
|
||||
// Return a movie poster containting the title and the description
|
||||
return (
|
||||
<MediaDisplay
|
||||
media={{
|
||||
imdbId: media.imdbId,
|
||||
title: media.title,
|
||||
overview: media.overview,
|
||||
poster: poster.url,
|
||||
genres: media.genres,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SonarrMediaDisplay(props: any) {
|
||||
const { media }: { media: any } = props;
|
||||
// Find a poster CoverType
|
||||
const poster = media.series.images.find((image: any) => image.coverType === 'poster');
|
||||
// Return a movie poster containting the title and the description
|
||||
return (
|
||||
<MediaDisplay
|
||||
media={{
|
||||
imdbId: media.series.imdbId,
|
||||
title: media.series.title,
|
||||
overview: media.series.overview,
|
||||
poster: poster.url,
|
||||
genres: media.series.genres,
|
||||
seasonNumber: media.seasonNumber,
|
||||
episodeNumber: media.episodeNumber,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
1
src/components/modules/calendar/index.ts
Normal file
1
src/components/modules/calendar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CalendarModule } from './CalendarModule';
|
||||
408
src/components/modules/calendar/mediaExample.ts
Normal file
408
src/components/modules/calendar/mediaExample.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
export const medias = [
|
||||
{
|
||||
title: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalTitle: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalLanguage: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
alternateTitles: [
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Доктор Стрэндж 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 11,
|
||||
name: 'Russian',
|
||||
},
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doutor Estranho 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange v multivesmíre šialenstva',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange 2: El multiverso de la locura',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 3,
|
||||
name: 'Spanish',
|
||||
},
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doktor Strange Deliliğin Çoklu Evreninde',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 17,
|
||||
name: 'Turkish',
|
||||
},
|
||||
id: 6,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'মহাবিশ্বের পাগলামিতে অদ্ভুত চিকিৎসক',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 7,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'จอมเวทย์มหากาฬ ในมัลติเวิร์สมหาภัย',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 28,
|
||||
name: 'Thai',
|
||||
},
|
||||
id: 8,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: "Marvel Studios' Doctor Strange in the Multiverse of Madness",
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 9,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange en el Multiverso de la Locura de Marvel Studios',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 3,
|
||||
name: 'Spanish',
|
||||
},
|
||||
id: 10,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doktors Streindžs neprāta multivisumā',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 11,
|
||||
},
|
||||
],
|
||||
secondaryYearSourceId: 0,
|
||||
sortTitle: 'doctor strange in multiverse madness',
|
||||
sizeOnDisk: 0,
|
||||
status: 'announced',
|
||||
overview:
|
||||
'Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.',
|
||||
inCinemas: '2022-05-04T00:00:00Z',
|
||||
images: [
|
||||
{
|
||||
coverType: 'poster',
|
||||
url: 'https://image.tmdb.org/t/p/original/wRnbWt44nKjsFPrqSmwYki5vZtF.jpg',
|
||||
},
|
||||
{
|
||||
coverType: 'fanart',
|
||||
url: 'https://image.tmdb.org/t/p/original/ndCSoasjIZAMMDIuMxuGnNWu4DU.jpg',
|
||||
},
|
||||
],
|
||||
website: 'https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness',
|
||||
year: 2022,
|
||||
hasFile: false,
|
||||
youTubeTrailerId: 'aWzlQ2N6qqg',
|
||||
studio: 'Marvel Studios',
|
||||
path: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
qualityProfileId: 1,
|
||||
monitored: true,
|
||||
minimumAvailability: 'announced',
|
||||
isAvailable: true,
|
||||
folderName: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
runtime: 126,
|
||||
cleanTitle: 'doctorstrangeinmultiversemadness',
|
||||
imdbId: 'tt9419884',
|
||||
tmdbId: 453395,
|
||||
titleSlug: '453395',
|
||||
certification: 'PG-13',
|
||||
genres: ['Fantasy', 'Action', 'Adventure'],
|
||||
tags: [],
|
||||
added: '2022-04-29T20:52:33Z',
|
||||
ratings: {
|
||||
tmdb: {
|
||||
votes: 0,
|
||||
value: 0,
|
||||
type: 'user',
|
||||
},
|
||||
},
|
||||
collection: {
|
||||
name: 'Doctor Strange Collection',
|
||||
tmdbId: 618529,
|
||||
images: [],
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
title: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalTitle: 'Doctor Strange in the Multiverse of Madness',
|
||||
originalLanguage: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
alternateTitles: [
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Доктор Стрэндж 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 11,
|
||||
name: 'Russian',
|
||||
},
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doutor Estranho 2',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange v multivesmíre šialenstva',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange 2: El multiverso de la locura',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 3,
|
||||
name: 'Spanish',
|
||||
},
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doktor Strange Deliliğin Çoklu Evreninde',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 17,
|
||||
name: 'Turkish',
|
||||
},
|
||||
id: 6,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'মহাবিশ্বের পাগলামিতে অদ্ভুত চিকিৎসক',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 7,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'จอมเวทย์มหากาฬ ในมัลติเวิร์สมหาภัย',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 28,
|
||||
name: 'Thai',
|
||||
},
|
||||
id: 8,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: "Marvel Studios' Doctor Strange in the Multiverse of Madness",
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 9,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doctor Strange en el Multiverso de la Locura de Marvel Studios',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 3,
|
||||
name: 'Spanish',
|
||||
},
|
||||
id: 10,
|
||||
},
|
||||
{
|
||||
sourceType: 'tmdb',
|
||||
movieId: 1,
|
||||
title: 'Doktors Streindžs neprāta multivisumā',
|
||||
sourceId: 0,
|
||||
votes: 0,
|
||||
voteCount: 0,
|
||||
language: {
|
||||
id: 1,
|
||||
name: 'English',
|
||||
},
|
||||
id: 11,
|
||||
},
|
||||
],
|
||||
secondaryYearSourceId: 0,
|
||||
sortTitle: 'doctor strange in multiverse madness',
|
||||
sizeOnDisk: 0,
|
||||
status: 'announced',
|
||||
overview:
|
||||
'Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.',
|
||||
inCinemas: '2022-05-05T00:00:00Z',
|
||||
images: [
|
||||
{
|
||||
coverType: 'poster',
|
||||
url: 'https://image.tmdb.org/t/p/original/wRnbWt44nKjsFPrqSmwYki5vZtF.jpg',
|
||||
},
|
||||
{
|
||||
coverType: 'fanart',
|
||||
url: 'https://image.tmdb.org/t/p/original/ndCSoasjIZAMMDIuMxuGnNWu4DU.jpg',
|
||||
},
|
||||
],
|
||||
website: 'https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness',
|
||||
year: 2022,
|
||||
hasFile: false,
|
||||
youTubeTrailerId: 'aWzlQ2N6qqg',
|
||||
studio: 'Marvel Studios',
|
||||
path: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
qualityProfileId: 1,
|
||||
monitored: true,
|
||||
minimumAvailability: 'announced',
|
||||
isAvailable: true,
|
||||
folderName: '/config/Doctor Strange in the Multiverse of Madness (2022)',
|
||||
runtime: 126,
|
||||
cleanTitle: 'doctorstrangeinmultiversemadness',
|
||||
imdbId: 'tt9419884',
|
||||
tmdbId: 453395,
|
||||
titleSlug: '453395',
|
||||
certification: 'PG-13',
|
||||
genres: ['Fantasy', 'Action', 'Adventure'],
|
||||
tags: [],
|
||||
added: '2022-04-29T20:52:33Z',
|
||||
ratings: {
|
||||
tmdb: {
|
||||
votes: 0,
|
||||
value: 0,
|
||||
type: 'user',
|
||||
},
|
||||
},
|
||||
collection: {
|
||||
name: 'Doctor Strange Collection',
|
||||
tmdbId: 618529,
|
||||
images: [],
|
||||
},
|
||||
id: 2,
|
||||
},
|
||||
];
|
||||
7
src/components/modules/date/DateModule.story.tsx
Normal file
7
src/components/modules/date/DateModule.story.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import DateComponent from './DateModule';
|
||||
|
||||
export default {
|
||||
title: 'Date module',
|
||||
};
|
||||
|
||||
export const Default = (args: any) => <DateComponent {...args} />;
|
||||
41
src/components/modules/date/DateModule.tsx
Normal file
41
src/components/modules/date/DateModule.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Group, Text, Title } from '@mantine/core';
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Clock } from 'tabler-icons-react';
|
||||
import { IModule } from '../modules';
|
||||
|
||||
export const DateModule: IModule = {
|
||||
title: 'Date',
|
||||
description: 'Show the current time and date in a card',
|
||||
icon: Clock,
|
||||
component: DateComponent,
|
||||
};
|
||||
|
||||
export default function DateComponent(props: any) {
|
||||
const [date, setDate] = useState(new Date());
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
|
||||
// Change date on minute change
|
||||
// Note: Using 10 000ms instead of 1000ms to chill a little :)
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
setDate(new Date());
|
||||
}, 10000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Group p="sm" direction="column">
|
||||
<Title>
|
||||
{hours < 10 ? `0${hours}` : hours}:{minutes < 10 ? `0${minutes}` : minutes}
|
||||
</Title>
|
||||
<Text size="xl">
|
||||
{
|
||||
// Use dayjs to format the date
|
||||
// https://day.js.org/en/getting-started/installation/
|
||||
dayjs(date).format('dddd, MMMM D')
|
||||
}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
1
src/components/modules/date/index.ts
Normal file
1
src/components/modules/date/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { DateModule } from './DateModule';
|
||||
2
src/components/modules/index.ts
Normal file
2
src/components/modules/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './date';
|
||||
export * from './calendar';
|
||||
29
src/components/modules/moduleWrapper.tsx
Normal file
29
src/components/modules/moduleWrapper.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Card, useMantineTheme } from '@mantine/core';
|
||||
import { useConfig } from '../../tools/state';
|
||||
import { IModule } from './modules';
|
||||
|
||||
export default function ModuleWrapper(props: any) {
|
||||
const { module }: { module: IModule } = props;
|
||||
const { config } = useConfig();
|
||||
const enabledModules = config.settings.enabledModules ?? [];
|
||||
// Remove 'Module' from enabled modules titles
|
||||
const isShown = enabledModules.includes(module.title);
|
||||
const theme = useMantineTheme();
|
||||
if (!isShown) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
hidden={!isShown}
|
||||
mx="sm"
|
||||
radius="lg"
|
||||
shadow="sm"
|
||||
style={{
|
||||
// Make background color of the card depend on the theme
|
||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : 'white',
|
||||
}}
|
||||
>
|
||||
<module.component />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
11
src/components/modules/modules.tsx
Normal file
11
src/components/modules/modules.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
// This interface is to be used in all the modules of the project
|
||||
// Each module should have its own interface and call the following function:
|
||||
// TODO: Add a function to register a module
|
||||
// Note: Maybe use context to keep track of the modules
|
||||
export interface IModule {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
component: React.ComponentType;
|
||||
props?: any;
|
||||
}
|
||||
9
src/components/modules/readme.md
Normal file
9
src/components/modules/readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
**Each module has a set of rules:**
|
||||
- Exported Typed IModule element (Unique Name, description, component, ...)
|
||||
- Needs to be in a new folder
|
||||
- Needs to be exported in the modules/newmodule/index.tsx of the new folder
|
||||
- Needs to be imported in the modules/index.tsx file
|
||||
- Needs to look good when wrapped with the modules/ModuleWrapper component
|
||||
- Needs to be put somewhere fitting in the app (While waiting for the big AppStore overhall)
|
||||
- Any API Calls need to be safe and done on the widget itself (via useEffect or similar)
|
||||
- You can't add a package (unless there is a very specific need for it. Contact [@Ajnart](ajnart@pm.me) or make a [Discussion](https://github.com/ajnart/homarr/discussions/new).
|
||||
Reference in New Issue
Block a user