Improve the wishlist button

This commit is contained in:
Grégoire Delattre 2019-07-08 14:45:16 +02:00
parent 2ab7e5c11f
commit 4703a18b78
16 changed files with 240 additions and 172 deletions

View File

@ -58,6 +58,14 @@ export function deleteMovie(imdbId, lastFetchUrl) {
)
}
export function movieWishlistToggle(imdbId, currentState) {
if (currentState == true) {
return deleteMovieFromWishlist(imdbId)
} else {
return addMovieToWishlist(imdbId)
}
}
export function addMovieToWishlist(imdbId) {
return request(
"MOVIE_ADD_TO_WISHLIST",

View File

@ -65,11 +65,18 @@ export function deleteShowFromWishlist(imdbId) {
)
}
export function showWishlistToggle(currentState, imdbId, season = 0, episode = 0) {
if (currentState === true) {
return deleteShowFromWishlist(imdbId);
} else {
return addShowToWishlist(imdbId, season, episode);
}
}
export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) {
return {
type: "SHOW_UPDATE_STORE_WISHLIST",
payload: {
wishlisted: wishlisted,
imdbId,
season,
episode,

View File

@ -13,7 +13,7 @@ export const ShowMore = ({ children, id, inLibrary }) => {
<span>
<a
onClick={() => setDisplay(true)}
className="badge badge-pill badge-secondary clickable"
className="badge badge-pill badge-secondary clickable mb-1"
>
More options ...
</a>

View File

@ -0,0 +1,17 @@
import React from "react"
import PropTypes from "prop-types"
export const WishlistButton = ({ wishlisted, wishlist }) => {
return (
<span className="mr-2 clickable wishlist-button">
<i
className={`fa ${wishlisted ? "fa-bookmark" : "fa-bookmark-o"}`}
onClick={wishlist}
/>
</span>
);
}
WishlistButton.propTypes = {
wishlisted: PropTypes.bool.isRequired,
wishlist: PropTypes.func.isRequired,
}

View File

@ -6,7 +6,7 @@ export const Rating = ({ rating, votes }) => {
return (
<span>
<i className="fa fa-star-o mr-1"></i>
<i className="fa fa-star mr-1"></i>
{Number(rating).toFixed(1)}
{votes !== 0 &&
<small className="ml-1">({votes} votes)</small>

View File

@ -1,25 +1,23 @@
import React from "react"
import PropTypes from "prop-types"
export const Title = ({ title, xs }) => {
import { WishlistButton } from "../buttons/wishlist"
export const Title = ({ title, wishlist, wishlisted }) => {
if (title === "") { return null; }
if (xs) {
return (<h5>{title}</h5>)
} else {
return (
<span>
<h2 className="d-none d-sm-block">{title}</h2>
<h4 className="d-block d-sm-none">{title}</h4>
</span>
);
}
return (
<span className="title">
<WishlistButton wishlist={wishlist} wishlisted={wishlisted} />
{title}
</span>
);
}
Title.propTypes = {
title: PropTypes.string,
xs: PropTypes.bool,
wishlist: PropTypes.func,
wishlisted: PropTypes.bool,
};
Title.defaultProps = {
title: "",
xs: false,
};

View File

@ -0,0 +1,41 @@
import React from "react"
import PropTypes from "prop-types"
export const TrackingLabel = ({ wishlisted, inLibrary, trackedSeason, trackedEpisode }) => {
if (wishlisted === false) {
return null;
}
if (wishlisted === true && inLibrary === true) {
return null;
}
if (trackedEpisode === null && trackedSeason === null) {
return null;
}
let str = ""
if (trackedSeason === 0 && trackedEpisode === 0) {
str = "All the episodes will be downloaded automatically";
} else if (trackedSeason > 0 && trackedEpisode > 0) {
str = `All the episodes will be downloaded automatically starting from
season ${trackedSeason} episode ${trackedEpisode}`;
} else {
str = "This movie will be downloaded automatically"
}
return (
<span>
<small>
<i className="fa fa-cloud-upload mr-1" />
{str}
</small>
</span>
)
}
TrackingLabel.propTypes = {
wishlisted: PropTypes.bool,
inLibrary: PropTypes.bool,
trackedSeason: PropTypes.number,
trackedEpisode: PropTypes.number,
};

View File

@ -2,9 +2,12 @@ import React from "react"
import PropTypes from "prop-types"
import { Map } from "immutable"
import { inLibrary, isWishlisted } from "../../utils"
import { DownloadAndStream } from "../buttons/download"
import { ImdbBadge } from "../buttons/imdb"
import { TrackingLabel } from "../details/tracking"
import { Genres } from "../details/genres"
import { Plot } from "../details/plot"
import { PolochonMetadata } from "../details/polochon"
@ -20,11 +23,10 @@ const ListDetails = (props) => {
return (
<div className="col-8 col-md-4 list-details pl-1 d-flex align-items-start flex-column">
<div className="video-details flex-fill d-flex flex-column">
<Title title={props.data.get("title")} />
<TrackingLabel
wishlisted={props.data.get("wishlisted")}
trackedSeason={props.data.get("tracked_season")}
trackedEpisode={props.data.get("tracked_episode")}
<Title
title={props.data.get("title")}
wishlist={props.wishlist}
wishlisted={isWishlisted(props.data)}
/>
<ReleaseDate date={props.data.get("year")} />
<Runtime runtime={props.data.get("runtime")} />
@ -41,6 +43,12 @@ const ListDetails = (props) => {
subtitles={props.data.get("subtitles")}
/>
</div>
<TrackingLabel
wishlisted={props.data.get("wishlisted")}
inLibrary={inLibrary(props.data)}
trackedSeason={props.data.get("tracked_season")}
trackedEpisode={props.data.get("tracked_episode")}
/>
<PolochonMetadata
quality={props.data.get("quality")}
releaseGroup={props.data.get("release_group")}
@ -56,37 +64,8 @@ const ListDetails = (props) => {
}
ListDetails.propTypes = {
data: PropTypes.instanceOf(Map),
wishlist: PropTypes.func,
loading: PropTypes.bool,
children: PropTypes.object,
};
export default ListDetails;
const TrackingLabel = (props) => {
let wishlistStr = props.wishlisted ? "Wishlisted" : "";
if (props.trackedEpisode !== null && props.trackedSeason !== null
&& props.trackedEpisode !== undefined && props.trackedSeason !== undefined) {
if ((props.trackedSeason === 0) && (props.trackedEpisode === 0)) {
wishlistStr = "Whole show tracked";
} else {
wishlistStr = `Tracked from season ${props.trackedSeason} episode ${props.trackedEpisode}`;
}
}
if (wishlistStr === "") {
return null;
}
return (
<p>
<span className="badge badge-secondary">
<i className="fa fa-bookmark"></i> {wishlistStr}
</span>
</p>
);
}
TrackingLabel.propTypes = {
wishlisted: PropTypes.bool,
trackedSeason: PropTypes.number,
trackedEpisode: PropTypes.number,
};

View File

@ -2,12 +2,12 @@ import React from "react"
import PropTypes from "prop-types"
import { OrderedMap, Map } from "immutable"
import { connect } from "react-redux"
import { selectMovie, updateFilter } from "../../actions/movies"
import { selectMovie, updateFilter, movieWishlistToggle } from "../../actions/movies"
import ListDetails from "../list/details"
import ListPosters from "../list/posters"
import { inLibrary } from "../../utils"
import { inLibrary, isWishlisted } from "../../utils"
import { ShowMore } from "../buttons/showMore"
@ -24,7 +24,7 @@ function mapStateToProps(state) {
};
}
const mapDispatchToProps = {
selectMovie, updateFilter,
selectMovie, updateFilter, movieWishlistToggle,
};
const MovieList = (props) => {
@ -50,7 +50,11 @@ const MovieList = (props) => {
params={props.match.params}
loading={props.loading}
/>
<ListDetails data={selectedMovie} loading={props.loading}>
<ListDetails
data={selectedMovie}
loading={props.loading}
wishlist={() => props.movieWishlistToggle(selectedMovie.get("imdb_id"), isWishlisted(selectedMovie))}
>
<ShowMore
id={selectedMovie.get("imdb_id")}
inLibrary={inLibrary(selectedMovie)}
@ -79,6 +83,7 @@ MovieList.propTypes = {
filter: PropTypes.string,
loading: PropTypes.bool,
updateFilter: PropTypes.func,
movieWishlistToggle: PropTypes.func,
selectMovie: PropTypes.func,
match: PropTypes.object,
};

View File

@ -1,8 +1,11 @@
import React from "react"
import PropTypes from "prop-types"
import { Map } from "immutable"
import { connect } from "react-redux"
import { inLibrary, prettyEpisodeName } from "../../../utils"
import { showWishlistToggle } from "../../../actions/shows"
import { inLibrary, isEpisodeWishlisted, prettyEpisodeName } from "../../../utils"
import { Plot } from "../../details/plot"
import { PolochonMetadata } from "../../details/polochon"
@ -17,13 +20,28 @@ import { EpisodeSubtitlesButton } from "./subtitlesButton"
import { EpisodeThumb } from "./episodeThumb"
import { EpisodeTorrentsButton } from "./torrentsButton"
export const Episode = (props) => (
const mapStateToProps = (state) => ({
trackedSeason: state.showStore.getIn(["show", "tracked_season"], null),
trackedEpisode: state.showStore.getIn(["show", "tracked_episode"], null),
})
const episode = (props) => (
<div className="d-flex flex-column flex-lg-row mb-3 pb-3 border-bottom border-light">
<EpisodeThumb url={props.data.get("thumb")} />
<div className="d-flex flex-column">
<Title
title={`${props.data.get("episode")}. ${props.data.get("title")}`}
xs
wishlisted={isEpisodeWishlisted(
props.data,
props.trackedSeason,
props.trackedEpisode,
)}
wishlist={() => props.showWishlistToggle(
isEpisodeWishlisted(props.data),
props.data.get("show_imdb_id"),
props.data.get("season"),
props.data.get("episode"),
)}
/>
<ReleaseDate date={props.data.get("aired")} />
<Runtime runtime={props.data.get("runtime")} />
@ -65,7 +83,12 @@ export const Episode = (props) => (
</div>
</div>
)
Episode.propTypes = {
episode.propTypes = {
data: PropTypes.instanceOf(Map).isRequired,
trackedSeason: PropTypes.number,
trackedEpisode: PropTypes.number,
showName: PropTypes.string.isRequired,
showWishlistToggle: PropTypes.func,
};
export const Episode = connect(mapStateToProps, {showWishlistToggle})(episode);

View File

@ -1,14 +1,21 @@
import React from "react"
import PropTypes from "prop-types"
import { Map } from "immutable"
import { connect } from "react-redux"
import { isWishlisted } from "../../../utils"
import { showWishlistToggle } from "../../../actions/shows"
import { TrackHeader } from "./track"
import { ImdbBadge } from "../../buttons/imdb"
import { Plot } from "../../details/plot"
import { Rating } from "../../details/rating"
import { ReleaseDate } from "../../details/releaseDate"
import { Title } from "../../details/title"
import { TrackingLabel } from "../../details/tracking"
export const Header = (props) => (
import { ImdbBadge } from "../../buttons/imdb"
export const header = (props) => (
<div className="card col-12 col-md-10 offset-md-1 mt-n3 mb-3">
<div className="d-flex flex-column flex-md-row">
<div className="d-flex justify-content-center">
@ -19,33 +26,42 @@ export const Header = (props) => (
</div>
<div>
<div className="card-body">
<h5 className="card-title">{props.data.get("title")}</h5>
<p className="card-title">
<Title
title={props.data.get("title")}
wishlisted={isWishlisted(props.data)}
wishlist={() => props.showWishlistToggle(
isWishlisted(props.data), props.data.get("imdb_id"),
)}
/>
</p>
<p className="card-text">
<ReleaseDate date={props.data.get("year")} />
</p>
<p className="card-text">
<Rating rating={props.data.get("rating")} />
</p>
<span className="card-text">
<p className="card-text">
<ImdbBadge imdbId={props.data.get("imdb_id")} />
</span>
</p>
<p className="card-text">
<TrackingLabel
inLibrary={false}
trackedSeason={props.data.get("tracked_season")}
trackedEpisode={props.data.get("tracked_episode")}
/>
</p>
<p className="card-text">
<Plot plot={props.data.get("plot")} />
</p>
<span className="card-text">
<TrackHeader
data={props.data}
addToWishlist={props.addToWishlist}
deleteFromWishlist={props.deleteFromWishlist}
/>
</span>
</div>
</div>
</div>
</div>
);
Header.propTypes = {
header.propTypes = {
data: PropTypes.instanceOf(Map),
deleteFromWishlist: PropTypes.func,
addToWishlist: PropTypes.func,
showWishlistToggle: PropTypes.func,
};
export const Header = connect(null, {showWishlistToggle})(header);

View File

@ -1,82 +0,0 @@
import React from "react"
import PropTypes from "prop-types"
import { Map } from "immutable"
import { connect } from "react-redux"
import { addShowToWishlist, deleteShowFromWishlist } from "../../../actions/shows"
import Tooltip from "react-bootstrap/Tooltip"
import OverlayTrigger from "react-bootstrap/OverlayTrigger"
export const trackHeader = (props) => {
const trackedSeason = props.data.get("tracked_season");
const trackedEpisode = props.data.get("tracked_episode");
const imdbId = props.data.get("imdb_id");
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
const handleClick = () => {
if (wishlisted) {
props.deleteShowFromWishlist(imdbId);
} else {
props.addShowToWishlist(imdbId);
}
}
if (wishlisted) {
const msg = (trackedSeason !== 0 && trackedEpisode !== 0)
? (<p>Show tracked from <strong>season {trackedSeason} episode {trackedEpisode}</strong></p>)
: (<p>Whole show tracked</p>);
return (
<span className="card-text">
{msg}
<a className="btn btn-sm btn-danger" onClick={handleClick}>
<i className="fa fa-bookmark"></i> Untrack the show
</a>
</span>
);
}
return (
<span className="card-text">
<p>Tracking inactive</p>
<a className="btn btn-sm btn-info" onClick={(e) => handleClick(e)}>
<i className="fa fa-bookmark-o"></i> Track the whole show
</a>
</span>
);
}
trackHeader.propTypes = {
data: PropTypes.instanceOf(Map),
addShowToWishlist: PropTypes.func,
deleteShowFromWishlist: PropTypes.func,
};
export const TrackHeader = connect(null, {
addShowToWishlist,
deleteShowFromWishlist,
})(trackHeader);
export const trackButton = (props) => {
const imdbId = props.data.get("show_imdb_id");
const season = props.data.get("season");
const episode = props.data.get("episode");
const tooltipId = `tooltip-${props.data.season}-${props.data.episode}`;
const tooltip = (
<Tooltip id={tooltipId}>Track show from here</Tooltip>
);
return (
<OverlayTrigger placement="top" overlay={tooltip}>
<span className="btn clickable"
onClick={() => props.addShowToWishlist(imdbId, season, episode)}>
<i className="fa fa-bookmark"></i>
</span>
</OverlayTrigger>
);
}
trackButton.propTypes = {
data: PropTypes.instanceOf(Map),
addShowToWishlist: PropTypes.func,
};
export const TrackButton = connect(null, {addShowToWishlist})(trackButton);

View File

@ -2,8 +2,10 @@ import React from "react"
import PropTypes from "prop-types"
import { Map } from "immutable"
import { connect } from "react-redux"
import { selectShow, addShowToWishlist,
deleteShowFromWishlist, getShowDetails, updateFilter } from "../../actions/shows"
import { selectShow, showWishlistToggle,
getShowDetails, updateFilter } from "../../actions/shows"
import { isWishlisted } from "../../utils"
import ListDetails from "../list/details"
import ListPosters from "../list/posters"
@ -18,7 +20,7 @@ function mapStateToProps(state) {
};
}
const mapDispatchToProps = {
selectShow, addShowToWishlist, deleteShowFromWishlist,
selectShow, showWishlistToggle,
getShowDetails, updateFilter,
};
@ -48,7 +50,22 @@ const ShowList = (props) => {
params={props.match.params}
loading={props.loading}
/>
<ListDetails data={selectedShow} loading={props.loading} />
<ListDetails
data={selectedShow}
loading={props.loading}
wishlist={() => props.showWishlistToggle(
isWishlisted(selectedShow), selectedShow.get("imdb_id"),
)}
>
<span>
<button onClick={
() => showDetails(selectedShow.get("imdb_id"))}
className="btn btn-primary btn-sm w-md-100">
<i className="fa fa-external-link mr-1" />
Details
</button>
</span>
</ListDetails>
</div>
);
}
@ -60,8 +77,7 @@ ShowList.propTypes = {
selectedImdbId: PropTypes.string,
filter: PropTypes.string,
loading: PropTypes.bool,
deleteShowFromWishlist: PropTypes.func,
addShowToWishlist: PropTypes.func,
showWishlistToggle: PropTypes.func,
selectShow: PropTypes.func,
getShowDetails: PropTypes.func,
updateFilter: PropTypes.func,

View File

@ -11,16 +11,10 @@ const handlers = {
"SHOW_FETCH_DETAILS_PENDING": state => state.set("loading", true),
"SHOW_FETCH_DETAILS_FULFILLED": (state, action) => sortEpisodes(state, action.payload.response.data),
"SHOW_UPDATE_STORE_WISHLIST": (state, action) => {
let season = action.payload.season;
let episode = action.payload.episode;
if (action.payload.wishlisted && season === null) {
season = 0;
episode = 0;
}
return state.mergeDeep(fromJS({
"show": {
"tracked_season": season,
"tracked_episode": episode,
"tracked_season": action.payload.season,
"tracked_episode": action.payload.episode,
}
}))},
"EPISODE_GET_DETAILS_PENDING": (state, action) => state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetching"], true),

View File

@ -17,3 +17,38 @@ export const prettyEpisodeName = (showName, season, episode) =>
export const inLibrary = (element) =>
element.get("polochon_url", "") !== "";
export const isWishlisted = (element) => {
const wishlisted = element.get("wishlisted", undefined)
if (wishlisted !== undefined) {
return wishlisted;
}
const trackedSeason = element.get("tracked_season", null);
const trackedEpisode = element.get("tracked_episode", null);
if ((trackedSeason !== null) && (trackedEpisode !== null)) {
return true;
}
return false
}
export const isEpisodeWishlisted = (element, trackedSeason, trackedEpisode) => {
if ((trackedSeason === null) && (trackedEpisode === null)) {
return false;
}
if ((trackedSeason === 0) && (trackedEpisode === 0)) {
return true
}
const season = element.get("season", 0);
const episode = element.get("episode", 0);
if (season < trackedSeason) {
return false
} else if (season > trackedSeason) {
return true
} else {
return (episode >= trackedEpisode)
}
}

View File

@ -60,7 +60,7 @@ div.show.dropdown.nav-item > div {
.plot {
text-align: justify;
max-height: 50%;
max-height: 65%;
overflow: auto;
}
@ -119,3 +119,14 @@ div.sweet-alert > h2 {
.player-modal {
width: 90%;
}
.title {
font-size: 1.5rem;
@include media-breakpoint-down(md) {
font-size: 1.2rem;
}
}
.wishlist-button:hover > i {
color: $primary;
}