mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-25 07:11:50 +01:00
Better show support
This commit is contained in:
parent
c10807808a
commit
57e9bc2dc1
@ -20,7 +20,7 @@
|
|||||||
<title>movie-web</title>
|
<title>movie-web</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript style="color: white">You need to enable JavaScript to run this app.</noscript>
|
<noscript style="color: var(--text)">You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.card {
|
.card {
|
||||||
background-color: #22232A;
|
background-color: var(--card);
|
||||||
padding: 3rem 4rem;
|
padding: 3rem 4rem;
|
||||||
margin: 0 3rem;
|
margin: 0 3rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
0
src/components/EpisodeSelector.css
Normal file
0
src/components/EpisodeSelector.css
Normal file
13
src/components/EpisodeSelector.js
Normal file
13
src/components/EpisodeSelector.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TypeSelector } from './TypeSelector';
|
||||||
|
import { NumberSelector } from './NumberSelector';
|
||||||
|
import './EpisodeSelector.css'
|
||||||
|
|
||||||
|
export function EpisodeSelector({ setSeason, setEpisode, seasons, episodes, currentSeason, currentEpisode }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<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}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -19,8 +19,8 @@
|
|||||||
.inputTextBox {
|
.inputTextBox {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #36363e;
|
background-color: var(--content);
|
||||||
color: white;
|
color: var(--text);
|
||||||
padding: .7rem 1.5rem;
|
padding: .7rem 1.5rem;
|
||||||
height: auto;
|
height: auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -28,52 +28,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inputSearchButton {
|
.inputSearchButton {
|
||||||
background-color: #A73B83;
|
background-color: var(--button);
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
color: white;
|
color: var(--text);
|
||||||
padding: .5rem 2.1rem;
|
padding: .5rem 2.1rem;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputDropdown {
|
|
||||||
border-width: 0;
|
|
||||||
outline: none;
|
|
||||||
background-color: #36363e;
|
|
||||||
color: white;
|
|
||||||
padding: .7rem 1rem;
|
|
||||||
height: auto;
|
|
||||||
width: 25%;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputOptionBox {
|
|
||||||
border-width: 0;
|
|
||||||
outline: none;
|
|
||||||
background-color: #36363e;
|
|
||||||
color: white;
|
|
||||||
height: auto;
|
|
||||||
width: 10%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputDropdown:hover {
|
|
||||||
background-color: #3C3D44;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputSearchButton:hover {
|
.inputSearchButton:hover {
|
||||||
background-color: #9C3179;
|
background-color: var(--button-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputTextBox:hover {
|
.inputTextBox:hover {
|
||||||
background-color: #3C3D44;
|
background-color: var(--content-hover);
|
||||||
}
|
|
||||||
|
|
||||||
.inputOptionBox:hover {
|
|
||||||
background-color: #3C3D44;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputSearchButton .text > .arrow {
|
.inputSearchButton .text > .arrow {
|
||||||
@ -83,11 +52,13 @@
|
|||||||
right: -0.8rem;
|
right: -0.8rem;
|
||||||
bottom: -0.2rem;
|
bottom: -0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputSearchButton .text {
|
.inputSearchButton .text {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputSearchButton:hover .text > .arrow {
|
.inputSearchButton:hover .text > .arrow {
|
||||||
transform: translateX(8px);
|
transform: translateX(8px);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -98,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inputSearchButton:active {
|
.inputSearchButton:active {
|
||||||
background-color: #8b286a;
|
background-color: var(--button-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
@media screen and (max-width: 700px) {
|
||||||
@ -121,17 +92,4 @@
|
|||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputDropdown {
|
|
||||||
width: 100%;
|
|
||||||
padding: .7rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputOptionBox {
|
|
||||||
margin-top: .5rem;
|
|
||||||
width: 50%;
|
|
||||||
/* align-items:stretch; */
|
|
||||||
align-self: center;
|
|
||||||
padding: .7rem 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,22 +5,13 @@ import './InputBox.css'
|
|||||||
// props = { onSubmit: (str) => {}, placeholder: string}
|
// props = { onSubmit: (str) => {}, placeholder: string}
|
||||||
export function InputBox({ onSubmit, placeholder }) {
|
export function InputBox({ onSubmit, placeholder }) {
|
||||||
const [searchTerm, setSearchTerm] = React.useState("");
|
const [searchTerm, setSearchTerm] = React.useState("");
|
||||||
const [type, setType] = React.useState("movie");
|
|
||||||
const [season, setSeason] = React.useState("");
|
|
||||||
const [episode, setEpisode] = React.useState("");
|
|
||||||
|
|
||||||
const showContentType = type === "show" ? false : true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="inputBar" onSubmit={(e) => {
|
<form className="inputBar" onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSubmit(searchTerm, type, season, episode)
|
onSubmit(searchTerm)
|
||||||
return false;
|
return false;
|
||||||
}}>
|
}}>
|
||||||
<select name="type" id="type" className="inputDropdown" onChange={(e) => setType(e.target.value)} required>
|
|
||||||
<option value="movie">Movie</option>
|
|
||||||
<option value="show">TV Show</option>
|
|
||||||
</select>
|
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
className="inputTextBox"
|
className="inputTextBox"
|
||||||
@ -30,26 +21,9 @@ export function InputBox({ onSubmit, placeholder }) {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<input
|
<button className="inputSearchButton">
|
||||||
type='text'
|
<span className="text">Search<span className="arrow"><Arrow /></span></span>
|
||||||
className='inputOptionBox'
|
</button>
|
||||||
id='inputOptionBoxSeason'
|
|
||||||
placeholder='Season'
|
|
||||||
value={season}
|
|
||||||
onChange={(e) => setSeason(e.target.value)}
|
|
||||||
hidden={showContentType}
|
|
||||||
required={!showContentType}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='inputOptionBox'
|
|
||||||
id='inputOptionBoxEpisode'
|
|
||||||
placeholder='Episode'
|
|
||||||
value={episode}
|
|
||||||
onChange={(e) => setEpisode(e.target.value)}
|
|
||||||
hidden={showContentType}
|
|
||||||
required={!showContentType} />
|
|
||||||
<button className="inputSearchButton"><span className="text">Search<span className="arrow"><Arrow /></span></span></button>
|
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
.movieRow {
|
.movieRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: #35363D;
|
background-color: var(--content);
|
||||||
color: white;
|
color: var(--text);
|
||||||
padding: .8rem 1.5rem;
|
padding: .8rem 1.5rem;
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -23,11 +23,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movieRow .left .year {
|
.movieRow .left .year {
|
||||||
color: #BCBECB;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieRow .watch {
|
.movieRow .watch {
|
||||||
color: #D678B7;
|
color: var(--theme-color-text);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.movieRow:hover {
|
.movieRow:hover {
|
||||||
background-color: #3A3B40;
|
background-color: var(--content-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieRow:hover .watch .arrow {
|
.movieRow:hover .watch .arrow {
|
||||||
@ -51,8 +51,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attribute {
|
.attribute {
|
||||||
color: white;
|
color: var(--text);
|
||||||
background-color: #D678B7;
|
background-color: var(--theme-color);
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
padding: .25rem;
|
padding: .25rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
@ -12,7 +12,6 @@ export function MovieRow(props) {
|
|||||||
<span className="year">({props.year})</span>
|
<span className="year">({props.year})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="watch">
|
<div className="watch">
|
||||||
<span className='attribute' hidden={props.type === 'show' ? false : true }>{props.season}x{props.episode}</span>
|
|
||||||
<p>Watch {props.type}</p>
|
<p>Watch {props.type}</p>
|
||||||
<Arrow/>
|
<Arrow/>
|
||||||
</div>
|
</div>
|
||||||
|
48
src/components/NumberSelector.css
Normal file
48
src/components/NumberSelector.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.numberSelector {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
|
||||||
|
gap: 5px;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberSelector .choiceWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberSelector .choiceWrapper::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberSelector .choice {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--choice);
|
||||||
|
margin-right: 5px;
|
||||||
|
padding: .2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 10%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberSelector .choice:hover {
|
||||||
|
background-color: var(--choice-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberSelector .choice.selected {
|
||||||
|
color: var(--text);
|
||||||
|
background-color: var(--choice-hover);
|
||||||
|
}
|
20
src/components/NumberSelector.js
Normal file
20
src/components/NumberSelector.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// import { Arrow } from './Arrow';
|
||||||
|
import './NumberSelector.css'
|
||||||
|
|
||||||
|
// setType: (txt: string) => void
|
||||||
|
// choices: { label: string, value: string }[]
|
||||||
|
// selected: string
|
||||||
|
export function NumberSelector({ setType, choices, selected }) {
|
||||||
|
return (
|
||||||
|
<div className="numberSelector">
|
||||||
|
{choices.map(v=>(
|
||||||
|
<div key={v.value} className="choiceWrapper">
|
||||||
|
<div className={`choice ${selected&&selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
|
||||||
|
{v.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
.progress {
|
.progress {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #BCBECB;
|
color: var(--text-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -32,12 +32,12 @@
|
|||||||
|
|
||||||
.progress .bar .bar-inner {
|
.progress .bar .bar-inner {
|
||||||
transition: width 400ms ease-in-out, background-color 100ms ease-in-out;
|
transition: width 400ms ease-in-out, background-color 100ms ease-in-out;
|
||||||
background-color: #D463AE;
|
background-color: var(--theme-color);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress.failed .bar .bar-inner {
|
.progress.failed .bar .bar-inner {
|
||||||
background-color: #d85b66;
|
background-color: var(--failed);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.title {
|
.title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: white;
|
color: var(--text);
|
||||||
/* max-width: 20rem; */
|
/* max-width: 20rem; */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -10,9 +10,13 @@
|
|||||||
.title-size-medium {
|
.title-size-medium {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
.title-size-small {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #afb1b8;
|
||||||
|
}
|
||||||
|
|
||||||
.title-accent {
|
.title-accent {
|
||||||
color: #E880C5;
|
color: var(--theme-color);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -34,3 +38,4 @@
|
|||||||
.title-accent.title-accent-link:hover .arrow {
|
.title-accent.title-accent-link:hover .arrow {
|
||||||
transform: translateY(.1rem) translateX(-.5rem);
|
transform: translateY(.1rem) translateX(-.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
59
src/components/TypeSelector.css
Normal file
59
src/components/TypeSelector.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
/* TODO better responsiveness, use dropdown if more than 5 options */
|
||||||
|
.typeSelector {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
max-width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelector::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #3a3c46;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelector .choice {
|
||||||
|
width: 7rem;
|
||||||
|
height: 3rem;
|
||||||
|
padding: .3rem .2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #585A67;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelector .choice:hover {
|
||||||
|
color: #afb1b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelector .choice.selected {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelector .selectedBar {
|
||||||
|
position: absolute;
|
||||||
|
height: 4px;
|
||||||
|
width: 7rem;
|
||||||
|
background-color: var(--theme-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
bottom: 0;
|
||||||
|
transition: transform 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.typeSelector {
|
||||||
|
width: 80%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
24
src/components/TypeSelector.js
Normal file
24
src/components/TypeSelector.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// import { Arrow } from './Arrow';
|
||||||
|
import './TypeSelector.css'
|
||||||
|
|
||||||
|
// setType: (txt: string) => void
|
||||||
|
// choices: { label: string, value: string }[]
|
||||||
|
// selected: string
|
||||||
|
export function TypeSelector({ setType, choices, selected }) {
|
||||||
|
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">
|
||||||
|
{choices.map(v=>(
|
||||||
|
<div key={v.value} className={`choice ${selected===v.value?'selected':''}`} onClick={() => setType(v.value)}>
|
||||||
|
{v.label}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="selectedBar" style={transformStyles}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -5,6 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.videoElementText {
|
.videoElementText {
|
||||||
color: white;
|
color: var(--text);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@ import Hls from 'hls.js'
|
|||||||
import './VideoElement.css'
|
import './VideoElement.css'
|
||||||
|
|
||||||
// streamUrl: string
|
// streamUrl: string
|
||||||
export function VideoElement({ streamUrl }) {
|
// loading: boolean
|
||||||
|
export function VideoElement({ streamUrl, loading }) {
|
||||||
const videoRef = React.useRef(null);
|
const videoRef = React.useRef(null);
|
||||||
const [error, setError] = React.useState(false);
|
const [error, setError] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setError(false)
|
setError(false)
|
||||||
if (!videoRef || !videoRef.current) return;
|
if (!videoRef || !videoRef.current || !streamUrl || streamUrl.length === 0 || loading) return;
|
||||||
|
|
||||||
const hls = new Hls();
|
const hls = new Hls();
|
||||||
|
|
||||||
@ -23,11 +24,19 @@ export function VideoElement({ streamUrl }) {
|
|||||||
|
|
||||||
hls.attachMedia(videoRef.current);
|
hls.attachMedia(videoRef.current);
|
||||||
hls.loadSource(streamUrl);
|
hls.loadSource(streamUrl);
|
||||||
}, [videoRef, streamUrl])
|
}, [videoRef, streamUrl, loading])
|
||||||
|
|
||||||
|
// TODO make better loading/error/empty state
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (<p className="videoElementText">Your browser is not supported</p>)
|
return (<p className="videoElementText">Your browser is not supported</p>)
|
||||||
|
|
||||||
|
if (loading)
|
||||||
|
return <p className="videoElementText">Loading episode</p>
|
||||||
|
|
||||||
|
if (!streamUrl || streamUrl.length === 0)
|
||||||
|
return <p className="videoElementText">No video selected</p>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video className="videoElement" ref={videoRef} controls autoPlay />
|
<video className="videoElement" ref={videoRef} controls autoPlay />
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ const MovieContext = React.createContext(null)
|
|||||||
export function MovieProvider(props) {
|
export function MovieProvider(props) {
|
||||||
const [page, setPage] = React.useState("search");
|
const [page, setPage] = React.useState("search");
|
||||||
const [stream, setStream] = React.useState("");
|
const [stream, setStream] = React.useState("");
|
||||||
const [streamData, setStreamData] = React.useState({ title: "", type: "", episode: "", season: "" })
|
const [streamData, setStreamData] = React.useState({ title: "", slug: "", type: "", episodes: [], seasons: [] })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieContext.Provider value={{
|
<MovieContext.Provider value={{
|
||||||
|
@ -1,6 +1,33 @@
|
|||||||
|
:root {
|
||||||
|
|
||||||
|
/* TODO finish theming for entire css */
|
||||||
|
--theme-color: #E880C5;
|
||||||
|
--theme-color-text: var(--theme-color);
|
||||||
|
|
||||||
|
--failed: #d85b66;
|
||||||
|
|
||||||
|
--body: #16171D;
|
||||||
|
--card: #22232A;
|
||||||
|
|
||||||
|
--text: white;
|
||||||
|
--text-secondary: #BCBECB;
|
||||||
|
|
||||||
|
--content: #36363e;
|
||||||
|
--content-hover: #3C3D44;
|
||||||
|
|
||||||
|
--button: #A73B83;
|
||||||
|
--button-hover: #9C3179;
|
||||||
|
--button-active: #8b286a;
|
||||||
|
|
||||||
|
--choice: #2E2F37;
|
||||||
|
--choice-hover: #45464D;
|
||||||
|
--choice-active: #45464D;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
body, html {
|
body, html {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #16171D;
|
background-color: var(--body);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,22 @@ async function getAccessToken(config) {
|
|||||||
return "Invalid type provided in config";
|
return "Invalid type provided in config";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getEpisodes(slug) {
|
||||||
|
const url = getCorsUrl(`https://lookmovie.io/shows/view/${slug}`);
|
||||||
|
const pageReq = await fetch(url).then((d) => d.text());
|
||||||
|
|
||||||
|
const data = JSON5.parse("{" +
|
||||||
|
pageReq
|
||||||
|
.slice(pageReq.indexOf(`show_storage`))
|
||||||
|
.split("};")[0]
|
||||||
|
.split("= {")[1]
|
||||||
|
.trim() +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.seasons
|
||||||
|
}
|
||||||
|
|
||||||
async function getStreamUrl(slug, type, season, episode) {
|
async function getStreamUrl(slug, type, season, episode) {
|
||||||
const url = getCorsUrl(`https://lookmovie.io/${type}s/view/${slug}`);
|
const url = getCorsUrl(`https://lookmovie.io/${type}s/view/${slug}`);
|
||||||
const pageReq = await fetch(url).then((d) => d.text());
|
const pageReq = await fetch(url).then((d) => d.text());
|
||||||
@ -92,9 +108,22 @@ async function getStreamUrl(slug, type, season, episode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function findContent(searchTerm, type) {
|
async function findContent(searchTerm, type) {
|
||||||
const searchUrl = getCorsUrl(`https://lookmovie.io/api/v1/${type}s/search/?q=${encodeURIComponent(searchTerm)}`);
|
// const searchUrl = getCorsUrl(`https://lookmovie.io/api/v1/${type}s/search/?q=${encodeURIComponent(searchTerm)}`);
|
||||||
const searchRes = await fetch(searchUrl).then((d) => d.json());
|
const searchUrl = getCorsUrl(`https://lookmovie.io/${type}s/search/?q=${encodeURIComponent(searchTerm)}`);
|
||||||
const results = [...searchRes.result.map((v) => ({ ...v, type: type}))];
|
const searchRes = await fetch(searchUrl).then((d) => d.text());
|
||||||
|
|
||||||
|
// Parse DOM to find search results on full search page
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(searchRes, "text/html");
|
||||||
|
const nodes = Array.from(doc.querySelectorAll('.movie-item-style-1'));
|
||||||
|
const results = nodes.map(node => {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
title: node.querySelector('h6 a').innerText,
|
||||||
|
year: node.querySelector('.year').innerText,
|
||||||
|
slug: node.querySelector('a').href.split('/').pop(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fuse = new Fuse(results, { threshold: 0.3, distance: 200, keys: ["title"] });
|
const fuse = new Fuse(results, { threshold: 0.3, distance: 200, keys: ["title"] });
|
||||||
const matchedResults = fuse
|
const matchedResults = fuse
|
||||||
@ -125,4 +154,4 @@ async function findContent(searchTerm, type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { findContent, getStreamUrl };
|
export { findContent, getStreamUrl, getEpisodes };
|
@ -1,72 +0,0 @@
|
|||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
|
|
||||||
// This service worker can be customized!
|
|
||||||
// See https://developers.google.com/web/tools/workbox/modules
|
|
||||||
// for the list of available Workbox modules, or add any other
|
|
||||||
// code you'd like.
|
|
||||||
// You can also remove this file if you'd prefer not to use a
|
|
||||||
// service worker, and the Workbox build step will be skipped.
|
|
||||||
|
|
||||||
import { clientsClaim } from 'workbox-core';
|
|
||||||
import { ExpirationPlugin } from 'workbox-expiration';
|
|
||||||
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
|
|
||||||
import { registerRoute } from 'workbox-routing';
|
|
||||||
import { StaleWhileRevalidate } from 'workbox-strategies';
|
|
||||||
|
|
||||||
clientsClaim();
|
|
||||||
|
|
||||||
// Precache all of the assets generated by your build process.
|
|
||||||
// Their URLs are injected into the manifest variable below.
|
|
||||||
// This variable must be present somewhere in your service worker file,
|
|
||||||
// even if you decide not to use precaching. See https://cra.link/PWA
|
|
||||||
precacheAndRoute(self.__WB_MANIFEST);
|
|
||||||
|
|
||||||
// Set up App Shell-style routing, so that all navigation requests
|
|
||||||
// are fulfilled with your index.html shell. Learn more at
|
|
||||||
// https://developers.google.com/web/fundamentals/architecture/app-shell
|
|
||||||
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
|
|
||||||
registerRoute(
|
|
||||||
// Return false to exempt requests from being fulfilled by index.html.
|
|
||||||
({ request, url }) => {
|
|
||||||
// If this isn't a navigation, skip.
|
|
||||||
if (request.mode !== 'navigate') {
|
|
||||||
return false;
|
|
||||||
} // If this is a URL that starts with /_, skip.
|
|
||||||
|
|
||||||
if (url.pathname.startsWith('/_')) {
|
|
||||||
return false;
|
|
||||||
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
|
|
||||||
|
|
||||||
if (url.pathname.match(fileExtensionRegexp)) {
|
|
||||||
return false;
|
|
||||||
} // Return true to signal that we want to use the handler.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
|
|
||||||
);
|
|
||||||
|
|
||||||
// An example runtime caching route for requests that aren't handled by the
|
|
||||||
// precache, in this case same-origin .png requests like those from in public/
|
|
||||||
registerRoute(
|
|
||||||
// Add in any other file extensions or routing criteria as needed.
|
|
||||||
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
|
|
||||||
new StaleWhileRevalidate({
|
|
||||||
cacheName: 'images',
|
|
||||||
plugins: [
|
|
||||||
// Ensure that once this runtime cache reaches a maximum size the
|
|
||||||
// least-recently used images are removed.
|
|
||||||
new ExpirationPlugin({ maxEntries: 50 }),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// This allows the web app to trigger skipWaiting via
|
|
||||||
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
|
|
||||||
self.addEventListener('message', (event) => {
|
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
||||||
self.skipWaiting();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Any other custom service worker logic can go here.
|
|
@ -0,0 +1,3 @@
|
|||||||
|
.showType-show .title-size-big {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
@ -3,17 +3,80 @@ import { Title } from '../components/Title'
|
|||||||
import { Card } from '../components/Card'
|
import { Card } from '../components/Card'
|
||||||
import { useMovie } from '../hooks/useMovie'
|
import { useMovie } from '../hooks/useMovie'
|
||||||
import { VideoElement } from '../components/VideoElement'
|
import { VideoElement } from '../components/VideoElement'
|
||||||
|
import { EpisodeSelector } from '../components/EpisodeSelector'
|
||||||
|
import './Movie.css'
|
||||||
|
import { getStreamUrl } from '../lib/lookMovie'
|
||||||
|
|
||||||
export function MovieView(props) {
|
export function MovieView(props) {
|
||||||
const { streamUrl, streamData } = useMovie();
|
const { streamUrl, streamData, setStreamUrl } = useMovie();
|
||||||
|
const [season, setSeason] = React.useState("1");
|
||||||
|
const [seasonList, setSeasonList] = React.useState([]);
|
||||||
|
const [episodeLists, setEpisodeList] = React.useState([]);
|
||||||
|
const [episode, setEpisode] = React.useState({ episode: null, season: null });
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setEpisodeList(streamData.episodes[season]);
|
||||||
|
}, [season, streamData.episodes])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (streamData.type === "show") {
|
||||||
|
setSeasonList(streamData.seasons);
|
||||||
|
setSeason(streamData.seasons[0])
|
||||||
|
// TODO load from localstorage last watched
|
||||||
|
setEpisode({ episode: streamData.episodes[streamData.seasons[0]][0], season: streamData.seasons[0] })
|
||||||
|
setEpisodeList(streamData.episodes[streamData.seasons[0]]);
|
||||||
|
}
|
||||||
|
}, [streamData])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let cancel = false;
|
||||||
|
// ignore if not a show
|
||||||
|
if (streamData.type !== "show") return () => {
|
||||||
|
cancel = true;
|
||||||
|
};
|
||||||
|
if (!episode.episode) {
|
||||||
|
setLoading(false);
|
||||||
|
setStreamUrl('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
getStreamUrl(streamData.slug, streamData.type, episode.season, episode.episode)
|
||||||
|
.then(({url}) => {
|
||||||
|
if (cancel) return;
|
||||||
|
setStreamUrl(url)
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
if (cancel) return;
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
}, [episode, streamData, setStreamUrl])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cardView">
|
<div className={`cardView showType-${streamData.type}`}>
|
||||||
<Card fullWidth>
|
<Card fullWidth>
|
||||||
<Title accent="Return to home" accentLink="search">
|
<Title accent="Return to home" accentLink="search">
|
||||||
{streamData.title} {streamData.type === "show" ? `(${streamData.season}x${streamData.episode})` : '' }
|
{streamData.title}
|
||||||
</Title>
|
</Title>
|
||||||
<VideoElement streamUrl={streamUrl}/>
|
{streamData.type === "show" ? <Title size="small">
|
||||||
|
Season {episode.season}: Episode {episode.episode}
|
||||||
|
</Title> : undefined}
|
||||||
|
<VideoElement streamUrl={streamUrl} loading={loading}/>
|
||||||
|
{streamData.type === "show" ?
|
||||||
|
<EpisodeSelector
|
||||||
|
setSeason={setSeason}
|
||||||
|
setEpisode={setEpisode}
|
||||||
|
seasons={seasonList}
|
||||||
|
episodes={episodeLists}
|
||||||
|
currentSeason={season}
|
||||||
|
currentEpisode={episode}
|
||||||
|
/>
|
||||||
|
: ''}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topRightCredits a, .topRightCredits a:visited {
|
.topRightCredits a, .topRightCredits a:visited {
|
||||||
color: #E880C5;
|
color: var(--theme-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,21 @@ import { Arrow } from '../components/Arrow'
|
|||||||
import { Progress } from '../components/Progress'
|
import { Progress } from '../components/Progress'
|
||||||
import { findContent, getStreamUrl } from '../lib/lookMovie'
|
import { findContent, getStreamUrl } from '../lib/lookMovie'
|
||||||
import { useMovie } from '../hooks/useMovie';
|
import { useMovie } from '../hooks/useMovie';
|
||||||
|
import { TypeSelector } from '../components/TypeSelector'
|
||||||
|
import { getEpisodes } from '../lib/lookMovie'
|
||||||
|
|
||||||
import './Search.css'
|
import './Search.css'
|
||||||
|
|
||||||
export function SearchView() {
|
export function SearchView() {
|
||||||
const { navigate, setStreamUrl, setStreamData } = useMovie();
|
const { navigate, setStreamUrl, setStreamData } = useMovie();
|
||||||
|
|
||||||
const maxSteps = 3;
|
const maxSteps = 4;
|
||||||
const [options, setOptions] = React.useState([]);
|
const [options, setOptions] = React.useState([]);
|
||||||
const [progress, setProgress] = React.useState(0);
|
const [progress, setProgress] = React.useState(0);
|
||||||
const [text, setText] = React.useState("");
|
const [text, setText] = React.useState("");
|
||||||
const [failed, setFailed] = React.useState(false);
|
const [failed, setFailed] = React.useState(false);
|
||||||
const [showingOptions, setShowingOptions] = React.useState(false);
|
const [showingOptions, setShowingOptions] = React.useState(false);
|
||||||
|
const [type, setType] = React.useState("movie");
|
||||||
|
|
||||||
const fail = (str) => {
|
const fail = (str) => {
|
||||||
setProgress(maxSteps);
|
setProgress(maxSteps);
|
||||||
@ -26,24 +29,46 @@ export function SearchView() {
|
|||||||
setFailed(true)
|
setFailed(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getStream(title, slug, type, season, episode) {
|
async function getStream(title, slug, type) {
|
||||||
setStreamUrl("");
|
setStreamUrl("");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setProgress(2);
|
setProgress(2);
|
||||||
setText(`Getting stream for "${title}"`)
|
setText(`Getting stream for "${title}"`)
|
||||||
const { url } = await getStreamUrl(slug, type, season, episode);
|
|
||||||
|
|
||||||
if (url === '') {
|
let seasons = [];
|
||||||
return fail(`Not found: ${title} (${season}x${episode})`)
|
let episodes = [];
|
||||||
|
if (type === "show") {
|
||||||
|
const episodeData = await getEpisodes(slug);
|
||||||
|
episodeData.forEach((e) => {
|
||||||
|
if (!seasons.includes(e.season))
|
||||||
|
seasons.push(e.season);
|
||||||
|
|
||||||
|
if (!episodes[e.season])
|
||||||
|
episodes[e.season] = []
|
||||||
|
episodes[e.season].push(e.episode)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let realUrl = '';
|
||||||
|
if (type === "movie") {
|
||||||
|
const { url } = await getStreamUrl(slug, type);
|
||||||
|
|
||||||
|
if (url === '') {
|
||||||
|
return fail(`Not found: ${title}`)
|
||||||
|
}
|
||||||
|
realUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProgress(maxSteps);
|
setProgress(maxSteps);
|
||||||
setStreamUrl(url);
|
setStreamUrl(realUrl);
|
||||||
setStreamData({
|
setStreamData({
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
season,
|
seasons,
|
||||||
episode
|
episodes,
|
||||||
|
slug
|
||||||
})
|
})
|
||||||
setText(`Streaming...`)
|
setText(`Streaming...`)
|
||||||
navigate("movie")
|
navigate("movie")
|
||||||
@ -52,9 +77,9 @@ export function SearchView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchMovie(query, contentType, season, episode) {
|
async function searchMovie(query, contentType) {
|
||||||
setFailed(false);
|
setFailed(false);
|
||||||
setText(`Searching for ${contentType} "${query}" ${contentType === 'show' ? ` (${season}x${episode})` : ''}`);
|
setText(`Searching for ${contentType} "${query}"`);
|
||||||
setProgress(1)
|
setProgress(1)
|
||||||
setShowingOptions(false)
|
setShowingOptions(false)
|
||||||
|
|
||||||
@ -64,11 +89,6 @@ export function SearchView() {
|
|||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
return fail(`Could not find that ${contentType}`)
|
return fail(`Could not find that ${contentType}`)
|
||||||
} else if (options.length > 1) {
|
} else if (options.length > 1) {
|
||||||
options.forEach((o) => {
|
|
||||||
o.season = season;
|
|
||||||
o.episode = episode;
|
|
||||||
});
|
|
||||||
|
|
||||||
setProgress(2);
|
setProgress(2);
|
||||||
setText(`Choose your ${contentType}`);
|
setText(`Choose your ${contentType}`);
|
||||||
setOptions(options);
|
setOptions(options);
|
||||||
@ -77,7 +97,7 @@ export function SearchView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { title, slug, type } = options[0];
|
const { title, slug, type } = options[0];
|
||||||
getStream(title, slug, type, season, episode);
|
getStream(title, slug, type);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fail(`Failed to watch ${contentType}`)
|
fail(`Failed to watch ${contentType}`)
|
||||||
}
|
}
|
||||||
@ -89,13 +109,21 @@ export function SearchView() {
|
|||||||
<Title accent="Because watching content legally is boring">
|
<Title accent="Because watching content legally is boring">
|
||||||
What do you wanna watch?
|
What do you wanna watch?
|
||||||
</Title>
|
</Title>
|
||||||
<InputBox placeholder="Hamilton" onSubmit={(str, type, season, episode) => searchMovie(str, type, season, episode)} />
|
<TypeSelector
|
||||||
|
setType={(type) => setType(type)}
|
||||||
|
choices={[
|
||||||
|
{ label: "Movie", value: "movie" },
|
||||||
|
{ label: "TV Show", value: "show" }
|
||||||
|
]}
|
||||||
|
selected={type}
|
||||||
|
/>
|
||||||
|
<InputBox placeholder="Hamilton" onSubmit={(str) => searchMovie(str, type)} />
|
||||||
<Progress show={progress > 0} failed={failed} progress={progress} steps={maxSteps} text={text} />
|
<Progress show={progress > 0} failed={failed} progress={progress} steps={maxSteps} text={text} />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card show={showingOptions} doTransition>
|
<Card show={showingOptions} doTransition>
|
||||||
<Title size="medium">
|
<Title size="medium">
|
||||||
Whoops, there are a few movies 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} type={v.type} year={v.year} season={v.season} episode={v.episode} onClick={() => {
|
||||||
|
109
yarn.lock
109
yarn.lock
@ -1437,6 +1437,59 @@
|
|||||||
schema-utils "^2.6.5"
|
schema-utils "^2.6.5"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
|
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||||
|
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
|
||||||
|
|
||||||
|
"@protobufjs/base64@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
|
||||||
|
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
|
||||||
|
|
||||||
|
"@protobufjs/codegen@^2.0.4":
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
|
||||||
|
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
|
||||||
|
|
||||||
|
"@protobufjs/eventemitter@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||||
|
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
|
||||||
|
|
||||||
|
"@protobufjs/fetch@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||||
|
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise" "^1.1.1"
|
||||||
|
"@protobufjs/inquire" "^1.1.0"
|
||||||
|
|
||||||
|
"@protobufjs/float@^1.0.2":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||||
|
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
|
||||||
|
|
||||||
|
"@protobufjs/inquire@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||||
|
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
|
||||||
|
|
||||||
|
"@protobufjs/path@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||||
|
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
|
||||||
|
|
||||||
|
"@protobufjs/pool@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||||
|
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
|
||||||
|
|
||||||
|
"@protobufjs/utf8@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||||
|
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve@^7.1.1":
|
"@rollup/plugin-node-resolve@^7.1.1":
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
|
||||||
@ -1752,6 +1805,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||||
|
|
||||||
|
"@types/long@^4.0.1":
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||||
|
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
@ -1762,6 +1820,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
|
||||||
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
|
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
|
||||||
|
|
||||||
|
"@types/node@>=13.7.0":
|
||||||
|
version "16.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.3.2.tgz#655432817f83b51ac869c2d51dd8305fb8342e16"
|
||||||
|
integrity sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||||
@ -3093,6 +3156,22 @@ caseless@~0.12.0:
|
|||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||||
|
|
||||||
|
castv2-client@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/castv2-client/-/castv2-client-1.2.0.tgz#a9193b1a5448b8cb9a0415bd021c8811ed7b0544"
|
||||||
|
integrity sha1-qRk7GlRIuMuaBBW9AhyIEe17BUQ=
|
||||||
|
dependencies:
|
||||||
|
castv2 "~0.1.4"
|
||||||
|
debug "^2.2.0"
|
||||||
|
|
||||||
|
castv2@~0.1.4:
|
||||||
|
version "0.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/castv2/-/castv2-0.1.10.tgz#d3df00124f1ba8a97691c69dd44221d3b5f93c56"
|
||||||
|
integrity sha512-3QWevHrjT22KdF08Y2a217IYCDQDP7vEJaY4n0lPBeC5UBYbMFMadDfVTsaQwq7wqsEgYUHElPGm3EO1ey+TNw==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.1.1"
|
||||||
|
protobufjs "^6.8.8"
|
||||||
|
|
||||||
chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||||
@ -3757,7 +3836,7 @@ cssnano-preset-default@^4.0.7:
|
|||||||
postcss-normalize-timing-functions "^4.0.2"
|
postcss-normalize-timing-functions "^4.0.2"
|
||||||
postcss-normalize-unicode "^4.0.1"
|
postcss-normalize-unicode "^4.0.1"
|
||||||
postcss-normalize-url "^4.0.1"
|
postcss-normalize-url "^4.0.1"
|
||||||
postcss-normalize-whitespace "^4.0.2"
|
postcss-normalize-var(--text)space "^4.0.2"
|
||||||
postcss-ordered-values "^4.1.2"
|
postcss-ordered-values "^4.1.2"
|
||||||
postcss-reduce-initial "^4.0.3"
|
postcss-reduce-initial "^4.0.3"
|
||||||
postcss-reduce-transforms "^4.0.2"
|
postcss-reduce-transforms "^4.0.2"
|
||||||
@ -6930,6 +7009,11 @@ loglevel@^1.6.8:
|
|||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||||
|
|
||||||
|
long@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||||
|
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||||
|
|
||||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
@ -8433,9 +8517,9 @@ postcss-normalize-url@^4.0.1:
|
|||||||
postcss "^7.0.0"
|
postcss "^7.0.0"
|
||||||
postcss-value-parser "^3.0.0"
|
postcss-value-parser "^3.0.0"
|
||||||
|
|
||||||
postcss-normalize-whitespace@^4.0.2:
|
postcss-normalize-var(--text)space@^4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82"
|
resolved "https://registry.yarnpkg.com/postcss-normalize-var(--text)space/-/postcss-normalize-var(--text)space-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82"
|
||||||
integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==
|
integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss "^7.0.0"
|
postcss "^7.0.0"
|
||||||
@ -8759,6 +8843,25 @@ prop-types@^15.7.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.8.1"
|
react-is "^16.8.1"
|
||||||
|
|
||||||
|
protobufjs@^6.8.8:
|
||||||
|
version "6.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
|
||||||
|
integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise" "^1.1.2"
|
||||||
|
"@protobufjs/base64" "^1.1.2"
|
||||||
|
"@protobufjs/codegen" "^2.0.4"
|
||||||
|
"@protobufjs/eventemitter" "^1.1.0"
|
||||||
|
"@protobufjs/fetch" "^1.1.0"
|
||||||
|
"@protobufjs/float" "^1.0.2"
|
||||||
|
"@protobufjs/inquire" "^1.1.0"
|
||||||
|
"@protobufjs/path" "^1.1.2"
|
||||||
|
"@protobufjs/pool" "^1.1.0"
|
||||||
|
"@protobufjs/utf8" "^1.1.0"
|
||||||
|
"@types/long" "^4.0.1"
|
||||||
|
"@types/node" ">=13.7.0"
|
||||||
|
long "^4.0.0"
|
||||||
|
|
||||||
proxy-addr@~2.0.5:
|
proxy-addr@~2.0.5:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||||
|
Loading…
Reference in New Issue
Block a user