Skip to content

Commit

Permalink
Merge pull request #7 from johnpc/genre-page
Browse files Browse the repository at this point in the history
add top genres feature
  • Loading branch information
johnpc authored Jan 2, 2025
2 parents bde4439 + 94114d7 commit 5e6a2d7
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
*.png
.husky/
Dockerfile
*.conf
*.conf
LICENSE
5 changes: 3 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ services:
- "80:80"
restart: unless-stopped
container_name: jellyfin-wrapped
environment:
- FORCE_JELLYFIN_SERVER_URL=http://<your_jellyfin_server>:8096
# Optional environment variable to force your Jellyfin Wrapped instance to only work with your Jellyfin server. This simplifies configuration.
# environment:
# - FORCE_JELLYFIN_SERVER_URL=http://<your_jellyfin_server>:8096
5 changes: 5 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FavoriteActorsPage from "./components/pages/FavoriteActorsPage";
import OldestShowPage from "./components/pages/OldestShowPage";
import OldestMoviePage from "./components/pages/OldestMoviePage";
import MusicVideoPage from "./components/pages/MusicVideoPage";
import GenreReviewPage from "./components/pages/GenreReviewPage";

// Layout component that wraps all routes
function RootLayout() {
Expand Down Expand Up @@ -68,6 +69,10 @@ const router = createBrowserRouter([
path: "/music-videos",
element: <MusicVideoPage />,
},
{
path: "/genres",
element: <GenreReviewPage />,
},
],
},
]);
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/FavoriteActorsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ export default function FavoriteActorsPage() {
size={"4"}
style={{ width: "100%" }}
onClick={() => {
navigate("/tv");
navigate("/genres");
}}
>
Review Live TV
Review Top Genres
</Button>
</Box>
);
Expand Down
128 changes: 128 additions & 0 deletions src/components/pages/GenreReviewPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useState, useEffect } from "react";
import {
listMovies,
listShows,
SimpleItemDto,
} from "@/lib/playback-reporting-queries";
import { MovieCard } from "./MoviesReviewPage/MovieCard";
import { Container, Grid, Box, Spinner, Button } from "@radix-ui/themes";
import { motion } from "framer-motion";
import { itemVariants, Title } from "../ui/styled";
import { useNavigate } from "react-router-dom";
import { generateGuid } from "@/lib/utils";
import { useErrorBoundary } from "react-error-boundary";
import { getCachedHiddenIds, setCachedHiddenId } from "@/lib/cache";

export default function GenreReviewPage() {
const { showBoundary } = useErrorBoundary();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(true);
const [movies, setMovies] = useState<SimpleItemDto[]>([]);
const [shows, setShows] = useState<SimpleItemDto[]>([]);
const [hiddenIds, setHiddenIds] = useState<string[]>(getCachedHiddenIds());

useEffect(() => {
const setup = async () => {
setIsLoading(true);
try {
const movies = await listMovies();
setMovies(movies.filter((movie) => !hiddenIds.includes(movie.id!)));
const shows = await listShows();
setShows(
shows
.map((show) => show.item)
.filter((show) => !hiddenIds.includes(show.id!)),
);
} catch (error) {
showBoundary(error);
} finally {
setIsLoading(false);
}
};
setup();
}, [hiddenIds]);

if (isLoading) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
}}
>
<Spinner size={"3"} />
</div>
);
}

const allGenres = movies.flatMap((movie) => movie.genres).filter((g) => g);
const genreCounts = allGenres.reduce(
(counts, genre) => {
counts[genre!] = (counts[genre!] || 0) + 1;
return counts;
},
{} as Record<string, number>,
);

const sortedGenres = Object.entries(genreCounts)
.sort(([, a], [, b]) => b - a)
.map(([genre]) => genre);

const itemsByGenre = sortedGenres.map((genre) => ({
genre,
count: [...movies, ...shows].filter((movie) =>
movie.genres?.includes(genre),
).length,
items: [...movies, ...shows].filter((movie) =>
movie.genres?.includes(genre),
),
}));

return (
<Box
style={{ backgroundColor: "var(--orange-8)" }}
className="min-h-screen"
>
<Container size="4" p="4">
<div style={{ display: "flex", flexDirection: "column", gap: "2rem" }}>
{itemsByGenre.map(({ genre, count, items }) => (
<div
key={genre}
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<Title as={motion.h1} variants={itemVariants}>
{genre} ({count} {count === 1 ? "items" : "items"})
</Title>
<Grid
columns={{ initial: "2", sm: "3", md: "4", lg: "5" }}
gap="4"
>
{items.map((movie) => (
<MovieCard
key={generateGuid()}
item={movie}
onHide={() => {
setCachedHiddenId(movie.id!);
setHiddenIds([...hiddenIds, movie.id!]);
}}
/>
))}
</Grid>
</div>
))}
</div>
</Container>
<Button
size={"4"}
style={{ width: "100%" }}
onClick={() => {
navigate("/tv");
}}
>
Review Live TV
</Button>
</Box>
);
}
5 changes: 5 additions & 0 deletions src/lib/playback-reporting-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { subYears } from "date-fns";
import {
BaseItemPerson,
ImageType,
NameGuidPair,
PluginStatus,
} from "@jellyfin/sdk/lib/generated-client";
import {
Expand All @@ -26,6 +27,8 @@ export type SimpleItemDto = {
communityRating?: number | null;
productionYear?: number | null;
people?: BaseItemPerson[] | null;
genres?: string[] | null;
genreItems?: NameGuidPair[] | null;
};

const oneYearAgo = subYears(new Date(), 1);
Expand Down Expand Up @@ -120,6 +123,8 @@ const getItemDtosByIds = async (ids: string[]): Promise<SimpleItemDto[]> => {
communityRating: item.data.CommunityRating,
people: item.data.People,
date: item.data.PremiereDate,
genres: item.data.Genres,
genreItems: item.data.GenreItems,
};
setCacheValue(`item_${itemId}`, JSON.stringify(simpleItem));

Expand Down

0 comments on commit 5e6a2d7

Please sign in to comment.