Merge pull request #11 from JipFr/master

Progress
This commit is contained in:
James Hawkins 2021-07-15 20:36:08 +01:00 committed by GitHub
commit 3f8836aacf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 113 additions and 14 deletions

View File

@ -3,11 +3,34 @@ import { TypeSelector } from './TypeSelector';
import { NumberSelector } from './NumberSelector'; import { NumberSelector } from './NumberSelector';
import './EpisodeSelector.css' import './EpisodeSelector.css'
export function EpisodeSelector({ setSeason, setEpisode, seasons, episodes, currentSeason, currentEpisode }) { export function EpisodeSelector({ setSeason, setEpisode, seasons, season, episodes, currentSeason, currentEpisode, slug }) {
const choices = episodes.map(v => {
let progressData = JSON.parse(localStorage.getItem('video-progress') || "{}")
let currentlyAt = 0;
let totalDuration = 0;
const progress = progressData?.lookmovie?.show?.[slug][`${season}-${v}`]
if(progress) {
currentlyAt = progress.currentlyAt
totalDuration = progress.totalDuration
}
const percentage = Math.round((currentlyAt / totalDuration) * 100)
return {
value: v.toString(),
label: v,
percentage
}
})
return ( return (
<div className="episodeSelector"> <div className="episodeSelector">
<TypeSelector setType={setSeason} choices={seasons.map(v=>({ value: v.toString(), label: `Season ${v}`}))} selected={currentSeason}/><br></br> <TypeSelector setType={setSeason} choices={seasons.map(v=>({ value: v.toString(), label: `Season ${v}`}))} selected={currentSeason}/><br></br>
<NumberSelector setType={(e) => setEpisode({episode: e, season: currentSeason})} choices={episodes.map(v=>({ value: v.toString(), label: v}))} selected={currentEpisode.season === currentSeason?currentEpisode.episode:null}/> <NumberSelector setType={(e) => setEpisode({episode: e, season: currentSeason})} choices={choices} selected={currentEpisode.season === currentSeason?currentEpisode.episode:null}/>
</div> </div>
) )
} }

View File

@ -1,4 +1,5 @@
.movieRow { .movieRow {
position: relative;
display: flex; display: flex;
border-radius: 5px; border-radius: 5px;
background-color: var(--content); background-color: var(--content);
@ -8,6 +9,7 @@
cursor: pointer; cursor: pointer;
transition: transform 50ms ease-in-out; transition: transform 50ms ease-in-out;
user-select: none; user-select: none;
overflow: hidden;
} }
.movieRow p { .movieRow p {

View File

@ -1,10 +1,22 @@
import React from 'react' import React from 'react'
import { Arrow } from './Arrow' import { Arrow } from './Arrow'
import './MovieRow.css' import './MovieRow.css'
import { PercentageOverlay } from './PercentageOverlay'
// title: string // title: string
// onClick: () => void // onClick: () => void
export function MovieRow(props) { export function MovieRow(props) {
const progressData = JSON.parse(localStorage.getItem("video-progress") || "{}")
let progress;
let percentage = null;
if(props.type === "movie") {
progress = progressData?.lookmovie?.movie?.[props.slug]?.full
if(progress) {
percentage = Math.floor((progress.currentlyAt / progress.totalDuration) * 100)
}
}
return ( return (
<div className="movieRow" onClick={() => props.onClick && props.onClick()}> <div className="movieRow" onClick={() => props.onClick && props.onClick()}>
<div className="left"> <div className="left">
@ -15,6 +27,7 @@ export function MovieRow(props) {
<p>Watch {props.type}</p> <p>Watch {props.type}</p>
<Arrow/> <Arrow/>
</div> </div>
<PercentageOverlay percentage={percentage} />
</div> </div>
) )
} }

View File

@ -8,6 +8,8 @@
.numberSelector .choiceWrapper { .numberSelector .choiceWrapper {
position: relative; position: relative;
border-radius: 10%;
overflow: hidden;
} }
.numberSelector .choiceWrapper::before { .numberSelector .choiceWrapper::before {
@ -34,7 +36,6 @@
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border-radius: 10%;
box-sizing: border-box; box-sizing: border-box;
} }
@ -46,3 +47,4 @@
color: var(--choice-active-text, var(--text)); color: var(--choice-active-text, var(--text));
background-color: var(--choice-active); background-color: var(--choice-active);
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
// import { Arrow } from './Arrow'; // import { Arrow } from './Arrow';
import './NumberSelector.css' import './NumberSelector.css'
import { PercentageOverlay } from './PercentageOverlay';
// setType: (txt: string) => void // setType: (txt: string) => void
// choices: { label: string, value: string }[] // choices: { label: string, value: string }[]
@ -10,8 +11,9 @@ export function NumberSelector({ setType, choices, selected }) {
<div className="numberSelector"> <div className="numberSelector">
{choices.map(v=>( {choices.map(v=>(
<div key={v.value} className="choiceWrapper"> <div key={v.value} className="choiceWrapper">
<div className={`choice ${selected&&selected===v.value?'selected':''}`} onClick={() => setType(v.value)}> <div className={`choice ${selected&&selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
{v.label} {v.label}
<PercentageOverlay percentage={v.percentage} />
</div> </div>
</div> </div>
))} ))}

View File

@ -0,0 +1,12 @@
.progressBar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.2;
}
.progressBarInner {
background: var(--theme-color);
height: 100%;
}

View File

@ -0,0 +1,13 @@
import React from 'react'
import './PercentageOverlay.css'
export function PercentageOverlay({ percentage }) {
if(percentage && percentage > 3) percentage = Math.max(20, percentage < 90 ? percentage : 100)
return percentage > 0 ? (
<div class="progressBar">
<div class="progressBarInner" style={{width: `${percentage}%`}}></div>
</div>
) : <React.Fragment></React.Fragment>
}

View File

@ -5,6 +5,8 @@
position: relative; position: relative;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
max-width: 100%; max-width: 100%;
}
.typeSelector:not(.nowrap) {
flex-wrap: wrap; flex-wrap: wrap;
} }
@ -52,8 +54,7 @@
} }
@media screen and (max-width: 700px) { @media screen and (max-width: 700px) {
.typeSelector { .typeSelector:not(.nowrap) {
width: 80%;
display: block; display: block;
} }
} }

View File

@ -5,14 +5,14 @@ import './TypeSelector.css'
// setType: (txt: string) => void // setType: (txt: string) => void
// choices: { label: string, value: string }[] // choices: { label: string, value: string }[]
// selected: string // selected: string
export function TypeSelector({ setType, choices, selected }) { export function TypeSelector({ setType, choices, selected, noWrap = false }) {
const selectedIndex = choices.findIndex(v=>v.value===selected); const selectedIndex = choices.findIndex(v=>v.value===selected);
const transformStyles = { const transformStyles = {
opacity: selectedIndex!==-1?1:0, opacity: selectedIndex!==-1?1:0,
transform: `translateX(${selectedIndex!==-1?selectedIndex*7:0}rem)` transform: `translateX(${selectedIndex!==-1?selectedIndex*7:0}rem)`
} }
return ( return (
<div className="typeSelector"> <div className={`typeSelector ${noWrap ? 'nowrap' : ''}`}>
{choices.map(v=>( {choices.map(v=>(
<div key={v.value} className={`choice ${selected===v.value?'selected':''}`} onClick={() => setType(v.value)}> <div key={v.value} className={`choice ${selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
{v.label} {v.label}

View File

@ -1,6 +1,6 @@
.videoElement { .videoElement {
width: 100%; width: 100%;
background-color: var(--content); background-color: black;
border-radius: 5px; border-radius: 5px;
} }

View File

@ -5,7 +5,7 @@ import { VideoPlaceholder } from './VideoPlaceholder'
// streamUrl: string // streamUrl: string
// loading: boolean // loading: boolean
export function VideoElement({ streamUrl, loading }) { export function VideoElement({ streamUrl, loading, setProgress }) {
const videoRef = React.useRef(null); const videoRef = React.useRef(null);
const [error, setError] = React.useState(false); const [error, setError] = React.useState(false);
@ -37,6 +37,6 @@ export function VideoElement({ streamUrl, loading }) {
return <VideoPlaceholder>No video selected</VideoPlaceholder> return <VideoPlaceholder>No video selected</VideoPlaceholder>
return ( return (
<video className="videoElement" ref={videoRef} controls autoPlay /> <video className="videoElement" ref={videoRef} controls autoPlay onProgress={setProgress} />
) )
} }

View File

@ -54,9 +54,37 @@ export function MovieView(props) {
}) })
return () => { return () => {
cancel = true; cancel = true;
} }
}, [episode, streamData, setStreamUrl]) }, [episode, streamData, setStreamUrl])
const setProgress = (evt) => {
let ls = JSON.parse(localStorage.getItem("video-progress") || "{}")
// We're just checking lookmovie for now since there is only one scraper
if(!ls.lookmovie) ls.lookmovie = {}
if(!ls.lookmovie[streamData.type]) ls.lookmovie[streamData.type] = {}
if(!ls.lookmovie[streamData.type][streamData.slug]) {
ls.lookmovie[streamData.type][streamData.slug] = {}
}
// Store real data
let key = streamData.type === "show" ? `${season}-${episode.episode}` : "full"
ls.lookmovie[streamData.type][streamData.slug][key] = {
currentlyAt: Math.floor(evt.currentTarget.currentTime),
totalDuration: Math.floor(evt.currentTarget.duration),
updatedAt: Date.now()
}
if(streamData.type === "show") {
ls.lookmovie[streamData.type][streamData.slug][key].show = {
season,
episode: episode.episode
}
}
localStorage.setItem("video-progress", JSON.stringify(ls))
}
return ( return (
<div className={`cardView showType-${streamData.type}`}> <div className={`cardView showType-${streamData.type}`}>
<Card fullWidth> <Card fullWidth>
@ -66,13 +94,15 @@ export function MovieView(props) {
{streamData.type === "show" ? <Title size="small"> {streamData.type === "show" ? <Title size="small">
Season {episode.season}: Episode {episode.episode} Season {episode.season}: Episode {episode.episode}
</Title> : undefined} </Title> : undefined}
<VideoElement streamUrl={streamUrl} loading={loading}/> <VideoElement streamUrl={streamUrl} loading={loading} setProgress={setProgress} />
{streamData.type === "show" ? {streamData.type === "show" ?
<EpisodeSelector <EpisodeSelector
setSeason={setSeason} setSeason={setSeason}
setEpisode={setEpisode} setEpisode={setEpisode}
season={season}
seasons={seasonList} seasons={seasonList}
episodes={episodeLists} episodes={episodeLists}
slug={streamData.slug}
currentSeason={season} currentSeason={season}
currentEpisode={episode} currentEpisode={episode}
/> />

View File

@ -115,6 +115,7 @@ export function SearchView() {
{ label: "Movie", value: "movie" }, { label: "Movie", value: "movie" },
{ label: "TV Show", value: "show" } { label: "TV Show", value: "show" }
]} ]}
noWrap={true}
selected={type} selected={type}
/> />
<InputBox placeholder={ type === "movie" ? "Hamilton" : "Atypical" } onSubmit={(str) => searchMovie(str, type)} /> <InputBox placeholder={ type === "movie" ? "Hamilton" : "Atypical" } onSubmit={(str) => searchMovie(str, type)} />
@ -126,7 +127,7 @@ export function SearchView() {
Whoops, there are a few {type}s like that Whoops, there are a few {type}s like that
</Title> </Title>
{options?.map((v, i) => ( {options?.map((v, i) => (
<MovieRow key={i} title={v.title} type={v.type} year={v.year} season={v.season} episode={v.episode} onClick={() => { <MovieRow key={i} title={v.title} slug={v.slug} type={v.type} year={v.year} season={v.season} episode={v.episode} onClick={() => {
setShowingOptions(false) setShowingOptions(false)
getStream(v.title, v.slug, v.type, v.season, v.episode) getStream(v.title, v.slug, v.type, v.season, v.episode)
}}/> }}/>