make responsive
Gitea/movie-explorer/pipeline/head There was a failure building this commit Details

This commit is contained in:
andrzej 2024-10-07 16:18:57 +02:00
parent 206b9cdda5
commit 4684cac15a
7 changed files with 58 additions and 39 deletions

View File

@ -1,7 +1,7 @@
import './spinner.css' import './spinner.css'
import { MovieWall } from './objects/movie-wall' import { MovieWall } from './objects/movie-wall'
import { HamburgerMenu } from './objects/HamburgerMenu' import { HamburgerMenu } from './objects/HamburgerMenu'
import { useState, useEffect } from 'react' import { useState, useEffect, useRef, useLayoutEffect } from 'react'
import tmdb from './objects/tmdb' import tmdb from './objects/tmdb'
import { Sidebar } from './objects/sidebar' import { Sidebar } from './objects/sidebar'
import { Movie } from './objects/movie-wall' import { Movie } from './objects/movie-wall'
@ -44,7 +44,7 @@ function App() {
const [chosenMovie, setChosenMovie] = useState(new MovieClass()) const [chosenMovie, setChosenMovie] = useState(new MovieClass())
const [similarMoviesAvailable, setSimilarMoviesAvailable] = useState(true) const [similarMoviesAvailable, setSimilarMoviesAvailable] = useState(true)
const [watchProviders, setWatchProviders] = useState(new WatchProvidersClass()) const [watchProviders, setWatchProviders] = useState(new WatchProvidersClass())
useEffect(() => { tmdb.getPopular(config, setMoviesHandler) }, []) useEffect(() => { tmdb.getPopular(config, setMovies) }, [])
useEffect(() => { useEffect(() => {
const bgString = chosenMovie.backdrop_path ? const bgString = chosenMovie.backdrop_path ?
tmdb.makeBgImgUrl(chosenMovie.backdrop_path) tmdb.makeBgImgUrl(chosenMovie.backdrop_path)
@ -58,26 +58,26 @@ function App() {
useEffect(() => { useEffect(() => {
console.log("CONFIG CHANGED!!") console.log("CONFIG CHANGED!!")
tmdb.getMovie(config, chosenMovie.id, setChosenMovie) tmdb.getMovie(config, chosenMovie.id, setChosenMovie)
tmdb.getSimilar(config, chosenMovie, setMoviesHandler, setSimilarMoviesAvailable) tmdb.getSimilar(config, chosenMovie, setMovies, setSimilarMoviesAvailable)
}, [config]) }, [config])
const crossfadeImageStyles = { const crossfadeImageStyles = {
width: "100vw", width: "100vw",
height: "100vh", height: "100vh",
aspectRatio: "16/9", aspectRatio: "16/9",
objectFit: "clip" objectFit: "clip",
opacity: "40%"
} }
const { width } = useWindowDimensions() const { width } = useWindowDimensions()
function setMoviesHandler(movies: Movie[]) {
setMovies(trimMoviesForScreen(movies))
}
function trimMoviesForScreen(movies: Movie[]) { function trimMoviesForScreen(movies: Movie[]) {
let desiredLength = 0 let desiredLength = 0
if (width > 640) { if (width > 1280) {
desiredLength = 18 desiredLength = 18
} else { desiredLength = 12 } } else if (width > 640) {
desiredLength = 16
} else { desiredLength = 15 }
//ensure movies.length is a multiple of 6 (so it makes an orphanless grid) //ensure movies.length is a multiple of 6 (so it makes an orphanless grid)
while (movies.length < desiredLength && movies.length >= 6) { desiredLength = desiredLength - 6 } while (movies.length < desiredLength && movies.length >= 6) { desiredLength = desiredLength - 6 }
return movies.slice(0, desiredLength) return movies.slice(0, desiredLength)
@ -87,24 +87,23 @@ function App() {
<> <>
<div className="w-screen h-screen flex flex-col absolute overflow-hidden"> <div className="w-screen h-screen flex flex-col absolute overflow-hidden">
<div className="-z-10 absolute h-full blur-xl"> <div className="-z-10 absolute h-full blur-xl bg-black">
<CrossfadeImage src={backdrop} style={crossfadeImageStyles} /> <CrossfadeImage src={backdrop} style={crossfadeImageStyles} />
</div> </div>
<header className="bg-black py-2"> <header className="bg-black py-2">
<HamburgerMenu setConfig={setConfig} /> <HamburgerMenu setConfig={setConfig} />
</header> </header>
<main className="flex flex-col sm:flex-row justify-center items-center gap-8 grow"> <main className="flex flex-col sm:flex-row sm:justify-center items-center gap-8 grow">
<Sidebar className="flex flex-col justify-between items-center max-w-sm bg-black/80 text-white w-96 h-full" movie={chosenMovie} similarMoviesAvailable={similarMoviesAvailable} watchProviders={watchProviders} config={config} /> <Sidebar className="flex flex-col justify-between items-center max-w-sm bg-black/80 text-white w-full sm:w-96 h-64 sm:h-full" movie={chosenMovie} similarMoviesAvailable={similarMoviesAvailable} watchProviders={watchProviders} config={config} />
<MovieWall <MovieWall
className="flex flex-wrap gap-2 max-w-5xl justify-center" className={"flex flex-wrap gap-2 w-fit justify-center sm:max-w-lg xl:max-w-5xl "}
movies={movies} movies={trimMoviesForScreen(movies)}
setMovies={setMoviesHandler} setMovies={setMovies}
config={config} config={config}
setChosenMovie={setChosenMovie} setChosenMovie={setChosenMovie}
setSimilarMoviesAvailable={setSimilarMoviesAvailable} setSimilarMoviesAvailable={setSimilarMoviesAvailable}
/> />
</main> </main>
<footer className="bg-black min-h-10"></footer>
</div> </div>
</> </>
) )

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
function getWindowDimensions() { function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window; const { innerWidth: width, innerHeight: height } = window;
@ -22,3 +22,4 @@ export default function useWindowDimensions() {
return windowDimensions; return windowDimensions;
} }

View File

@ -1,4 +1,4 @@
import React, { useState } from "react" import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import { Config } from "../App" import { Config } from "../App"
import tmdb from "./tmdb" import tmdb from "./tmdb"
@ -35,17 +35,20 @@ interface MovieWallProps extends React.ComponentPropsWithRef<"div"> {
setChosenMovie: React.Dispatch<React.SetStateAction<any>>; setChosenMovie: React.Dispatch<React.SetStateAction<any>>;
setSimilarMoviesAvailable: React.Dispatch<React.SetStateAction<boolean>>; setSimilarMoviesAvailable: React.Dispatch<React.SetStateAction<boolean>>;
config: Config; config: Config;
className: string className: string;
} }
export function MovieWall({ movies, setMovies, config, setChosenMovie, setSimilarMoviesAvailable, className }: MovieWallProps) { export function MovieWall({ movies, setMovies, config, setChosenMovie, setSimilarMoviesAvailable, className }: MovieWallProps) {
const posters: React.ReactElement[] = [] const posters: React.ReactElement[] = []
for (let i = 0; i < movies.length; i++) { for (let i = 0; i < movies.length; i++) {
const movie = movies[i] const movie = movies[i]
const isHighlighted = movie.vote_average ? movie.vote_average > 6 : false const isHighlighted = movie.vote_average ? movie.vote_average > 6 : false
const borderColor = isHighlighted ? "border-orange-300" : "border-gray-300"
posters.push( posters.push(
<Poster className=" w-24 sm:w-36 aspect-poster rounded-xl overflow-hidden flex justify-center relative" isHighlighted={isHighlighted} movie={movie} key={movie.id} index={i} <Poster className={"w-16 sm:w-28 xl:w-36 aspect-poster rounded-xl border-2 overflow-hidden relative flex item-center justify-center " + borderColor} isHighlighted={isHighlighted} movie={movie} key={movie.id} index={i}
listSimilar={tmdb.getSimilar} listSimilar={tmdb.getSimilar}
config={config} config={config}
setMovies={setMovies} setMovies={setMovies}
@ -80,12 +83,13 @@ function Poster({ className, movie, config, listSimilar, setMovies, setChosenMov
const [hasError, setHasError] = useState(false) const [hasError, setHasError] = useState(false)
return <><div className={className} > return <><div className={className} >
{isLoading ? {isLoading ?
<div className="m-auto text-center h-full w-full fixed"> <div className="m-auto text-center h-fit w-fit">
<div className="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div> <div className="lds-spinner align-middle"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div>
: <div className="z-10 h-full w-full absolute cursor-pointer" onClick={() => clickHandler()}></div> : <div className="z-10 h-full w-full absolute cursor-pointer hover:bg-white/40" onClick={() => clickHandler()}></div>
} }
{hasError ?? <div className="m-auto text-center"></div>} {hasError ?? <div className="m-auto text-center"></div>}
<img <img
className="animate-blur object-cover w-full"
src={tmdb.makeImgUrl(movie.poster_path ?? "")} src={tmdb.makeImgUrl(movie.poster_path ?? "")}
onLoad={() => setIsLoading(false)} onLoad={() => setIsLoading(false)}
onError={() => setHasError(true)} onError={() => setHasError(true)}

View File

@ -19,19 +19,24 @@ export function Sidebar({ movie, similarMoviesAvailable, watchProviders, config,
"Summary unavailable." : "Summary unavailable." :
"" ""
return <div id="sidebar" className={className}> return <div id="sidebar" className={className}>
<h1 className="font-bold text-lg my-4 h-14 overflow-hidden">{movie?.title ?? "loading"}</h1> <h1 className="font-bold text-base sm:text-lg text-nowrap sm:text-wrap my-1 sm:my-4 sm:h-14 overflow-hidden">{movie?.title ?? "loading"}</h1>
<a href={tmdb.makeMovieLink(movie)} target="_blank" rel="noopener noreferrer" className="w-72 aspect-poster flex justify-center"> <div className="flex flex-row sm:flex-col gap-2 justify-center sm:items-center">
<a href={tmdb.makeMovieLink(movie)} target="_blank" rel="noopener noreferrer" className="h-32 sm:h-96 aspect-poster flex justify-center">
<CrossfadeImage src={tmdb.makeImgUrl(movie.poster_path ?? "")} style={{ <CrossfadeImage src={tmdb.makeImgUrl(movie.poster_path ?? "")} style={{
objectFit: "clip", objectFit: "clip",
height: "100%" height: "100%"
}} /> }} />
</a> </a>
{similarMoviesAvailable ? "" : "No hay data suficiente para darte pelílculas relacionadas."} {similarMoviesAvailable ? "" : "No hay data suficiente para darte pelílculas relacionadas."}
<p id="summary" className="h-44 overflow-scroll mt-4 p-2">{movie.overview === null ? "Loading" <div className="h-32 sm:h-56 w-64 sm:w-full overflow-scroll sm:mt-4 p-2 sm:p-4 text-sm sm:text-base flex justify-center items-center border-2 border-slate-800 rounded-lg ">
{/* TODO: make the apology vertically centered */}
<p id="summary" className="self-start">{movie.overview === null ? "Loading"
: movie.overview === "" ? apology : movie.overview === "" ? apology
: movie.overview} : movie.overview}
</p> </p>
<WhereToWatch watchProviders={watchProviders} config={config} className="w-full h-32 flex flex-col justify-end mb-4" /> </div>
</div>
<WhereToWatch watchProviders={watchProviders} config={config} className="w-full h-16 sm:h-32 flex flex-col justify-center mb-1 sm:mb-4 text-xs sm:text-base" />
</div > </div >

View File

@ -53,6 +53,7 @@ export default {
* Calls TMDB/similar, then fires the callback with res.data.results.unshift(movie) as argument * Calls TMDB/similar, then fires the callback with res.data.results.unshift(movie) as argument
*/ */
getSimilar: async function({ language, page }: Config, movie: Movie, setMovies: Function, setSimilarMoviesAvailable: Function) { getSimilar: async function({ language, page }: Config, movie: Movie, setMovies: Function, setSimilarMoviesAvailable: Function) {
let res = await tmdb.get(movie.id + '/similar', { let res = await tmdb.get(movie.id + '/similar', {
params: { params: {
language, language,

View File

@ -77,7 +77,7 @@ function watchProvidersExist(watchProviders: any) {
} }
export function WhereToWatch({ watchProviders, config, className }: WhereToWatchProps) { export function WhereToWatch({ watchProviders, config, className }: WhereToWatchProps) {
return <><footer id="sidebar-footer" className={className}> return <><footer className={className}>
{watchProvidersExist(watchProviders) ? {watchProvidersExist(watchProviders) ?
<> <>
<div className="flex w-full justify-around"> <div className="flex w-full justify-around">
@ -85,7 +85,7 @@ export function WhereToWatch({ watchProviders, config, className }: WhereToWatch
{watchProviders?.rent?.length > 0 ? <div className=""><h1>{config.language == "es" ? "Alquiler:" : config.language == "en" ? "To Rent:" : ""}</h1><ProviderIcons providerList={watchProviders.rent} link={watchProviders.link} /></div> : ""} {watchProviders?.rent?.length > 0 ? <div className=""><h1>{config.language == "es" ? "Alquiler:" : config.language == "en" ? "To Rent:" : ""}</h1><ProviderIcons providerList={watchProviders.rent} link={watchProviders.link} /></div> : ""}
{watchProviders?.buy?.length > 0 ? <div className="watch-providers"><h1>{config.language == "es" ? "Comprar:" : config.language == "en" ? "To Buy:" : ""}</h1><ProviderIcons providerList={watchProviders.buy} link={watchProviders.link} /></div> : ""} {watchProviders?.buy?.length > 0 ? <div className="watch-providers"><h1>{config.language == "es" ? "Comprar:" : config.language == "en" ? "To Buy:" : ""}</h1><ProviderIcons providerList={watchProviders.buy} link={watchProviders.link} /></div> : ""}
</div> </div>
<p className="w-fit mx-auto mt-4">Powered by Just Watch.</p> <p className="w-fit mx-auto mt-1 sm:mt-4">Powered by Just Watch.</p>
</> : </> :
<p className="w-fit mx-auto">{config.language == "es" ? "Por el momento no hay opciónes streaming." : config.language == "en" ? "There are no online viewing options right now." : ""}</p> <p className="w-fit mx-auto">{config.language == "es" ? "Por el momento no hay opciónes streaming." : config.language == "en" ? "There are no online viewing options right now." : ""}</p>
} }

View File

@ -6,6 +6,15 @@ export default {
aspectRatio: { aspectRatio: {
poster: "2 / 3", poster: "2 / 3",
}, },
keyframes: {
blur: {
"0%": { filter: "blur(10px)" },
"100%": { filter: "blur(0px)" },
},
},
animation: {
blur: "blur 0.5s linear",
},
}, },
}, },
plugins: [], plugins: [],