make responsive
Gitea/movie-explorer/pipeline/head There was a failure building this commit
Details
Gitea/movie-explorer/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
206b9cdda5
commit
4684cac15a
31
src/App.tsx
31
src/App.tsx
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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 >
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,9 +85,9 @@ 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>
|
||||||
}
|
}
|
||||||
</footer >
|
</footer >
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
Loading…
Reference in New Issue