Compare commits
4 Commits
9488795186
...
f2427ee6c7
Author | SHA1 | Date | |
---|---|---|---|
f2427ee6c7 | |||
f8de3be779 | |||
7b960d6616 | |||
24324455da |
@ -84,7 +84,6 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
|
|||||||
pEpisode.Title = eDB.Title
|
pEpisode.Title = eDB.Title
|
||||||
pEpisode.Rating = eDB.Rating
|
pEpisode.Rating = eDB.Rating
|
||||||
pEpisode.Plot = eDB.Plot
|
pEpisode.Plot = eDB.Plot
|
||||||
pEpisode.Thumb = eDB.Thumb
|
|
||||||
pEpisode.Runtime = eDB.Runtime
|
pEpisode.Runtime = eDB.Runtime
|
||||||
pEpisode.Aired = eDB.Aired
|
pEpisode.Aired = eDB.Aired
|
||||||
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
||||||
|
@ -90,6 +90,7 @@ func FillMovieFromDB(mDB *movieDB, pMovie *polochon.Movie) {
|
|||||||
pMovie.SortTitle = mDB.SortTitle
|
pMovie.SortTitle = mDB.SortTitle
|
||||||
pMovie.Tagline = mDB.Tagline
|
pMovie.Tagline = mDB.Tagline
|
||||||
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
||||||
|
pMovie.Fanart = imageURL("movies/" + mDB.ImdbID + "-fanart.jpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateFromMovie will update the movieDB from a Movie
|
// updateFromMovie will update the movieDB from a Movie
|
||||||
|
@ -9,8 +9,9 @@ import (
|
|||||||
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
||||||
type TorrentVideo struct {
|
type TorrentVideo struct {
|
||||||
*polochon.Torrent
|
*polochon.Torrent
|
||||||
Img string `json:"img"`
|
Thumb string `json:"thumb"`
|
||||||
Video polochon.Video `json:"video,omitempty"`
|
Fanart string `json:"fanart"`
|
||||||
|
Video polochon.Video `json:"video,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTorrentVideo returns a new TorrentVideo
|
// NewTorrentVideo returns a new TorrentVideo
|
||||||
@ -47,11 +48,13 @@ func (t *TorrentVideo) Update(detailer polochon.Detailer, db *sqlx.DB, log *logr
|
|||||||
if err := GetShow(db, v.Show); err != nil {
|
if err := GetShow(db, v.Show); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Img = v.Show.Poster
|
t.Fanart = v.Show.Fanart
|
||||||
|
t.Thumb = v.Show.Poster
|
||||||
v.Show = nil
|
v.Show = nil
|
||||||
}
|
}
|
||||||
case *polochon.Movie:
|
case *polochon.Movie:
|
||||||
t.Img = v.Thumb
|
t.Thumb = v.Thumb
|
||||||
|
t.Fanart = v.Fanart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,51 @@ func PolochonMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
|
|||||||
return env.RenderJSON(w, movies)
|
return env.RenderJSON(w, movies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMovieHandler will return a single movie
|
||||||
|
func GetMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
|
||||||
|
user := auth.GetCurrentUser(r, env.Log)
|
||||||
|
|
||||||
|
client, err := user.NewPapiClient(env.Database)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
movies, err := client.GetMovies()
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moviesWishlist, err := models.GetMovieWishlist(env.Database, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pMovie, _ := movies.Has(id)
|
||||||
|
movie := New(
|
||||||
|
id,
|
||||||
|
client,
|
||||||
|
pMovie,
|
||||||
|
moviesWishlist.IsMovieInWishlist(id),
|
||||||
|
)
|
||||||
|
|
||||||
|
detailers := []polochon.Detailer{env.Backend.Detailer}
|
||||||
|
err = movie.GetDetails(env, detailers)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
|
||||||
|
err = movie.GetTorrents(env, torrenters)
|
||||||
|
if err != nil {
|
||||||
|
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.RenderJSON(w, movie)
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshMovieHandler refreshes details for a movie
|
// RefreshMovieHandler refreshes details for a movie
|
||||||
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
@ -150,7 +150,13 @@ func (m *Movie) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download poster
|
// Download poster
|
||||||
err = web.Download(m.Thumb, m.imgFile(), true)
|
err = web.Download(m.Thumb, m.imgFile("thumb"), 300)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got error trying to download the poster %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download fanart
|
||||||
|
err = web.Download(m.Fanart, m.imgFile("fanart"), 960)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("got error trying to download the poster %q", err)
|
log.Errorf("got error trying to download the poster %q", err)
|
||||||
}
|
}
|
||||||
@ -237,13 +243,19 @@ func (m *Movie) RefreshTorrents(env *web.Env, torrenters []polochon.Torrenter) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// imgURL returns the default image url
|
// imgURL returns the default image url
|
||||||
func (m *Movie) imgURL() string {
|
func (m *Movie) imgURL(imgType string) string {
|
||||||
return fmt.Sprintf("movies/%s.jpg", m.ImdbID)
|
var location string
|
||||||
|
if imgType == "thumb" {
|
||||||
|
location = m.ImdbID
|
||||||
|
} else {
|
||||||
|
location = m.ImdbID + "-" + imgType
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("movies/%s.jpg", location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// imgFile returns the image location on disk
|
// imgFile returns the image location on disk
|
||||||
func (m *Movie) imgFile() string {
|
func (m *Movie) imgFile(imgType string) string {
|
||||||
return filepath.Join(models.PublicDir, "img", m.imgURL())
|
return filepath.Join(models.PublicDir, "img", m.imgURL(imgType))
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPolochonMovies returns an array of the user's polochon movies
|
// getPolochonMovies returns an array of the user's polochon movies
|
||||||
|
@ -41,6 +41,7 @@ func setupRoutes(env *web.Env) {
|
|||||||
env.Handle("/movies/explore/options", extmedias.MovieExplorerOptions).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/explore/options", extmedias.MovieExplorerOptions).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/search/{search}", movies.SearchMovie).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/search/{search}", movies.SearchMovie).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE")
|
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE")
|
||||||
|
env.Handle("/movies/{id:tt[0-9]+}", movies.GetMovieHandler).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
|
@ -170,24 +170,24 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
}
|
}
|
||||||
// Download the show images
|
// Download the show images
|
||||||
for _, img := range []struct {
|
for _, img := range []struct {
|
||||||
url string
|
url string
|
||||||
urlType string
|
urlType string
|
||||||
scale bool
|
maxWidth uint
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
url: s.Show.Banner,
|
url: s.Show.Banner,
|
||||||
urlType: "banner",
|
urlType: "banner",
|
||||||
scale: false,
|
maxWidth: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: s.Show.Fanart,
|
url: s.Show.Fanart,
|
||||||
urlType: "fanart",
|
urlType: "fanart",
|
||||||
scale: false,
|
maxWidth: 960,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: s.Show.Poster,
|
url: s.Show.Poster,
|
||||||
urlType: "poster",
|
urlType: "poster",
|
||||||
scale: true,
|
maxWidth: 300,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if img.url == "" {
|
if img.url == "" {
|
||||||
@ -197,7 +197,7 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
if _, err := os.Stat(s.imgFile(img.urlType)); err == nil {
|
if _, err := os.Stat(s.imgFile(img.urlType)); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := web.Download(img.url, s.imgFile(img.urlType), img.scale); err != nil {
|
if err := web.Download(img.url, s.imgFile(img.urlType), img.maxWidth); err != nil {
|
||||||
env.Log.Errorf("failed to dowload %s: %s", img.urlType, err)
|
env.Log.Errorf("failed to dowload %s: %s", img.urlType, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := web.Download(e.Thumb, fileName, false)
|
err := web.Download(e.Thumb, fileName, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Errorf("failed to dowload the thumb for season %d episode %d ( %s ) : %s", e.Season, e.Episode, e.Thumb, err)
|
env.Log.Errorf("failed to dowload the thumb for season %d episode %d ( %s ) : %s", e.Season, e.Episode, e.Thumb, err)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Download used for downloading file
|
// Download used for downloading file
|
||||||
var Download = func(srcURL, dest string, scale bool) error {
|
var Download = func(srcURL, dest string, maxWidth uint) error {
|
||||||
if err := createDirectory(dest); err != nil {
|
if err := createDirectory(dest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -36,8 +36,8 @@ var Download = func(srcURL, dest string, scale bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if scale {
|
if maxWidth != 0 {
|
||||||
image = resize.Resize(300, 0, image, resize.Lanczos3)
|
image = resize.Resize(maxWidth, 0, image, resize.Lanczos3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the file
|
// Create the file
|
||||||
|
@ -48,6 +48,17 @@ export function getMovieDetails(imdbId) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchMovieDetails(imdbId) {
|
||||||
|
return request(
|
||||||
|
"MOVIE_FETCH_DETAILS",
|
||||||
|
configureAxios().get(`/movies/${imdbId}`),
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
imdbId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||||
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
|
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
|
||||||
fetchMovies(lastFetchUrl),
|
fetchMovies(lastFetchUrl),
|
||||||
|
@ -33,6 +33,7 @@ import { AdminPanel } from "./components/admins/panel";
|
|||||||
import { Notifications } from "./components/notifications/notifications";
|
import { Notifications } from "./components/notifications/notifications";
|
||||||
import { Alert } from "./components/alerts/alert";
|
import { Alert } from "./components/alerts/alert";
|
||||||
import MovieList from "./components/movies/list";
|
import MovieList from "./components/movies/list";
|
||||||
|
import { MovieDetails } from "./components/movies/details";
|
||||||
import { AppNavBar } from "./components/navbar";
|
import { AppNavBar } from "./components/navbar";
|
||||||
import { WsHandler } from "./components/websocket";
|
import { WsHandler } from "./components/websocket";
|
||||||
import { ShowDetails } from "./components/shows/details";
|
import { ShowDetails } from "./components/shows/details";
|
||||||
@ -66,6 +67,7 @@ const App = () => (
|
|||||||
/>
|
/>
|
||||||
<Route path="/movies/polochon" exact component={MovieList} />
|
<Route path="/movies/polochon" exact component={MovieList} />
|
||||||
<Route path="/movies/wishlist" exact component={MovieList} />
|
<Route path="/movies/wishlist" exact component={MovieList} />
|
||||||
|
<Route path="/movies/details/:imdbId" exact component={MovieDetails} />
|
||||||
<Route path="/movies/search/:search" exact component={MovieList} />
|
<Route path="/movies/search/:search" exact component={MovieList} />
|
||||||
<Route
|
<Route
|
||||||
path="/movies/explore/:source/:category"
|
path="/movies/explore/:source/:category"
|
||||||
@ -81,7 +83,9 @@ const App = () => (
|
|||||||
component={ShowList}
|
component={ShowList}
|
||||||
/>
|
/>
|
||||||
<Route path="/shows/details/:imdbId" exact component={ShowDetails} />
|
<Route path="/shows/details/:imdbId" exact component={ShowDetails} />
|
||||||
<Route render={() => <Redirect to="/movies/explore/yts/seeds" />} />
|
<Route
|
||||||
|
render={() => <Redirect to="/movies/explore/trakttv/trending" />}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSelector } from "react-redux";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Fanart = () => {
|
export const Fanart = ({ url }) => {
|
||||||
const url = useSelector((state) => state.show.show.fanart_url);
|
if (url == "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="show-fanart mx-n3 mt-n1">
|
<div className="show-fanart mx-n3 mt-n1">
|
||||||
<img
|
<img
|
||||||
@ -12,3 +14,9 @@ export const Fanart = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Fanart.propTypes = {
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
Fanart.defaultProps = {
|
||||||
|
url: "",
|
||||||
|
};
|
162
frontend/js/components/movies/details.js
Normal file
162
frontend/js/components/movies/details.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchMovieDetails,
|
||||||
|
getMovieDetails,
|
||||||
|
movieWishlistToggle,
|
||||||
|
} from "../../actions/movies";
|
||||||
|
import { searchMovieSubtitle } from "../../actions/subtitles";
|
||||||
|
|
||||||
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
|
import { Fanart } from "../details/fanart";
|
||||||
|
import { Plot } from "../details/plot";
|
||||||
|
import { Rating } from "../details/rating";
|
||||||
|
import { ReleaseDate } from "../details/releaseDate";
|
||||||
|
import { Title } from "../details/title";
|
||||||
|
import { PolochonMetadata } from "../details/polochon";
|
||||||
|
import { TrackingLabel } from "../details/tracking";
|
||||||
|
import { Genres } from "../details/genres";
|
||||||
|
import { Runtime } from "../details/runtime";
|
||||||
|
|
||||||
|
import { DownloadAndStream } from "../buttons/download";
|
||||||
|
import { ImdbBadge } from "../buttons/imdb";
|
||||||
|
import { TorrentsButton } from "../buttons/torrents";
|
||||||
|
import { SubtitlesButton } from "../buttons/subtitles";
|
||||||
|
import { ShowMore } from "../buttons/showMore";
|
||||||
|
|
||||||
|
export const MovieDetails = ({ match }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const loading = useSelector((state) => state.movie.loading);
|
||||||
|
const fanartUrl = useSelector((state) => state.movie.movie.fanart);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchMovieDetails(match.params.imdbId));
|
||||||
|
}, [dispatch, match]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Fanart url={fanartUrl} />
|
||||||
|
<Header />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MovieDetails.propTypes = {
|
||||||
|
match: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
audioCodec,
|
||||||
|
container,
|
||||||
|
fetchingDetails,
|
||||||
|
fetchingSubtitles,
|
||||||
|
genres,
|
||||||
|
imdb_id: imdbId,
|
||||||
|
plot,
|
||||||
|
polochon_url: polochonUrl,
|
||||||
|
poster_url: posterUrl,
|
||||||
|
quality,
|
||||||
|
rating,
|
||||||
|
runtime,
|
||||||
|
size,
|
||||||
|
subtitles,
|
||||||
|
title,
|
||||||
|
torrents,
|
||||||
|
videoCodec,
|
||||||
|
votes,
|
||||||
|
wishlisted,
|
||||||
|
year,
|
||||||
|
release_group: releaseGroup,
|
||||||
|
} = useSelector((state) => state.movie.movie);
|
||||||
|
|
||||||
|
const inLibrary = polochonUrl !== "";
|
||||||
|
|
||||||
|
if (!imdbId || imdbId === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-12 col-lg-10 offset-lg-1 mb-3">
|
||||||
|
<div className="d-flex flex-column flex-md-row">
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<img className="overflow-hidden object-fit-cover" src={posterUrl} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-sm-1">
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="card-title">
|
||||||
|
<Title
|
||||||
|
title={title}
|
||||||
|
wishlisted={wishlisted}
|
||||||
|
wishlist={() =>
|
||||||
|
dispatch(movieWishlistToggle(imdbId, wishlisted))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<ReleaseDate date={year} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Runtime runtime={runtime} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Genres genres={genres} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Rating rating={rating} votes={votes} />
|
||||||
|
</p>
|
||||||
|
<div className="card-text">
|
||||||
|
<ImdbBadge imdbId={imdbId} />
|
||||||
|
<DownloadAndStream
|
||||||
|
url={polochonUrl}
|
||||||
|
name={title}
|
||||||
|
subtitles={subtitles}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="card-text">
|
||||||
|
<TrackingLabel inLibrary={inLibrary} wishlisted={wishlisted} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<PolochonMetadata
|
||||||
|
quality={quality}
|
||||||
|
releaseGroup={releaseGroup}
|
||||||
|
container={container}
|
||||||
|
audioCodec={audioCodec}
|
||||||
|
videoCodec={videoCodec}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Plot plot={plot} />
|
||||||
|
</p>
|
||||||
|
<div className="card-text">
|
||||||
|
<ShowMore id={imdbId} inLibrary={inLibrary}>
|
||||||
|
<TorrentsButton
|
||||||
|
torrents={torrents}
|
||||||
|
searching={fetchingDetails}
|
||||||
|
search={() => dispatch(getMovieDetails(imdbId))}
|
||||||
|
url={`#/torrents/search/movies/${encodeURI(title)}`}
|
||||||
|
/>
|
||||||
|
<SubtitlesButton
|
||||||
|
inLibrary={inLibrary}
|
||||||
|
fetchingSubtitles={fetchingSubtitles}
|
||||||
|
subtitles={subtitles}
|
||||||
|
search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))}
|
||||||
|
/>
|
||||||
|
</ShowMore>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -37,7 +37,7 @@ const fetchUrl = (match) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MovieList = ({ match }) => {
|
const MovieList = ({ match, history }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -63,6 +63,10 @@ const MovieList = ({ match }) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const movieDetails = (imdbId) => {
|
||||||
|
history.push("/movies/details/" + imdbId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<ListPosters
|
<ListPosters
|
||||||
@ -73,8 +77,8 @@ const MovieList = ({ match }) => {
|
|||||||
exploreOptions={exploreOptions}
|
exploreOptions={exploreOptions}
|
||||||
selectedImdbId={selectedImdbId}
|
selectedImdbId={selectedImdbId}
|
||||||
onClick={selectFunc}
|
onClick={selectFunc}
|
||||||
onDoubleClick={() => {}}
|
onDoubleClick={movieDetails}
|
||||||
onKeyEnter={() => {}}
|
onKeyEnter={movieDetails}
|
||||||
params={match.params}
|
params={match.params}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
@ -105,6 +109,7 @@ MovieList.propTypes = {
|
|||||||
updateFilter: PropTypes.func,
|
updateFilter: PropTypes.func,
|
||||||
movieWishlistToggle: PropTypes.func,
|
movieWishlistToggle: PropTypes.func,
|
||||||
selectMovie: PropTypes.func,
|
selectMovie: PropTypes.func,
|
||||||
|
history: PropTypes.object,
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ Search.propTypes = {
|
|||||||
|
|
||||||
const MoviesDropdown = () => (
|
const MoviesDropdown = () => (
|
||||||
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
||||||
<LinkContainer to="/movies/explore/yts/seeds">
|
<LinkContainer to="/movies/explore/trakttv/trending">
|
||||||
<NavDropdown.Item>Discover</NavDropdown.Item>
|
<NavDropdown.Item>Discover</NavDropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
<LinkContainer to="/movies/polochon">
|
<LinkContainer to="/movies/polochon">
|
||||||
|
@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
|
|||||||
|
|
||||||
import Loader from "../loader/loader";
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
import { Fanart } from "./details/fanart";
|
import { Fanart } from "../details/fanart";
|
||||||
import { Header } from "./details/header";
|
import { Header } from "./details/header";
|
||||||
import { SeasonsList } from "./details/seasons";
|
import { SeasonsList } from "./details/seasons";
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import { fetchShowDetails } from "../../actions/shows";
|
|||||||
export const ShowDetails = ({ match }) => {
|
export const ShowDetails = ({ match }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const loading = useSelector((state) => state.show.loading);
|
const loading = useSelector((state) => state.show.loading);
|
||||||
|
const fanartUrl = useSelector((state) => state.show.show.fanart_url);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchShowDetails(match.params.imdbId));
|
dispatch(fetchShowDetails(match.params.imdbId));
|
||||||
@ -24,7 +25,7 @@ export const ShowDetails = ({ match }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Fanart />
|
<Fanart url={fanartUrl} />
|
||||||
<div className="row no-gutters">
|
<div className="row no-gutters">
|
||||||
<Header />
|
<Header />
|
||||||
<SeasonsList />
|
<SeasonsList />
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Poster = ({ url }) => {
|
export const Poster = ({ thumb, fanart }) => {
|
||||||
if (!url || url === "") {
|
if (thumb === "" && fanart === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-2 d-none d-md-block">
|
<div className="col-md-2">
|
||||||
<img className="card-img" src={url} />
|
{thumb !== "" && (
|
||||||
|
<img className="card-img d-none d-sm-block" src={thumb} />
|
||||||
|
)}
|
||||||
|
{fanart !== "" && (
|
||||||
|
<img className="card-img d-block d-sm-none" src={fanart} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Poster.propTypes = {
|
Poster.propTypes = {
|
||||||
url: PropTypes.string,
|
thumb: PropTypes.string,
|
||||||
|
fanart: PropTypes.string,
|
||||||
|
};
|
||||||
|
Poster.defaultProps = {
|
||||||
|
thumb: "",
|
||||||
|
fanart: "",
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { Torrent } from "./torrent";
|
import { Torrent } from "./torrent";
|
||||||
import { Poster } from "./poster";
|
import { Poster } from "./poster";
|
||||||
@ -17,18 +18,32 @@ export const TorrentGroup = ({ torrentKey }) => {
|
|||||||
const title = (torrent) => {
|
const title = (torrent) => {
|
||||||
switch (torrent.type) {
|
switch (torrent.type) {
|
||||||
case "movie":
|
case "movie":
|
||||||
return torrent.video.title;
|
return (
|
||||||
|
<Link
|
||||||
|
className="link-unstyled"
|
||||||
|
to={`/movies/details/${torrent.video.imdb_id}`}
|
||||||
|
>
|
||||||
|
{torrent.video.title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
case "episode":
|
case "episode":
|
||||||
return torrent.video.show_title;
|
return (
|
||||||
|
<Link
|
||||||
|
className="link-unstyled"
|
||||||
|
to={`/shows/details/${torrent.video.show_imdb_id}`}
|
||||||
|
>
|
||||||
|
{torrent.video.show_title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return "Files";
|
return <span>Files</span>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-100 mb-3 card">
|
<div className="w-100 mb-3 card">
|
||||||
<div className="row no-gutters">
|
<div className="row no-gutters">
|
||||||
<Poster url={torrents[0].img} />
|
<Poster thumb={torrents[0].thumb} fanart={torrents[0].fanart} />
|
||||||
<div className="col-sm">
|
<div className="col-sm">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h4 className="card-title">{title(torrents[0])}</h4>
|
<h4 className="card-title">{title(torrents[0])}</h4>
|
||||||
|
@ -5,6 +5,7 @@ import { enableMapSet } from "immer";
|
|||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
import movies from "./movies";
|
import movies from "./movies";
|
||||||
|
import movie from "./movie";
|
||||||
import shows from "./shows";
|
import shows from "./shows";
|
||||||
import show from "./show";
|
import show from "./show";
|
||||||
import user from "./users";
|
import user from "./users";
|
||||||
@ -16,6 +17,7 @@ import notifications from "./notifications";
|
|||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
movies,
|
movies,
|
||||||
|
movie,
|
||||||
shows,
|
shows,
|
||||||
show,
|
show,
|
||||||
user,
|
user,
|
||||||
|
77
frontend/js/reducers/movie.js
Normal file
77
frontend/js/reducers/movie.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
import { formatSubtitle, formatMovie } from "./utils";
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
loading: false,
|
||||||
|
movie: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state = defaultState, action) =>
|
||||||
|
produce(state, (draft) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "MOVIE_FETCH_DETAILS_PENDING":
|
||||||
|
draft.loading = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "MOVIE_FETCH_DETAILS_FULFILLED": {
|
||||||
|
draft.movie = formatMovie(action.payload.response.data);
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_GET_DETAILS_PENDING": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.movie.fetchingDetails = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_GET_DETAILS_FULFILLED": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.movie = formatMovie(action.payload.response.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
draft.movie.fetchingSubtitles.push(lang);
|
||||||
|
if (draft.movie.subtitles.get(lang)) {
|
||||||
|
draft.movie.subtitles.get(lang).searching = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_SUBTITLES_UPDATE_FULFILLED": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
let data = action.payload.response.data;
|
||||||
|
draft.movie.fetchingSubtitles = draft.movie.fetchingSubtitles.filter(
|
||||||
|
(l) => l != lang
|
||||||
|
);
|
||||||
|
if (data) {
|
||||||
|
draft.movie.subtitles.set(lang, formatSubtitle(data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return draft;
|
||||||
|
}
|
||||||
|
});
|
@ -1,7 +1,6 @@
|
|||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import { formatTorrents } from "../utils";
|
import { formatSubtitle, formatMovie } from "./utils";
|
||||||
import { formatSubtitle, formatSubtitles } from "./utils";
|
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -11,14 +10,6 @@ const defaultState = {
|
|||||||
exploreOptions: {},
|
exploreOptions: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMovie = (movie) => {
|
|
||||||
movie.fetchingDetails = false;
|
|
||||||
movie.fetchingSubtitles = [];
|
|
||||||
movie.torrents = formatTorrents(movie);
|
|
||||||
movie.subtitles = formatSubtitles(movie.subtitles);
|
|
||||||
return movie;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatMovies = (movies = []) => {
|
const formatMovies = (movies = []) => {
|
||||||
let allMoviesInPolochon = true;
|
let allMoviesInPolochon = true;
|
||||||
movies.map((movie) => {
|
movies.map((movie) => {
|
||||||
@ -66,10 +57,17 @@ export default (state = defaultState, action) =>
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "MOVIE_GET_DETAILS_PENDING":
|
case "MOVIE_GET_DETAILS_PENDING":
|
||||||
|
if (!draft.movies.get(action.payload.main.imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
draft.movies.get(action.payload.main.imdbId).fetchingDetails = true;
|
draft.movies.get(action.payload.main.imdbId).fetchingDetails = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MOVIE_GET_DETAILS_FULFILLED":
|
case "MOVIE_GET_DETAILS_FULFILLED":
|
||||||
|
if (!draft.movies.get(action.payload.main.imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
draft.movies.set(
|
draft.movies.set(
|
||||||
action.payload.response.data.imdb_id,
|
action.payload.response.data.imdb_id,
|
||||||
formatMovie(action.payload.response.data)
|
formatMovie(action.payload.response.data)
|
||||||
@ -92,6 +90,10 @@ export default (state = defaultState, action) =>
|
|||||||
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
|
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
|
||||||
let imdbId = action.payload.main.imdbId;
|
let imdbId = action.payload.main.imdbId;
|
||||||
let lang = action.payload.main.lang;
|
let lang = action.payload.main.lang;
|
||||||
|
if (!draft.movies.get(imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
draft.movies.get(imdbId).fetchingSubtitles.push(lang);
|
draft.movies.get(imdbId).fetchingSubtitles.push(lang);
|
||||||
if (draft.movies.get(imdbId).subtitles.get(lang)) {
|
if (draft.movies.get(imdbId).subtitles.get(lang)) {
|
||||||
draft.movies.get(imdbId).subtitles.get(lang).searching = true;
|
draft.movies.get(imdbId).subtitles.get(lang).searching = true;
|
||||||
@ -103,6 +105,10 @@ export default (state = defaultState, action) =>
|
|||||||
let imdbId = action.payload.main.imdbId;
|
let imdbId = action.payload.main.imdbId;
|
||||||
let lang = action.payload.main.lang;
|
let lang = action.payload.main.lang;
|
||||||
let data = action.payload.response.data;
|
let data = action.payload.response.data;
|
||||||
|
if (!draft.movies.get(imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
draft.movies.get(imdbId).fetchingSubtitles = draft.movies
|
draft.movies.get(imdbId).fetchingSubtitles = draft.movies
|
||||||
.get(imdbId)
|
.get(imdbId)
|
||||||
.fetchingSubtitles.filter((l) => l != lang);
|
.fetchingSubtitles.filter((l) => l != lang);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import { formatTorrents } from "../utils";
|
import { formatTorrents, formatSubtitle, formatSubtitles } from "./utils";
|
||||||
import { formatSubtitle, formatSubtitles } from "./utils";
|
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -20,3 +20,32 @@ export const formatSubtitle = (subtitle) => {
|
|||||||
subtitle.searching = false;
|
subtitle.searching = false;
|
||||||
return subtitle;
|
return subtitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatTorrents = (input) => {
|
||||||
|
if (!input.torrents || input.torrents.length == 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let torrentMap = new Map();
|
||||||
|
input.torrents.forEach((torrent) => {
|
||||||
|
if (!torrent.result || !torrent.result.source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!torrentMap.has(torrent.result.source)) {
|
||||||
|
torrentMap.set(torrent.result.source, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
|
||||||
|
});
|
||||||
|
|
||||||
|
return torrentMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatMovie = (movie) => {
|
||||||
|
movie.fetchingDetails = false;
|
||||||
|
movie.fetchingSubtitles = [];
|
||||||
|
movie.torrents = formatTorrents(movie);
|
||||||
|
movie.subtitles = formatSubtitles(movie.subtitles);
|
||||||
|
return movie;
|
||||||
|
};
|
||||||
|
@ -35,26 +35,5 @@ export const prettySize = (fileSizeInBytes) => {
|
|||||||
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatTorrents = (input) => {
|
|
||||||
if (!input.torrents || input.torrents.length == 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let torrentMap = new Map();
|
|
||||||
input.torrents.forEach((torrent) => {
|
|
||||||
if (!torrent.result || !torrent.result.source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!torrentMap.has(torrent.result.source)) {
|
|
||||||
torrentMap.set(torrent.result.source, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
|
|
||||||
});
|
|
||||||
|
|
||||||
return torrentMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const upperCaseFirst = (string) =>
|
export const upperCaseFirst = (string) =>
|
||||||
string.charAt(0).toUpperCase() + string.slice(1);
|
string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user