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 { MovieWall } from './objects/movie-wall'
|
||||
import { HamburgerMenu } from './objects/HamburgerMenu'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef, useLayoutEffect } from 'react'
|
||||
import tmdb from './objects/tmdb'
|
||||
import { Sidebar } from './objects/sidebar'
|
||||
import { Movie } from './objects/movie-wall'
|
||||
|
@ -44,7 +44,7 @@ function App() {
|
|||
const [chosenMovie, setChosenMovie] = useState(new MovieClass())
|
||||
const [similarMoviesAvailable, setSimilarMoviesAvailable] = useState(true)
|
||||
const [watchProviders, setWatchProviders] = useState(new WatchProvidersClass())
|
||||
useEffect(() => { tmdb.getPopular(config, setMoviesHandler) }, [])
|
||||
useEffect(() => { tmdb.getPopular(config, setMovies) }, [])
|
||||
useEffect(() => {
|
||||
const bgString = chosenMovie.backdrop_path ?
|
||||
tmdb.makeBgImgUrl(chosenMovie.backdrop_path)
|
||||
|
@ -58,26 +58,26 @@ function App() {
|
|||
useEffect(() => {
|
||||
console.log("CONFIG CHANGED!!")
|
||||
tmdb.getMovie(config, chosenMovie.id, setChosenMovie)
|
||||
tmdb.getSimilar(config, chosenMovie, setMoviesHandler, setSimilarMoviesAvailable)
|
||||
tmdb.getSimilar(config, chosenMovie, setMovies, setSimilarMoviesAvailable)
|
||||
}, [config])
|
||||
const crossfadeImageStyles = {
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
aspectRatio: "16/9",
|
||||
objectFit: "clip"
|
||||
objectFit: "clip",
|
||||
opacity: "40%"
|
||||
}
|
||||
|
||||
const { width } = useWindowDimensions()
|
||||
|
||||
function setMoviesHandler(movies: Movie[]) {
|
||||
setMovies(trimMoviesForScreen(movies))
|
||||
}
|
||||
|
||||
function trimMoviesForScreen(movies: Movie[]) {
|
||||
let desiredLength = 0
|
||||
if (width > 640) {
|
||||
if (width > 1280) {
|
||||
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)
|
||||
while (movies.length < desiredLength && movies.length >= 6) { desiredLength = desiredLength - 6 }
|
||||
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="-z-10 absolute h-full blur-xl">
|
||||
<div className="-z-10 absolute h-full blur-xl bg-black">
|
||||
<CrossfadeImage src={backdrop} style={crossfadeImageStyles} />
|
||||
</div>
|
||||
<header className="bg-black py-2">
|
||||
<HamburgerMenu setConfig={setConfig} />
|
||||
</header>
|
||||
<main className="flex flex-col sm:flex-row 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} />
|
||||
<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-full sm:w-96 h-64 sm:h-full" movie={chosenMovie} similarMoviesAvailable={similarMoviesAvailable} watchProviders={watchProviders} config={config} />
|
||||
<MovieWall
|
||||
className="flex flex-wrap gap-2 max-w-5xl justify-center"
|
||||
movies={movies}
|
||||
setMovies={setMoviesHandler}
|
||||
className={"flex flex-wrap gap-2 w-fit justify-center sm:max-w-lg xl:max-w-5xl "}
|
||||
movies={trimMoviesForScreen(movies)}
|
||||
setMovies={setMovies}
|
||||
config={config}
|
||||
setChosenMovie={setChosenMovie}
|
||||
setSimilarMoviesAvailable={setSimilarMoviesAvailable}
|
||||
/>
|
||||
</main>
|
||||
<footer className="bg-black min-h-10"></footer>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
function getWindowDimensions() {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
|
@ -22,3 +22,4 @@ export default function useWindowDimensions() {
|
|||
|
||||
return windowDimensions;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react"
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import { Config } from "../App"
|
||||
import tmdb from "./tmdb"
|
||||
|
||||
|
@ -35,17 +35,20 @@ interface MovieWallProps extends React.ComponentPropsWithRef<"div"> {
|
|||
setChosenMovie: React.Dispatch<React.SetStateAction<any>>;
|
||||
setSimilarMoviesAvailable: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
config: Config;
|
||||
className: string
|
||||
className: string;
|
||||
}
|
||||
|
||||
export function MovieWall({ movies, setMovies, config, setChosenMovie, setSimilarMoviesAvailable, className }: MovieWallProps) {
|
||||
|
||||
|
||||
const posters: React.ReactElement[] = []
|
||||
for (let i = 0; i < movies.length; i++) {
|
||||
const movie = movies[i]
|
||||
const isHighlighted = movie.vote_average ? movie.vote_average > 6 : false
|
||||
|
||||
const borderColor = isHighlighted ? "border-orange-300" : "border-gray-300"
|
||||
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}
|
||||
config={config}
|
||||
setMovies={setMovies}
|
||||
|
@ -80,12 +83,13 @@ function Poster({ className, movie, config, listSimilar, setMovies, setChosenMov
|
|||
const [hasError, setHasError] = useState(false)
|
||||
return <><div className={className} >
|
||||
{isLoading ?
|
||||
<div className="m-auto text-center h-full w-full fixed">
|
||||
<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="z-10 h-full w-full absolute cursor-pointer" onClick={() => clickHandler()}></div>
|
||||
<div className="m-auto text-center h-fit w-fit">
|
||||
<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 hover:bg-white/40" onClick={() => clickHandler()}></div>
|
||||
}
|
||||
{hasError ?? <div className="m-auto text-center">⚠️</div>}
|
||||
<img
|
||||
className="animate-blur object-cover w-full"
|
||||
src={tmdb.makeImgUrl(movie.poster_path ?? "")}
|
||||
onLoad={() => setIsLoading(false)}
|
||||
onError={() => setHasError(true)}
|
||||
|
|
|
@ -19,19 +19,24 @@ export function Sidebar({ movie, similarMoviesAvailable, watchProviders, config,
|
|||
"Summary unavailable." :
|
||||
""
|
||||
return <div id="sidebar" className={className}>
|
||||
<h1 className="font-bold text-lg my-4 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">
|
||||
<CrossfadeImage src={tmdb.makeImgUrl(movie.poster_path ?? "")} style={{
|
||||
objectFit: "clip",
|
||||
height: "100%"
|
||||
}} />
|
||||
</a>
|
||||
{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"
|
||||
: movie.overview === "" ? apology
|
||||
: movie.overview}
|
||||
</p>
|
||||
<WhereToWatch watchProviders={watchProviders} config={config} className="w-full h-32 flex flex-col justify-end mb-4" />
|
||||
<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>
|
||||
<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={{
|
||||
objectFit: "clip",
|
||||
height: "100%"
|
||||
}} />
|
||||
</a>
|
||||
{similarMoviesAvailable ? "" : "No hay data suficiente para darte pelílculas relacionadas."}
|
||||
<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}
|
||||
</p>
|
||||
</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 >
|
||||
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ export default {
|
|||
* 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) {
|
||||
|
||||
let res = await tmdb.get(movie.id + '/similar', {
|
||||
params: {
|
||||
language,
|
||||
|
|
|
@ -77,7 +77,7 @@ function watchProvidersExist(watchProviders: any) {
|
|||
|
||||
}
|
||||
export function WhereToWatch({ watchProviders, config, className }: WhereToWatchProps) {
|
||||
return <><footer id="sidebar-footer" className={className}>
|
||||
return <><footer className={className}>
|
||||
{watchProvidersExist(watchProviders) ?
|
||||
<>
|
||||
<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?.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>
|
||||
<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 >
|
||||
</>
|
||||
|
|
|
@ -6,6 +6,15 @@ export default {
|
|||
aspectRatio: {
|
||||
poster: "2 / 3",
|
||||
},
|
||||
keyframes: {
|
||||
blur: {
|
||||
"0%": { filter: "blur(10px)" },
|
||||
"100%": { filter: "blur(0px)" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
blur: "blur 0.5s linear",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
|
Loading…
Reference in New Issue