mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
commit
3f8836aacf
13 changed files with 113 additions and 14 deletions
|
@ -3,11 +3,34 @@ import { TypeSelector } from './TypeSelector';
|
|||
import { NumberSelector } from './NumberSelector';
|
||||
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 (
|
||||
<div className="episodeSelector">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.movieRow {
|
||||
position: relative;
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
background-color: var(--content);
|
||||
|
@ -8,6 +9,7 @@
|
|||
cursor: pointer;
|
||||
transition: transform 50ms ease-in-out;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.movieRow p {
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
import React from 'react'
|
||||
import { Arrow } from './Arrow'
|
||||
import './MovieRow.css'
|
||||
import { PercentageOverlay } from './PercentageOverlay'
|
||||
|
||||
// title: string
|
||||
// onClick: () => void
|
||||
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 (
|
||||
<div className="movieRow" onClick={() => props.onClick && props.onClick()}>
|
||||
<div className="left">
|
||||
|
@ -15,6 +27,7 @@ export function MovieRow(props) {
|
|||
<p>Watch {props.type}</p>
|
||||
<Arrow/>
|
||||
</div>
|
||||
<PercentageOverlay percentage={percentage} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
.numberSelector .choiceWrapper {
|
||||
position: relative;
|
||||
border-radius: 10%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.numberSelector .choiceWrapper::before {
|
||||
|
@ -34,7 +36,6 @@
|
|||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-radius: 10%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -46,3 +47,4 @@
|
|||
color: var(--choice-active-text, var(--text));
|
||||
background-color: var(--choice-active);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
// import { Arrow } from './Arrow';
|
||||
import './NumberSelector.css'
|
||||
import { PercentageOverlay } from './PercentageOverlay';
|
||||
|
||||
// setType: (txt: string) => void
|
||||
// choices: { label: string, value: string }[]
|
||||
|
@ -10,8 +11,9 @@ export function NumberSelector({ setType, choices, selected }) {
|
|||
<div className="numberSelector">
|
||||
{choices.map(v=>(
|
||||
<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}
|
||||
<PercentageOverlay percentage={v.percentage} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
12
src/components/PercentageOverlay.css
Normal file
12
src/components/PercentageOverlay.css
Normal 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%;
|
||||
}
|
13
src/components/PercentageOverlay.js
Normal file
13
src/components/PercentageOverlay.js
Normal 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>
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
position: relative;
|
||||
margin-bottom: 1.5rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
.typeSelector:not(.nowrap) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
@ -52,8 +54,7 @@
|
|||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.typeSelector {
|
||||
width: 80%;
|
||||
.typeSelector:not(.nowrap) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ import './TypeSelector.css'
|
|||
// setType: (txt: string) => void
|
||||
// choices: { label: string, value: 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 transformStyles = {
|
||||
opacity: selectedIndex!==-1?1:0,
|
||||
transform: `translateX(${selectedIndex!==-1?selectedIndex*7:0}rem)`
|
||||
}
|
||||
return (
|
||||
<div className="typeSelector">
|
||||
<div className={`typeSelector ${noWrap ? 'nowrap' : ''}`}>
|
||||
{choices.map(v=>(
|
||||
<div key={v.value} className={`choice ${selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
|
||||
{v.label}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.videoElement {
|
||||
width: 100%;
|
||||
background-color: var(--content);
|
||||
background-color: black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { VideoPlaceholder } from './VideoPlaceholder'
|
|||
|
||||
// streamUrl: string
|
||||
// loading: boolean
|
||||
export function VideoElement({ streamUrl, loading }) {
|
||||
export function VideoElement({ streamUrl, loading, setProgress }) {
|
||||
const videoRef = React.useRef(null);
|
||||
const [error, setError] = React.useState(false);
|
||||
|
||||
|
@ -37,6 +37,6 @@ export function VideoElement({ streamUrl, loading }) {
|
|||
return <VideoPlaceholder>No video selected</VideoPlaceholder>
|
||||
|
||||
return (
|
||||
<video className="videoElement" ref={videoRef} controls autoPlay />
|
||||
<video className="videoElement" ref={videoRef} controls autoPlay onProgress={setProgress} />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,9 +54,37 @@ export function MovieView(props) {
|
|||
})
|
||||
return () => {
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
}, [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 (
|
||||
<div className={`cardView showType-${streamData.type}`}>
|
||||
<Card fullWidth>
|
||||
|
@ -66,13 +94,15 @@ export function MovieView(props) {
|
|||
{streamData.type === "show" ? <Title size="small">
|
||||
Season {episode.season}: Episode {episode.episode}
|
||||
</Title> : undefined}
|
||||
<VideoElement streamUrl={streamUrl} loading={loading}/>
|
||||
<VideoElement streamUrl={streamUrl} loading={loading} setProgress={setProgress} />
|
||||
{streamData.type === "show" ?
|
||||
<EpisodeSelector
|
||||
setSeason={setSeason}
|
||||
setEpisode={setEpisode}
|
||||
season={season}
|
||||
seasons={seasonList}
|
||||
episodes={episodeLists}
|
||||
slug={streamData.slug}
|
||||
currentSeason={season}
|
||||
currentEpisode={episode}
|
||||
/>
|
||||
|
|
|
@ -115,6 +115,7 @@ export function SearchView() {
|
|||
{ label: "Movie", value: "movie" },
|
||||
{ label: "TV Show", value: "show" }
|
||||
]}
|
||||
noWrap={true}
|
||||
selected={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
|
||||
</Title>
|
||||
{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)
|
||||
getStream(v.title, v.slug, v.type, v.season, v.episode)
|
||||
}}/>
|
||||
|
|
Loading…
Reference in a new issue