Skip to content

Commit

Permalink
feat(videodetail): add modal and trailer
Browse files Browse the repository at this point in the history
  • Loading branch information
royschut committed Jun 14, 2021
1 parent 322b1fe commit 017c6f1
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 28 deletions.
37 changes: 37 additions & 0 deletions src/components/Modal/Modal.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@use '../../styles/variables';
@use '../../styles/theme';

.modal {
position: fixed;
width: 100vw;
height: 100vh;
left: 0px;
top: 0px;
z-index: 1;
}
.backdrop {
width: inherit;
height: inherit;
background: rgba(0, 0, 0, 0.6);
}
.modalContainer {
position: absolute;
top: 0px;
left: 0px;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.main {
position: relative;
width: 80vw;
// height: 80vh;
background-color: rgba(0, 0, 0, 0.9);
}
.close {
position: absolute;
right: 30px;
top: 30px;
}
12 changes: 12 additions & 0 deletions src/components/Modal/Modal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';

import Modal from './Modal';

describe('<Modal>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<Modal />);

expect(container).toMatchSnapshot();
});
});
40 changes: 40 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { ReactFragment, useEffect } from 'react';

import IconButton from '../IconButton/IconButton';
import Close from '../../icons/Close';

import styles from './Modal.module.scss';

type Props = {
onClose: () => void;
children: ReactFragment;
};

const Modal: React.FC<Props> = ({ onClose, children }: Props) => {
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => event.keyCode === 27 && onClose();

document.body.style.overflow = 'hidden';
document.addEventListener('keydown', onKeyDown);
return () => {
document.body.style.overflow = 'scroll';
document.removeEventListener('keydown', onKeyDown);
};
}, [onClose]);

return (
<div className={styles.modal} onClick={onClose}>
<div className={styles.backdrop} />
<div className={styles.modalContainer}>
<div className={styles.main} onClick={(event) => event.stopPropagation()}>
<IconButton onClick={onClose} aria-label={'Close'} className={styles.close}>
<Close />
</IconButton>
{children}
</div>
</div>
</div>
);
};

export default Modal;
45 changes: 45 additions & 0 deletions src/components/Modal/__snapshots__/Modal.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Modal> renders and matches snapshot 1`] = `
<div>
<div
class="modal"
>
<div
class="backdrop"
/>
<div
class="modalContainer"
>
<div
class="main"
>
<div
aria-label="Close"
class="iconButton close"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
class="icon"
focusable="false"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
<path
d="M0 0h24v24H0z"
fill="none"
/>
</g>
</svg>
</div>
</div>
</div>
</div>
</div>
`;
10 changes: 10 additions & 0 deletions src/components/Video/Video.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,13 @@
.playerInfo {
margin: 0px 30px;
}
.trailerMeta {
position: absolute;
z-index: 1;
left: 16px;
top: 16px;
&.hidden {
transition: opacity 0.6s ease;
opacity: 0;
}
}
22 changes: 21 additions & 1 deletion src/components/Video/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,31 @@ import Play from '../../icons/Play';
import Button from '../Button/Button';
import IconButton from '../IconButton/IconButton';
import { formatDuration } from '../../utils/formatting';
import Modal from '../Modal/Modal';

import styles from './Video.module.scss';

type Poster = 'fading' | 'normal';

type Props = {
item: PlaylistItem;
trailerItem?: PlaylistItem;
play: boolean;
startPlay: () => void;
goBack: () => void;
poster: Poster;
enableSharing: boolean;
hasShared: boolean;
onShareClick: () => void;
playTrailer: boolean;
onTrailerClick: () => void;
onTrailerClose: () => void;
relatedShelf?: JSX.Element;
};

const Video: React.FC<Props> = ({
item,
trailerItem,
play,
startPlay,
goBack,
Expand All @@ -42,6 +48,9 @@ const Video: React.FC<Props> = ({
hasShared,
onShareClick,
relatedShelf,
playTrailer,
onTrailerClick,
onTrailerClose,
}: Props) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [mouseActive, setMouseActive] = useState(false);
Expand Down Expand Up @@ -88,7 +97,8 @@ const Video: React.FC<Props> = ({
<Button
label={t('video:trailer')}
startIcon={<PlayTrailer />}
onClick={() => null}
onClick={onTrailerClick}
active={playTrailer}
fullWidth={breakpoint < Breakpoint.sm}
/>
<Button label={t('video:favorite')} startIcon={<Favorite />} onClick={() => null} />
Expand Down Expand Up @@ -125,6 +135,16 @@ const Video: React.FC<Props> = ({
</div>
</div>
)}
{playTrailer && trailerItem && (
<Modal onClose={onTrailerClose}>
<div onMouseMove={mouseActivity} onClick={mouseActivity}>
<Cinema item={trailerItem} onComplete={onTrailerClose} isTrailer />
<div
className={classNames(styles.trailerMeta, styles.title, { [styles.hidden]: !mouseActive })}
>{`${item.title} - Trailer`}</div>
</div>
</Modal>
)}
</div>
);
};
Expand Down
26 changes: 21 additions & 5 deletions src/containers/Cinema/Cinema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ type Props = {
item: PlaylistItem;
onPlay?: () => void;
onPause?: () => void;
onComplete?: () => void;
isTrailer?: boolean;
};

const Cinema: React.FC<Props> = ({ item, onPlay, onPause }: Props) => {
const Cinema: React.FC<Props> = ({ item, onPlay, onPause, onComplete, isTrailer = false }: Props) => {
const config: Config = useContext(ConfigContext);
const [initialized, setInitialized] = useState(false);
const file = item.sources[0]?.file;
const file = item.sources?.[0]?.file;
const scriptUrl = `https://content.jwplatform.com/libraries/${config.player}.js`;
const enableWatchHistory = config.options.enableContinueWatching && !isTrailer;

const createWatchHistoryItem = (): WatchHistoryItem | undefined => {
const player = window.jwplayer && (window.jwplayer('cinema') as jwplayer.JWPlayer);
Expand All @@ -32,7 +35,7 @@ const Cinema: React.FC<Props> = ({ item, onPlay, onPause }: Props) => {
} as WatchHistoryItem;
};
const watchHistory = watchHistoryStore.useState((state) => state.watchHistory);
const updateWatchHistory = useWatchHistoryUpdater(createWatchHistoryItem);
const updateWatchHistory = useWatchHistoryUpdater(createWatchHistoryItem, enableWatchHistory);

useEffect(() => {
const getPlayer = () => window.jwplayer && (window.jwplayer('cinema') as jwplayer.JWPlayer);
Expand All @@ -43,19 +46,32 @@ const Cinema: React.FC<Props> = ({ item, onPlay, onPause }: Props) => {
const { watchHistory } = watchHistoryStore.getRawState();
const position = watchHistory.find((historyItem) => historyItem.mediaid === item.mediaid)?.position;

if (position) {
if (position && enableWatchHistory) {
setTimeout(() => player.seek(position), 1000);
}
});
player.on('play', () => onPlay && onPlay());
player.on('pause', () => onPause && onPause());
player.on('complete', () => onComplete && onComplete());
};

if (config.player && !initialized) {
getPlayer() ? loadVideo() : addScript(scriptUrl, loadVideo);
setInitialized(true);
}
}, [item, onPlay, onPause, config.player, file, scriptUrl, initialized, watchHistory, updateWatchHistory]);
}, [
item,
onPlay,
onPause,
onComplete,
config.player,
file,
scriptUrl,
initialized,
watchHistory,
enableWatchHistory,
updateWatchHistory,
]);

return <div className={styles.Cinema} id="cinema" />;
};
Expand Down
39 changes: 21 additions & 18 deletions src/containers/Video/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,42 @@ import React, { useContext, useState } from 'react';
import type { Config } from 'types/Config';
import type { PlaylistItem } from 'types/playlist';

import useMedia from '../../hooks/useMedia';
import { copyToClipboard } from '../../utils/dom';
import useBlurImageUpdater from '../../hooks/useBlurImageUpdater';
import { ConfigContext } from '../../providers/ConfigProvider';
import VideoComponent from '../../components/Video/Video';
import { cardUrl, videoUrl } from '../../utils/formatting';
import usePlaylist from '../../hooks/usePlaylist';
import Shelf from '../Shelf/Shelf';
import CardGrid from '../../components/CardGrid/CardGrid';

export type VideoType = 'movie' | 'series';

export type VideoProps = {
playlistId: string;
mediaId: string;
videoType: VideoType;
playlistId?: string;
episodeId?: string | null;
mediaId?: string | null;
};

const Video = ({ playlistId, videoType, episodeId, mediaId }: VideoProps): JSX.Element => {
const Video = ({ mediaId, playlistId, videoType, episodeId }: VideoProps): JSX.Element => {
const history = useHistory();
const location = useLocation();
const play = new URLSearchParams(location.search).get('play') === '1';
const config: Config = useContext(ConfigContext);
const [hasShared, setHasShared] = useState<boolean>(false);
const [playTrailer, setPlayTrailer] = useState<boolean>(false);
const enableSharing: boolean = config.options.enableSharing === true;
const posterFading: boolean = config.options.posterFading === true;

const { isLoading, error, data: { playlist } = { playlist: [] } } = usePlaylist(playlistId);
const { isLoading, error, data: media } = useMedia(mediaId);
const item: PlaylistItem = media?.playlist?.[0];

const updateBlurImage = useBlurImageUpdater(playlist);
const { data: trailerMedia } = useMedia(item?.trailerId || '');
const trailerItem: PlaylistItem = trailerMedia?.playlist[0];

const getMovieItem = () => playlist.find((item) => item.mediaid === mediaId);
const getSeriesItem = () => playlist.length && playlist[0];
const item = videoType === 'movie' ? getMovieItem() : getSeriesItem();
const { data: { playlist } = { playlist: [] } } = usePlaylist(config.recommendationsPlaylist || '', mediaId);
const updateBlurImage = useBlurImageUpdater(playlist);

const startPlay = () => item && history.push(videoUrl(item, playlistId, true));
const goBack = () => item && history.push(videoUrl(item, playlistId, false));
Expand All @@ -55,32 +58,32 @@ const Video = ({ playlistId, videoType, episodeId, mediaId }: VideoProps): JSX.E
setTimeout(() => setHasShared(false), 2000);
};

//temp:
console.info({ episodeId });

if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading list</p>;
if (!playlist) return <p>List not found</p>;
if (!item) return <p>Can not find medium</p>;

if (episodeId || videoType) {
//todo
}

return (
<VideoComponent
item={item}
trailerItem={trailerItem}
play={play}
startPlay={startPlay}
goBack={goBack}
poster={posterFading ? 'fading' : 'normal'}
enableSharing={enableSharing}
hasShared={hasShared}
onShareClick={onShareClick}
playTrailer={playTrailer}
onTrailerClick={() => setPlayTrailer(true)}
onTrailerClose={() => setPlayTrailer(false)}
relatedShelf={
config.recommendationsPlaylist ? (
<Shelf
playlistId={config.recommendationsPlaylist}
onCardClick={onCardClick}
onCardHover={onCardHover}
relatedMediaId={item.mediaid}
/>
<CardGrid playlist={playlist} onCardClick={onCardClick} onCardHover={onCardHover} isLoading={isLoading} />
) : undefined
}
/>
Expand Down
20 changes: 20 additions & 0 deletions src/hooks/useMedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { UseBaseQueryResult, useQuery } from 'react-query';
import type { Media } from 'types/media';

const baseUrl = 'https://content.jwplatform.com'; // temp data, till config arrives

const getMediaById = (mediaId: string, recommendations_playlist_id?: string) => {
const recommendationQuery = recommendations_playlist_id
? `?recommendations_playlist_id=${recommendations_playlist_id}`
: '';

return fetch(`${baseUrl}/v2/media/${mediaId}${recommendationQuery}`).then((res) => res.json());
};

export type UseMediaResult<TData = Media, TError = unknown> = UseBaseQueryResult<TData, TError>;

export default function useMedia(mediaId: string, recommendations_playlist_id?: string): UseMediaResult {
return useQuery(['media', mediaId], () => getMediaById(mediaId, recommendations_playlist_id), {
enabled: !!mediaId,
});
}
Loading

0 comments on commit 017c6f1

Please sign in to comment.