mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-11 17:55:33 +00:00
update all movie lists
This commit is contained in:
parent
88dc5f0890
commit
32d9cd040c
3 changed files with 159 additions and 15 deletions
|
|
@ -1,3 +1,4 @@
|
|||
// This endpoint is not used anymore, but we keep it here for reference or if we feel like fixing the backend
|
||||
import { conf } from "@/utils/setup/config";
|
||||
|
||||
export interface TmdbMovie {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { getMediaDetails } from "./tmdb";
|
||||
import { MWMediaType } from "./types/mw";
|
||||
import { TMDBContentTypes, TMDBMovieData } from "./types/tmdb";
|
||||
|
||||
export interface TraktLatestResponse {
|
||||
tmdb_ids: number[];
|
||||
|
|
@ -41,6 +43,13 @@ export interface TraktNetworkResponse {
|
|||
count: number;
|
||||
}
|
||||
|
||||
export interface CuratedMovieList {
|
||||
listName: string;
|
||||
listSlug: string;
|
||||
tmdbIds: number[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
// Pagination utility
|
||||
export function paginateResults(
|
||||
results: TraktLatestResponse,
|
||||
|
|
@ -116,6 +125,115 @@ export const getDiscoverContent = () =>
|
|||
export const getNetworkContent = (tmdbId: string) =>
|
||||
fetchFromTrakt<TraktNetworkResponse>(`/network/${tmdbId}`);
|
||||
|
||||
// Curated movie lists (replacing Letterboxd functionality)
|
||||
export const getNarrativeMovies = () => fetchFromTrakt("/narrative");
|
||||
export const getTopMovies = () => fetchFromTrakt("/top");
|
||||
export const getLifetimeMovies = () => fetchFromTrakt("/lifetime");
|
||||
export const getNeverHeardMovies = () => fetchFromTrakt("/never");
|
||||
export const getLGBTQContent = () => fetchFromTrakt("/LGBTQ");
|
||||
export const getMindfuckMovies = () => fetchFromTrakt("/mindfuck");
|
||||
export const getTrueStoryMovies = () => fetchFromTrakt("/truestory");
|
||||
export const getGreatestTVShows = () => fetchFromTrakt("/greatesttv");
|
||||
|
||||
// Get all curated movie lists
|
||||
export const getCuratedMovieLists = async (): Promise<CuratedMovieList[]> => {
|
||||
const listConfigs = [
|
||||
{
|
||||
name: "Letterboxd Top 250 Narrative Feature Films",
|
||||
slug: "narrative",
|
||||
endpoint: "/narrative",
|
||||
},
|
||||
{
|
||||
name: "1001 Greatest Movies of All Time",
|
||||
slug: "top",
|
||||
endpoint: "/top",
|
||||
},
|
||||
{
|
||||
name: "1001 Movies You Must See Before You Die",
|
||||
slug: "lifetime",
|
||||
endpoint: "/lifetime",
|
||||
},
|
||||
{
|
||||
name: "Great Movies You May Have Never Heard Of",
|
||||
slug: "never",
|
||||
endpoint: "/never",
|
||||
},
|
||||
{
|
||||
name: "LGBT Movies/Shows",
|
||||
slug: "LGBTQ",
|
||||
endpoint: "/LGBTQ",
|
||||
},
|
||||
{
|
||||
name: "Best Mindfuck Movies",
|
||||
slug: "mindfuck",
|
||||
endpoint: "/mindfuck",
|
||||
},
|
||||
{
|
||||
name: "Based on a True Story Movies",
|
||||
slug: "truestory",
|
||||
endpoint: "/truestory",
|
||||
},
|
||||
{
|
||||
name: "Rolling Stone's 100 Greatest TV Shows",
|
||||
slug: "greatesttv",
|
||||
endpoint: "/greatesttv",
|
||||
},
|
||||
];
|
||||
|
||||
const lists: CuratedMovieList[] = [];
|
||||
|
||||
for (const config of listConfigs) {
|
||||
try {
|
||||
const response = await fetchFromTrakt(config.endpoint);
|
||||
lists.push({
|
||||
listName: config.name,
|
||||
listSlug: config.slug,
|
||||
tmdbIds: response.tmdb_ids.slice(0, 30), // Limit to first 30 items
|
||||
count: Math.min(response.count, 30), // Update count to reflect the limit
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch ${config.name}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return lists;
|
||||
};
|
||||
|
||||
// Fetch movie details for multiple TMDB IDs
|
||||
export const getMovieDetailsForIds = async (
|
||||
tmdbIds: number[],
|
||||
limit: number = 50,
|
||||
): Promise<TMDBMovieData[]> => {
|
||||
const limitedIds = tmdbIds.slice(0, limit);
|
||||
const movieDetails: TMDBMovieData[] = [];
|
||||
|
||||
// Process in smaller batches to avoid overwhelming the API
|
||||
const batchSize = 10;
|
||||
for (let i = 0; i < limitedIds.length; i += batchSize) {
|
||||
const batch = limitedIds.slice(i, i + batchSize);
|
||||
const batchPromises = batch.map(async (id) => {
|
||||
try {
|
||||
const details = await getMediaDetails(
|
||||
id.toString(),
|
||||
TMDBContentTypes.MOVIE,
|
||||
);
|
||||
return details as TMDBMovieData;
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch movie details for ID ${id}:`, error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
const validResults = batchResults.filter(
|
||||
(result): result is TMDBMovieData => result !== null,
|
||||
);
|
||||
movieDetails.push(...validResults);
|
||||
}
|
||||
|
||||
return movieDetails;
|
||||
};
|
||||
|
||||
// Type conversion utilities
|
||||
export function convertToMediaType(type: TraktContentType): MWMediaType {
|
||||
return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { t } from "i18next";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { TmdbMovie, getLetterboxdLists } from "@/backend/metadata/letterboxd";
|
||||
import {
|
||||
CuratedMovieList,
|
||||
getCuratedMovieLists,
|
||||
getMovieDetailsForIds,
|
||||
} from "@/backend/metadata/traktApi";
|
||||
import { TMDBMovieData } from "@/backend/metadata/types/tmdb";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { WideContainer } from "@/components/layout/WideContainer";
|
||||
import { MediaCard } from "@/components/media/MediaCard";
|
||||
|
|
@ -19,29 +24,49 @@ import { MediaCarousel } from "./components/MediaCarousel";
|
|||
|
||||
export function DiscoverMore() {
|
||||
const [detailsData, setDetailsData] = useState<any>();
|
||||
const [letterboxdLists, setLetterboxdLists] = useState<any[]>([]);
|
||||
const [curatedLists, setCuratedLists] = useState<CuratedMovieList[]>([]);
|
||||
const [movieDetails, setMovieDetails] = useState<{
|
||||
[listSlug: string]: TMDBMovieData[];
|
||||
}>({});
|
||||
const detailsModal = useModal("discover-details");
|
||||
const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
||||
const navigate = useNavigate();
|
||||
const { lastView } = useDiscoverStore();
|
||||
const { isMobile } = useIsMobile();
|
||||
|
||||
// Track overflow states for Letterboxd lists
|
||||
// Track overflow states for curated lists
|
||||
const [overflowStates, setOverflowStates] = useState<{
|
||||
[key: string]: boolean;
|
||||
}>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLetterboxdLists = async () => {
|
||||
const fetchCuratedLists = async () => {
|
||||
try {
|
||||
const response = await getLetterboxdLists();
|
||||
setLetterboxdLists(response.lists);
|
||||
const lists = await getCuratedMovieLists();
|
||||
setCuratedLists(lists);
|
||||
|
||||
// Fetch movie details for each list
|
||||
const details: { [listSlug: string]: TMDBMovieData[] } = {};
|
||||
for (const list of lists) {
|
||||
try {
|
||||
const movies = await getMovieDetailsForIds(list.tmdbIds, 50);
|
||||
if (movies.length > 0) {
|
||||
details[list.listSlug] = movies;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to fetch movies for list ${list.listSlug}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
setMovieDetails(details);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch Letterboxd lists:", error);
|
||||
console.error("Failed to fetch curated lists:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLetterboxdLists();
|
||||
fetchCuratedLists();
|
||||
}, []);
|
||||
|
||||
const handleShowDetails = async (media: MediaItem) => {
|
||||
|
|
@ -143,9 +168,9 @@ export function DiscoverMore() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Letterboxd Lists */}
|
||||
{letterboxdLists.map((list) => (
|
||||
<div key={list.listUrl}>
|
||||
{/* Curated Movie Lists */}
|
||||
{curatedLists.map((list) => (
|
||||
<div key={list.listSlug}>
|
||||
<div className="flex items-center justify-between ml-2 md:ml-8 mt-2">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
|
|
@ -158,11 +183,11 @@ export function DiscoverMore() {
|
|||
<div className="relative overflow-hidden carousel-container md:pb-4">
|
||||
<div
|
||||
className="grid grid-flow-col auto-cols-max gap-4 pt-0 overflow-x-scroll scrollbar-none rounded-xl overflow-y-hidden md:pl-8 md:pr-8"
|
||||
ref={(el) => setCarouselRef(el, list.listUrl)}
|
||||
ref={(el) => setCarouselRef(el, list.listSlug)}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="md:w-12" />
|
||||
{list.tmdbMovies.map((movie: TmdbMovie) => (
|
||||
{movieDetails[list.listSlug]?.map((movie: TMDBMovieData) => (
|
||||
<div
|
||||
key={movie.id}
|
||||
className="relative mt-4 group cursor-pointer user-select-none rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto"
|
||||
|
|
@ -188,9 +213,9 @@ export function DiscoverMore() {
|
|||
</div>
|
||||
{!isMobile && (
|
||||
<CarouselNavButtons
|
||||
categorySlug={list.listUrl}
|
||||
categorySlug={list.listSlug}
|
||||
carouselRefs={carouselRefs}
|
||||
hasOverflow={overflowStates[list.listUrl]}
|
||||
hasOverflow={overflowStates[list.listSlug]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue