406 lines
13 KiB
JavaScript

import React from "react"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
import { addTorrent } from "../../actions/torrents"
import { refreshSubtitles } from "../../actions/subtitles"
import { addShowToWishlist, deleteShowFromWishlist, getEpisodeDetails, fetchShowDetails } from "../../actions/shows"
import Loader from "../loader/loader"
import DownloadButton from "../buttons/download"
import SubtitlesButton from "../buttons/subtitles"
import ImdbButton from "../buttons/imdb"
import RefreshIndicator from "../buttons/refresh"
import { OverlayTrigger, Tooltip } from "react-bootstrap"
import { Button, Dropdown, MenuItem } from "react-bootstrap"
function mapStateToProps(state) {
return {
loading: state.showStore.loading,
show: state.showStore.get("show"),
};
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({addTorrent, addShowToWishlist, deleteShowFromWishlist,
fetchShowDetails, getEpisodeDetails,
refreshSubtitles }, dispatch)
class ShowDetails extends React.Component {
render() {
// Loading
if (this.props.loading) {
return (<Loader />);
}
return (
<div className="row" id="container">
<Header
data={this.props.show}
addToWishlist={this.props.addShowToWishlist}
deleteFromWishlist={this.props.deleteShowFromWishlist}
/>
<SeasonsList
data={this.props.show}
router={this.props.router}
addTorrent={this.props.addTorrent}
addToWishlist={this.props.addShowToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails}
refreshSubtitles={this.props.refreshSubtitles}
/>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ShowDetails);
function Header(props){
return (
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1">
<div className="panel panel-default">
<div className="panel-body">
<HeaderThumbnail data={props.data} />
<HeaderDetails
data={props.data}
addToWishlist={props.addToWishlist}
deleteFromWishlist={props.deleteFromWishlist}
/>
</div>
</div>
</div>
);
}
function HeaderThumbnail(props){
return (
<div className="col-xs-12 col-sm-2 text-center">
<img src={props.data.get("poster_url")}
className="show-thumbnail thumbnail-selected img-thumbnail img-responsive"/>
</div>
);
}
function HeaderDetails(props){
return (
<div className="col-xs-12 col-sm-10">
<dl className="dl-horizontal">
<dt>Title</dt>
<dd>{props.data.get("title")}</dd>
<dt>Plot</dt>
<dd className="plot">{props.data.get("plot")}</dd>
<dt>IMDB</dt>
<dd>
<ImdbButton imdbId={props.data.get("imdb_id")} size="xs"/>
</dd>
<dt>Year</dt>
<dd>{props.data.get("year")}</dd>
<dt>Rating</dt>
<dd>{props.data.get("rating")}</dd>
</dl>
<TrackHeader
data={props.data}
addToWishlist={props.addToWishlist}
deleteFromWishlist={props.deleteFromWishlist}
/>
</div>
);
}
function SeasonsList(props){
return (
<div>
{props.data.get("seasons").entrySeq().map(function([season, data]) {
return (
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1" key={season.toString()}>
<Season
data={data}
season={season}
showName={props.data.get("title")}
router={props.router}
addTorrent={props.addTorrent}
addToWishlist={props.addToWishlist}
getEpisodeDetails={props.getEpisodeDetails}
refreshSubtitles={props.refreshSubtitles}
/>
</div>
);
})}
</div>
)
}
class Season extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = { colapsed: true };
}
handleClick(e) {
e.preventDefault();
this.setState({ colapsed: !this.state.colapsed });
}
render() {
return (
<div className="panel panel-default">
<div className="panel-heading clickable" onClick={(e) => this.handleClick(e)}>
Season {this.props.season}
<small className="text-primary"> ({this.props.data.toList().size} episodes)</small>
<span className="pull-right">
{this.state.colapsed ||
<i className="fa fa-chevron-down"></i>
}
{this.state.colapsed &&
<i className="fa fa-chevron-left"></i>
}
</span>
</div>
{this.state.colapsed ||
<table className="table table-striped table-hover">
<tbody>
{this.props.data.toList().map(function(episode) {
let key = `${episode.get("season")}-${episode.get("episode")}`;
return (
<Episode
key={key}
data={episode}
showName={this.props.showName}
router={this.props.router}
addTorrent={this.props.addTorrent}
addToWishlist={this.props.addToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails}
refreshSubtitles={this.props.refreshSubtitles}
/>
)
}, this)}
</tbody>
</table>
}
</div>
)
}
}
function PolochonMetadata(props) {
if (!props.quality || props.quality === "") {
return null;
}
return (
<span>
<span className="badge badge-pill badge-secondary">{props.quality}</span>
<span className="badge badge-pill badge-secondary">{props.container} </span>
<span className="badge badge-pill badge-secondary">{props.videoCodec}</span>
<span className="badge badge-pill badge-secondary">{props.audioCodec}</span>
<span className="badge badge-pill badge-secondary">{props.releaseGroup}</span>
</span>
);
}
function Episode(props) {
return (
<tr>
<th scope="row" className="col-xs-2">
<TrackButton
data={props.data}
addToWishlist={props.addToWishlist}
/>
{props.data.get("episode")}
</th>
<td className="col-xs-12">
{props.data.get("title")}
<PolochonMetadata
quality={props.data.get("quality")}
releaseGroup={props.data.get("release_group")}
container={props.data.get("container")}
audioCodec={props.data.get("audio_codec")}
videoCodec={props.data.get("video_codec")}
/>
<span className="pull-right episode-buttons">
{props.data.get("polochon_url") !== "" &&
<SubtitlesButton
fetching={props.data.get("fetchingSubtitles")}
subtitles={props.data.get("subtitles")}
refreshSubtitles={props.refreshSubtitles}
resourceID={props.data.get("show_imdb_id")}
season={props.data.get("season")}
episode={props.data.get("episode")}
type="episode"
xs
/>
}
{props.data.get("torrents") && props.data.get("torrents").toList().map(function(torrent) {
let key = `${props.data.get("season")}-${props.data.get("episode")}-${torrent.get("source")}-${torrent.get("quality")}`;
return (
<Torrent
data={torrent}
key={key}
addTorrent={props.addTorrent}
/>
)
})}
<DownloadButton
url={props.data.get("polochon_url")}
subtitles={props.data.get("subtitles")}
xs
/>
<GetDetailsButton
showName={props.showName}
router={props.router}
data={props.data}
getEpisodeDetails={props.getEpisodeDetails}
/>
</span>
</td>
</tr>
)
}
class Torrent extends React.PureComponent {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e, url) {
e.preventDefault();
this.props.addTorrent(url);
}
render() {
return (
<span>
<a type="button"
className="btn btn-primary btn-xs"
onClick={(e) => this.handleClick(e, this.props.data.get("url"))}
href={this.props.data.url} >
<i className="fa fa-download"></i> {this.props.data.get("quality")}
</a>
</span>
)
}
}
class TrackHeader extends React.PureComponent {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
e.preventDefault();
const trackedSeason = this.props.data.get("tracked_season");
const trackedEpisode = this.props.data.get("tracked_episode");
const imdbId = this.props.data.get("imdb_id");
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
if (wishlisted) {
this.props.deleteFromWishlist(imdbId);
} else {
this.props.addToWishlist(imdbId);
}
}
render() {
const trackedSeason = this.props.data.get("tracked_season");
const trackedEpisode = this.props.data.get("tracked_episode");
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
let msg;
if (wishlisted) {
if (trackedSeason !== 0 && trackedEpisode !== 0) {
msg = (
<dd>Show tracked from <strong>season {trackedSeason} episode {trackedEpisode}</strong></dd>
);
} else {
msg = (
<dd>Whole show tracked</dd>
);
}
return (
<dl className="dl-horizontal">
<dt>Tracking active</dt>
{msg}
<dt></dt>
<dd>
<a className="btn btn-xs btn-danger" onClick={(e) => this.handleClick(e)}>
<i className="fa fa-bookmark"></i> Untrack the show
</a>
</dd>
</dl>
);
} else {
return (
<dl className="dl-horizontal">
<dt>Tracking inactive</dt>
<dd>
<a className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
<i className="fa fa-bookmark-o"></i> Track the whole show
</a>
</dd>
</dl>
);
}
}
}
class TrackButton extends React.PureComponent {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
e.preventDefault();
const imdbId = this.props.data.get("show_imdb_id");
const season = this.props.data.get("season");
const episode = this.props.data.get("episode");
this.props.addToWishlist(imdbId, season, episode);
}
render() {
const tooltipId = `tooltip-${this.props.data.season}-${this.props.data.episode}`;
const tooltip = (
<Tooltip id={tooltipId}>Track show from here</Tooltip>
);
return (
<OverlayTrigger placement="top" overlay={tooltip}>
<a type="button" className="btn btn-default btn-xs" onClick={(e) => this.handleClick(e)}>
<i className="fa fa-bookmark"></i>
</a>
</OverlayTrigger>
);
}
}
class GetDetailsButton extends React.PureComponent {
constructor(props) {
super(props);
this.handleFetchClick = this.handleFetchClick.bind(this);
this.handleAdvanceTorrentSearchClick = this.handleAdvanceTorrentSearchClick.bind(this);
this.state = {
imdbId: this.props.data.get("show_imdb_id"),
season: this.props.data.get("season"),
episode: this.props.data.get("episode"),
};
}
handleFetchClick() {
if (this.props.data.get("fetching")) { return }
this.props.getEpisodeDetails(this.state.imdbId, this.state.season, this.state.episode);
}
handleAdvanceTorrentSearchClick() {
const pad = (d) => (d < 10) ? "0" + d.toString() : d.toString();
const search = `${this.props.showName} S${pad(this.state.season)}E${pad(this.state.episode)}`;
const url = `/torrents/search/shows/${encodeURI(search)}`;
this.props.router.push(url);
}
render() {
const id = `${this.state.imdbId}-${this.state.season}-${this.state.episode}-refresh-dropdown`;
return (
<Dropdown id={id} dropup>
<Button className="btn-xs" bsStyle="info" onClick={this.handleFetchClick}>
<RefreshIndicator refresh={this.props.data.get("fetching")} />
</Button>
<Dropdown.Toggle className="btn-xs" bsStyle="info"/>
<Dropdown.Menu>
<MenuItem onClick={this.handleAdvanceTorrentSearchClick}>
<span>
<i className="fa fa-magnet"></i> Advanced torrent search
</span>
</MenuItem>
</Dropdown.Menu>
</Dropdown>
);
}
}