Skip to content

Commit

Permalink
✨ Add trailer and revamp movie detail UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Sing Ming Chen committed Nov 14, 2023
1 parent a94d632 commit 4a761ca
Show file tree
Hide file tree
Showing 10 changed files with 435 additions and 300 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -36,6 +36,7 @@
"graphql": "^0.9.3",
"hint.css": "^2.4.1",
"history": "^4.6.3",
"hls.js": "0.12.4",
"isomorphic-fetch": "^2.2.1",
"material-ui": "^0.20.0",
"memory-cache": "^0.1.6",
Expand Down
47 changes: 47 additions & 0 deletions src/app/components/HLSVideoPlayer.tsx
@@ -0,0 +1,47 @@
import * as React from 'react';
import * as Hls from 'hls.js';

class HLSVideoPlayer extends React.Component<{ streamUrl: string; poster: string }, {}> {
videoRef = null;
hls = null;

componentDidMount() {
this.initPlayer();
}

componentDidUpdate(prevProps) {
if (this.props.streamUrl !== prevProps.streamUrl) {
this.initPlayer();
}
}

componentWillUnmount() {
if (this.hls) {
this.hls.destroy();
}
}

initPlayer() {
if (Hls.isSupported()) {
const video = this.videoRef;
this.hls = new Hls();
this.hls.loadSource(this.props.streamUrl);
this.hls.attachMedia(video);
}
}

render() {
return (
<video
ref={(element) => {
this.videoRef = element;
}}
poster={this.props.poster}
controls
width={'100%'}
/>
);
}
}

export default HLSVideoPlayer;
171 changes: 108 additions & 63 deletions src/app/components/movieDetail.tsx
@@ -1,10 +1,10 @@
import * as React from 'react';
import SVGSocialShare from 'material-ui/svg-icons/social/share';
import IconButton from 'material-ui/IconButton';
import { Table, TableBody, TableRow, TableRowColumn } from 'material-ui/Table';
import Movie from '../../models/movie';
import Ratings from './ratings';
import { getMovieSchema } from '../helper';
import HLSVideoPlayer from './HLSVideoPlayer';

interface MovieDetailProps {
movie: Movie;
Expand All @@ -16,87 +16,132 @@ function getBigPosterUrl(linePosterUrl: string): string {
}
return linePosterUrl.replace('/w280', '/w644');
}
class MovieDetail extends React.PureComponent<MovieDetailProps, {}> {
constructor(props) {
class MovieDetail extends React.PureComponent<MovieDetailProps, { isExpanded: boolean }> {
constructor(props: MovieDetailProps) {
super(props);
this.state = {
isExpanded: false, // Initially, the summary is not expanded
};
}

share() {
share = () => {
navigator['share']({
title: document.title,
text: `${this.props.movie.chineseTitle} ${this.props.movie.englishTitle} | ${document['meta'].description} | MovieRater`,
url: location.href,
})
.then(() => console.log('Successful share'))
.catch((error) => console.log('Error sharing', error));
}
};

render() {
const schema = JSON.stringify(getMovieSchema(this.props.movie));
toggleSummary = () => {
this.setState((prevState) => ({
isExpanded: !prevState.isExpanded,
}));
};

renderDetail = () => {
const { isExpanded } = this.state;
const { movie } = this.props;
const schema = JSON.stringify(getMovieSchema(movie));
const trailer = getLINETrailerDetail(movie.lineTrailerHash);

return (
<div>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: schema }}></script>
<div className="col-md-8 col-xs-12 pull-right">
<Ratings className="ratings" movie={this.props.movie}>
{navigator['share'] && (
<IconButton
style={{ position: 'absolute', top: '3px', right: 0 }}
onTouchTap={(e) => {
e.preventDefault();
this.share();
}}
>
<SVGSocialShare color={'#9E9E9E'} />
</IconButton>
)}
</Ratings>
<Table className="movieDetail" selectable={false}>
<TableBody displayRowCheckbox={false}>
<TableRow>
<TableRowColumn>中文名稱</TableRowColumn>
<TableRowColumn>{this.props.movie.chineseTitle}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>英文名稱</TableRowColumn>
<TableRowColumn>{this.props.movie.englishTitle}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>上映日期</TableRowColumn>
<TableRowColumn>{this.props.movie.releaseDate}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>類型</TableRowColumn>
<TableRowColumn>{this.props.movie.types.join('、')}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>片長</TableRowColumn>
<TableRowColumn>{this.props.movie.runTime}分鐘</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>導演</TableRowColumn>
<TableRowColumn>{this.props.movie.directors.join('、')}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>演員</TableRowColumn>
<TableRowColumn>{this.props.movie.actors.join('、')}</TableRowColumn>
</TableRow>
<TableRow>
<TableRowColumn>發行公司</TableRowColumn>
<TableRowColumn>{this.props.movie.launchCompany}</TableRowColumn>
</TableRow>
</TableBody>
</Table>
<div>
<img src={getBigPosterUrl(movie.posterUrl)} className="col-xs-4 movieImage" alt="" />
<div className="col-xs-8">
<Ratings className="ratings" movie={movie}>
{navigator['share'] && (
<IconButton
style={{ position: 'absolute', top: '3px', right: 8 }}
onTouchTap={(e) => {
e.preventDefault();
this.share();
}}
>
<SVGSocialShare color={'#9E9E9E'} />
</IconButton>
)}
</Ratings>
<div className="movieDetail">
<div>
<span>中文名稱:</span>
<span>{movie.chineseTitle}</span>
</div>
<div>
<span>英文名稱:</span>
<span>{movie.englishTitle}</span>
</div>
<div>
<span>上映日期:</span>
<span>{movie.releaseDate}</span>
</div>
<div>
<span>類型:</span>
<span>{movie.types.join('、')}</span>
</div>
<div>
<span>片長:</span>
<span>{movie.runTime}分鐘</span>
</div>
<div>
<span>導演:</span>
<span>{movie.directors.join('、')}</span>
</div>
<div>
<span>演員:</span>
<span>{movie.actors.join('、')}</span>
</div>
<div className="visible-md visible-lg">
<span>劇情簡介:</span>
<span
dangerouslySetInnerHTML={{
__html:
'💥《即刻救援》票房名導皮耶莫瑞爾12億打造全新動作爽片<br/>💥《玩命關頭X》《自殺突擊隊:突擊》動作巨星約翰希南領銜主演<br/>💥《GLOW:華麗女子摔角聯盟》金球獎提名愛莉森布里共同演出<br/>💥《捍衛任務4》《不可能的任務7》團隊火爆動作鉅獻<br/>💥即刻重裝上陣, 全力殺出重圍!<br/><br/>  前特種部隊成員梅森(約翰希南 飾)為日復一日的辦公室生活感到厭煩,某天,他接到一份新工作,要在落魄的記者克萊兒(愛莉森布里 飾)前往帕多尼亞採訪統治當地的獨裁者尤安(尤安帕布羅瑞巴 飾)時擔任她的保鑣,不料,一場突如其來的軍事政變打斷了採訪,兩人必須設法逃出生天……。<br/><br/>更多資訊請見:<br/>「車庫娛樂」粉絲頁:https://www.facebook.com/garageplay.tw​<br/>「車庫娛樂」官網:https://garageplay.tw/',
}}
></span>
</div>
</div>
</div>
</div>
<div className="col-xs-12 hidden-md hidden-lg">
<span>劇情簡介:</span>
<div
className={`collapsed-summary ${isExpanded ? 'expanded-summary' : ''}`} // Add or remove the 'expanded-summary' class
dangerouslySetInnerHTML={{
__html:
'💥《即刻救援》票房名導皮耶莫瑞爾12億打造全新動作爽片<br/>💥《玩命關頭X》《自殺突擊隊:突擊》動作巨星約翰希南領銜主演<br/>💥《GLOW:華麗女子摔角聯盟》金球獎提名愛莉森布里共同演出<br/>💥《捍衛任務4》《不可能的任務7》團隊火爆動作鉅獻<br/>💥即刻重裝上陣, 全力殺出重圍!<br/><br/>  前特種部隊成員梅森(約翰希南 飾)為日復一日的辦公室生活感到厭煩,某天,他接到一份新工作,要在落魄的記者克萊兒(愛莉森布里 飾)前往帕多尼亞採訪統治當地的獨裁者尤安(尤安帕布羅瑞巴 飾)時擔任她的保鑣,不料,一場突如其來的軍事政變打斷了採訪,兩人必須設法逃出生天……。<br/><br/>更多資訊請見:<br/>「車庫娛樂」粉絲頁:https://www.facebook.com/garageplay.tw​<br/>「車庫娛樂」官網:https://garageplay.tw/',
}}
></div>
{!isExpanded && (
<button onClick={this.toggleSummary} className="readMore">
顯示更多...
</button>
)}
</div>
<img
src={getBigPosterUrl(this.props.movie.posterUrl)}
style={{ padding: 0 }}
className="col-md-4 col-xs-12"
alt=""
/>
{trailer && (
<div className="col-xs-12" style={{ marginTop: '.5em' }}>
<HLSVideoPlayer streamUrl={trailer.streamUrl} poster={trailer.poster} />
</div>
)}
</div>
);
};

render() {
return this.renderDetail();
}
}

export default MovieDetail;
function getLINETrailerDetail(lineTrailerHash: string) {
if (!lineTrailerHash) {
return null;
}
return {
streamUrl: `https://obs.line-scdn.net/${lineTrailerHash}/270p.m3u8`,
poster: `https://obs.line-scdn.net/${lineTrailerHash}/w644`,
};
}
26 changes: 5 additions & 21 deletions src/app/components/movieDetailTabs.tsx
Expand Up @@ -23,6 +23,7 @@ const movieDetailQuery = gql`
movieBaseId
lineUrlHash
lineRating
lineTrailerHash
posterUrl
chineseTitle
englishTitle
Expand Down Expand Up @@ -89,17 +90,6 @@ export default class MovieDetailTabs extends React.PureComponent<any, MovieDetai
});
};

handleSlideHeight = () => {
// const slides = document.querySelectorAll("[role='option']") as NodeListOf<HTMLDivElement>;
// Array.from(slides).forEach((slide, index) => {
// slide.style.height = index === this.state.slideIndex ? 'auto' : '500px';
// })
};

componentDidUpdate = (prevProps, prevState) => {
this.handleSlideHeight();
};

render() {
const {
data: { loading, movies },
Expand All @@ -123,21 +113,15 @@ export default class MovieDetailTabs extends React.PureComponent<any, MovieDetai
return (
<Paper zDepth={2}>
<Tabs onChange={this.handleChange.bind(this)} value={this.state.slideIndex}>
<Tab label="Detail" value={0} />
<Tab label="Ptt" value={1} />
<Tab label="Summary" value={2} />
{movie.schedules.length > 0 && <Tab label="Time" value={3} />}
<Tab label="電影資訊" value={0} />
{movie.schedules.length > 0 && <Tab label="時刻表" value={1} />}
<Tab label="Ptt" value={2} />
</Tabs>
<div className={`swipeViewWrapper active-${this.state.slideIndex}`}>
<SwipeableViews index={this.state.slideIndex} onChangeIndex={this.handleChange.bind(this)} threshold={6}>
<MovieDetail movie={movie}></MovieDetail>
<PttArticles movie={movie}></PttArticles>
<div
className="col-xs-12"
style={{ paddingTop: '1em' }}
dangerouslySetInnerHTML={{ __html: movie.summary }}
></div>
<Schedules schedules={movie.schedules}></Schedules>
<PttArticles movie={movie}></PttArticles>
</SwipeableViews>
</div>
</Paper>
Expand Down

0 comments on commit 4a761ca

Please sign in to comment.