mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-09 18:40:43 +00:00
Merge branch 'master' of github.com:Stremio/stremio-web into board
This commit is contained in:
commit
15ca1fb614
14 changed files with 861 additions and 138 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Calendar, Discover, Addons, Settings, Board, Player } from 'stremio-routes';
|
||||
import { Calendar, Discover, Addons, Settings, Board, Player, Detail } from 'stremio-routes';
|
||||
|
||||
const config = {
|
||||
views: [
|
||||
|
|
@ -27,6 +27,10 @@ const config = {
|
|||
{
|
||||
path: '/settings',
|
||||
component: Settings
|
||||
},
|
||||
{
|
||||
path: '/detail',
|
||||
component: Detail
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import MetadataItem from './MetadataItem';
|
|||
import Router from './Router';
|
||||
import LibraryItemList from './LibraryItemList';
|
||||
import MetaItem from './MetaItem';
|
||||
import Addon from './Addon';
|
||||
import ShareAddon from './ShareAddon';
|
||||
import UserPanel from './UserPanel';
|
||||
import Slider from './Slider';
|
||||
|
|
@ -20,7 +19,6 @@ export {
|
|||
Router,
|
||||
LibraryItemList,
|
||||
MetaItem,
|
||||
Addon,
|
||||
ShareAddon,
|
||||
UserPanel,
|
||||
Slider
|
||||
|
|
|
|||
|
|
@ -32,12 +32,26 @@ const renderType = (types) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles['type']}>
|
||||
{types.length <= 1 ? types.join('') : types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]}
|
||||
<div className={styles['types-container']}>
|
||||
<div className={styles['type']}>
|
||||
{types.length <= 1 ? types.join('') : types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderHostname = (hostname) => {
|
||||
if (hostname.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles['hostname-container']}>
|
||||
<div className={styles['hostname']}>{hostname}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderDescription = (description) => {
|
||||
if (description.length === 0) {
|
||||
return null;
|
||||
|
|
@ -48,25 +62,9 @@ const renderDescription = (description) => {
|
|||
);
|
||||
}
|
||||
|
||||
const renderUrls = (urls) => {
|
||||
if (urls.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles['urls-container']}>
|
||||
{urls.map((url) => {
|
||||
return (
|
||||
<div key={url} className={styles['url']}>{url}</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Addon = (props) => {
|
||||
return (
|
||||
<div className={styles['addon']}>
|
||||
<div className={classnames(styles['addon'], props.className)}>
|
||||
<div className={styles['logo-container']}>
|
||||
<Icon className={styles['logo']} icon={props.logo.length === 0 ? 'ic_addons' : props.logo} />
|
||||
</div>
|
||||
|
|
@ -77,28 +75,30 @@ const Addon = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
{renderType(props.types)}
|
||||
{renderHostname(props.hostname)}
|
||||
{renderDescription(props.description)}
|
||||
{renderUrls(props.urls)}
|
||||
<div className={styles['buttons']}>
|
||||
<div className={classnames(styles['button'], props.isInstalled ? styles['uninstall-button'] : styles['install-button'])} onClick={props.onToggleClicked}>
|
||||
<span className={styles['label']}>{props.isInstalled ? 'Uninstall' : 'Install'}</span>
|
||||
</div>
|
||||
<div className={classnames(styles['button'], styles['share-button'])} onClick={props.shareAddon}>
|
||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||
<span className={styles['label']}>SHARE ADD-ON</span>
|
||||
</div>
|
||||
<div className={classnames(styles['button'], props.isInstalled ? styles['install-button'] : styles['uninstall-button'])} onClick={props.onToggleClicked}>
|
||||
<span className={styles['label']}>{props.isInstalled ? 'Install' : 'Uninstall'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Addon.propTypes = {
|
||||
className: PropTypes.string,
|
||||
logo: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
version: PropTypes.string.isRequired,
|
||||
types: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
hostname: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
urls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
isOfficial: PropTypes.bool.isRequired,
|
||||
isInstalled: PropTypes.bool.isRequired,
|
||||
shareAddon: PropTypes.func.isRequired,
|
||||
onToggleClicked: PropTypes.func.isRequired
|
||||
|
|
@ -108,9 +108,10 @@ Addon.defaultProps = {
|
|||
name: '',
|
||||
version: '',
|
||||
types: [],
|
||||
hostname: '',
|
||||
description: '',
|
||||
urls: [],
|
||||
isOfficial: false,
|
||||
isInstalled: false
|
||||
};
|
||||
|
||||
export default Addon;
|
||||
export default Addon;
|
||||
|
|
@ -1,34 +1,22 @@
|
|||
.addon {
|
||||
--addon-width: 514px;
|
||||
--name-font-size: 20px;
|
||||
--version-font-size: 12px;
|
||||
--type-font-size: 12px;
|
||||
--description-font-size: 12px;
|
||||
--url-font-size: 12px;
|
||||
--button-border-width: 2px;
|
||||
--button-label-font-size: 14px;
|
||||
}
|
||||
|
||||
.addon {
|
||||
display: grid;
|
||||
display: inline-grid;
|
||||
width: var(--addon-width);
|
||||
padding: 2%;
|
||||
background-color: var(--color-background);
|
||||
grid-template-columns: 1fr 4fr;
|
||||
grid-template-rows: calc(0.04 * var(--addon-width)) calc(0.04 * var(--addon-width)) auto auto calc((0.08 * var(--addon-width)));
|
||||
padding: 1.6em;
|
||||
background-color: var(--color-backgroundlighter);
|
||||
grid-template-columns: 1fr 5fr 4fr;
|
||||
grid-template-rows: calc(0.03 * var(--addon-width)) calc(0.03 * var(--addon-width)) calc(0.03 * var(--addon-width)) auto;
|
||||
grid-template-areas:
|
||||
"logo header"
|
||||
"logo type"
|
||||
"logo description"
|
||||
"urls urls"
|
||||
"buttons buttons";
|
||||
"logo header buttons"
|
||||
"logo type buttons"
|
||||
"logo hostname buttons"
|
||||
"logo description buttons";
|
||||
|
||||
.logo-container {
|
||||
grid-area: logo;
|
||||
|
||||
.logo {
|
||||
width: calc(0.2 * var(--addon-width));
|
||||
height: calc(0.2 * var(--addon-width));
|
||||
width: calc(0.12 * var(--addon-width));
|
||||
height: calc(0.12 * var(--addon-width));
|
||||
padding: 20%;
|
||||
fill: var(--color-surfacelighter);
|
||||
background-color: var(--color-backgrounddarker);
|
||||
|
|
@ -50,7 +38,7 @@
|
|||
.name {
|
||||
max-width: 80%;
|
||||
margin-right: 3%;
|
||||
font-size: var(--name-font-size);
|
||||
font-size: 1.8em;
|
||||
color: var(--color-surfacelighter);
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
|
|
@ -59,7 +47,6 @@
|
|||
|
||||
.version {
|
||||
flex: 1;
|
||||
font-size: var(--version-font-size);
|
||||
color: var(--color-surface);
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
|
|
@ -68,44 +55,56 @@
|
|||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
.types-container {
|
||||
grid-area: type;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 4%;
|
||||
font-size: var(--type-font-size);
|
||||
overflow: hidden;
|
||||
color: var(--color-secondarylighter);
|
||||
|
||||
.type {
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.hostname-container {
|
||||
grid-area: hostname;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 4%;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--color-surfacelighter);
|
||||
|
||||
.hostname {
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
grid-area: description;
|
||||
padding-left: 4%;
|
||||
font-size: var(--description-font-size);
|
||||
color: var(--color-surfacelighter);
|
||||
word-break: break-all; //Firefox doesn't support { break-word }
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.urls-container {
|
||||
grid-area: urls;
|
||||
padding: 4% 0%;
|
||||
overflow: auto;
|
||||
word-break: break-all;
|
||||
|
||||
.url {
|
||||
font-size: var(--url-font-size);
|
||||
color: var(--color-surface);
|
||||
}
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
grid-area: buttons;
|
||||
height: calc(0.12 * var(--addon-width));
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
|
||||
.button {
|
||||
width: 46%;
|
||||
width: 65%;
|
||||
height: calc(0.05 * var(--addon-width));
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -114,11 +113,12 @@
|
|||
border-style: solid;
|
||||
|
||||
.icon {
|
||||
width: 7%;
|
||||
margin-right: 2%;
|
||||
width: 8%;
|
||||
margin-right: 3%;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.1em;
|
||||
color: var(--color-surfacelighter);
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,8 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-secondarylighter);
|
||||
border-color: var(--color-secondarylight);
|
||||
background-color: var(--color-secondarylight);
|
||||
|
||||
.icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
|
|
@ -151,15 +152,23 @@
|
|||
background-color: var(--color-signal5);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-signal580);
|
||||
background-color: var(--color-signal560);
|
||||
}
|
||||
}
|
||||
|
||||
&.uninstall-button {
|
||||
border-color: var(--color-surfacedark);
|
||||
|
||||
.label {
|
||||
color: var(--color-surfacedark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surfacedark);
|
||||
|
||||
.label {
|
||||
color: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +1,154 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Addon from './Addon';
|
||||
import Icon from 'stremio-icons/dom';
|
||||
import styles from './styles';
|
||||
|
||||
const ADDON_CATEGORIES = {
|
||||
OFFICIAL: 1,
|
||||
COMMUNITY: 2,
|
||||
MY: 3
|
||||
};
|
||||
|
||||
const ADDON_TYPES = {
|
||||
All: -1,
|
||||
Movies: 0,
|
||||
Series: 1,
|
||||
Channels: 2,
|
||||
Others: 3
|
||||
};
|
||||
|
||||
const DEFAULT_TYPE = 'All';
|
||||
|
||||
class Addons extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedCategory: ADDON_CATEGORIES.OFFICIAL,
|
||||
selectedAddonType: DEFAULT_TYPE
|
||||
};
|
||||
}
|
||||
|
||||
changeSelectedCategory = (event) => {
|
||||
event.currentTarget.blur();
|
||||
this.setState({ selectedCategory: ADDON_CATEGORIES[event.currentTarget.dataset.category] });
|
||||
}
|
||||
|
||||
changeSelectedAddonType = (event) => {
|
||||
event.currentTarget.blur();
|
||||
this.setState({ selectedAddonType: event.currentTarget.dataset.type })
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.selectedCategory !== this.state.selectedCategory ||
|
||||
nextState.selectedAddonType !== this.state.selectedAddonType;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.selectedCategory !== prevState.selectedCategory || !this.getAddonTypes().includes(this.state.selectedAddonType)) {
|
||||
this.setState({ selectedAddonType: DEFAULT_TYPE })
|
||||
}
|
||||
}
|
||||
|
||||
getAddonTypes() {
|
||||
const addonTypes = this.props.addons
|
||||
.filter((addon) => {
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.OFFICIAL) return addon.isOfficial === true;
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.COMMUNITY) return addon.isOfficial === false;
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.MY) return addon.isInstalled === true;
|
||||
})
|
||||
.map((addon) => addon.types)
|
||||
.join()
|
||||
.split(',')
|
||||
.filter((type, index, types) => types.indexOf(type) === index)
|
||||
.sort(function(a, b) {
|
||||
const valueA = ADDON_TYPES[a];
|
||||
const valueB = ADDON_TYPES[b];
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) return valueA - valueB;
|
||||
if (!isNaN(valueA)) return -1;
|
||||
if (!isNaN(valueB)) return 1;
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return addonTypes;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{ color: 'pink', paddingTop: 40 }}>You're on the Addons Tab</div>
|
||||
<div className={styles['addons-container']}>
|
||||
<div className={styles['side-menu']}>
|
||||
<div className={styles['add-addon-container']}>
|
||||
<Icon className={styles['add-addon-icon']} icon={'ic_plus'} />
|
||||
<div className={styles['add-addon-label']}>Add new Add-On</div>
|
||||
</div>
|
||||
{Object.keys(ADDON_CATEGORIES).map((category) =>
|
||||
<div className={classnames(styles['label'], { [styles['selected']]: this.state.selectedCategory === ADDON_CATEGORIES[category] })} tabIndex={'0'} key={category} data-category={category} onClick={this.changeSelectedCategory}>
|
||||
{category}
|
||||
</div>
|
||||
)}
|
||||
<div className={classnames(styles['label'], { [styles['selected']]: this.state.selectedAddonType === DEFAULT_TYPE })} tabIndex={'0'} data-type={DEFAULT_TYPE} onClick={this.changeSelectedAddonType}>
|
||||
All
|
||||
</div>
|
||||
{this.getAddonTypes().map((type) =>
|
||||
<div className={classnames(styles['label'], { [styles['selected']]: this.state.selectedAddonType === type })} tabIndex={'0'} key={type} data-type={type} onClick={this.changeSelectedAddonType}>
|
||||
{type}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles['scroll-container']}>
|
||||
{this.props.addons
|
||||
.filter((addon) => {
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.OFFICIAL) return addon.isOfficial === true;
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.COMMUNITY) return addon.isOfficial === false;
|
||||
if (this.state.selectedCategory === ADDON_CATEGORIES.MY) return addon.isInstalled === true;
|
||||
})
|
||||
.filter((addon) => {
|
||||
return this.state.selectedAddonType === DEFAULT_TYPE ||
|
||||
addon.types.indexOf(this.state.selectedAddonType) !== -1;
|
||||
})
|
||||
.map((addon) =>
|
||||
<Addon key={addon.name}
|
||||
className={styles['addon']}
|
||||
logo={addon.logo}
|
||||
name={addon.name}
|
||||
version={addon.version}
|
||||
isOfficial={addon.isOfficial}
|
||||
isInstalled={addon.isInstalled}
|
||||
types={addon.types}
|
||||
hostname={addon.hostname}
|
||||
description={addon.description}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Addons.propTypes = {
|
||||
addons: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
Addons.defaultProps = {
|
||||
addons: [
|
||||
{ logo: 'ic_series', name: 'Watch Hub', version: '1.3.0', isOfficial: true, isInstalled: false, types: ['ovies', 'Series'], hostname: 'piratebay-stremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuappstremio-addon.herokuapp.com', description: 'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.' },
|
||||
{ name: 'Cinemeta', version: '2.4.0', isOfficial: false, isInstalled: true, types: ['Moies', 'Series'], hostname: 'stremio-zooqle.now.sh', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_youtube_small', name: 'YouTube', version: '1.3.0', isOfficial: false, isInstalled: true, types: ['Channels', 'Videos'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ name: 'OpenSubtitles', version: '1.3.0', isOfficial: false, isInstalled: false, types: ['Movie', 'Seies'], description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_series', name: 'Zooqle', version: '1.3.0', isOfficial: true, isInstalled: false, types: ['Movies', 'Series'], hostname: 'pct.best4stremio.space', description: 'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.' },
|
||||
{ name: 'PirateBay Addon', version: '2.4.0', isOfficial: false, isInstalled: true, types: ['Movies', 'eries'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_youtube_small', name: 'Popcorn Time', version: '1.3.0', isOfficial: false, isInstalled: true, types: ['Channels', 'Videos'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ name: 'IBERIAN', version: '1.3.0', isOfficial: false, isInstalled: true, types: ['Movies', 'Other'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_series', name: 'Ex Addon', version: '1.3.0', isOfficial: false, isInstalled: false, types: ['Movies', 'Series'], hostname: 'pct.best4stremio.space', description: 'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.' },
|
||||
{ name: 'Juan Carlos', version: '2.4.0', isOfficial: false, isInstalled: true, types: ['Movies', 'Seris', 'Channels'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_youtube_small', name: 'Juan Carlos Torrents', version: '1.3.0', isOfficial: false, isInstalled: true, types: ['Channels', 'Videos'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ name: 'Juan Carlos 2', version: '1.3.0', isOfficial: false, isInstalled: false, types: ['Moes', 'Serie'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_series', name: 'Netflix', version: '1.3.0', isOfficial: true, isInstalled: false, types: ['Movies', 'Series'], hostname: 'pct.best4stremio.space', description: 'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.' },
|
||||
{ name: 'Anime Addon', version: '2.4.0', isOfficial: false, isInstalled: true, types: ['Movies', 'Series'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ logo: 'ic_youtube_small', name: 'DTube', version: '1.3.0', isOfficial: true, isInstalled: true, types: ['hannels', 'Videos'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notified when they upload new videos.' },
|
||||
{ name: 'Mixer', version: '1.3.0', isOfficial: false, isInstalled: true, types: ['Movies', 'Series'], hostname: 'pct.best4stremio.space', description: 'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.' }
|
||||
]
|
||||
};
|
||||
|
||||
export default Addons;
|
||||
|
|
|
|||
108
src/routes/Addons/styles.less
Normal file
108
src/routes/Addons/styles.less
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
.addons-container {
|
||||
--addon-width: 960px;
|
||||
--label-width: 196px;
|
||||
--label-height: 44px;
|
||||
--spacing: 8px;
|
||||
--button-border-width: 2px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.addons-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.side-menu {
|
||||
width: calc(var(--label-width) + var(--spacing) * 5.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 calc(var(--spacing) * 2.5 + var(--button-border-width));
|
||||
margin: calc(var(--spacing) * 2.5 + var(--button-border-width)) calc(var(--spacing) * 4.5) calc(var(--spacing) * 2.5 + var(--button-border-width)) 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.add-addon-container {
|
||||
width: var(--label-width);
|
||||
min-height: var(--label-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-top: var(--button-border-width);
|
||||
margin-bottom: calc(var(--spacing) * 2.5 + var(--button-border-width));
|
||||
padding: calc(var(--spacing) * 1.5);
|
||||
background: var(--color-signal5);
|
||||
|
||||
.add-addon-icon {
|
||||
flex: 1;
|
||||
margin-right: calc(var(--spacing) * 2);
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
|
||||
.add-addon-label {
|
||||
flex: 8;
|
||||
font-size: 1.2em;
|
||||
color: var(--color-surfacelighter);
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-signal560);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
width: var(--label-width);
|
||||
min-height: var(--label-height);
|
||||
cursor: pointer;
|
||||
margin-top: var(--button-border-width);
|
||||
padding: calc(var(--spacing) * 1.5);
|
||||
font-size: 1.2em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-surfacelight);
|
||||
|
||||
&.selected {
|
||||
color: var(--color-surfacelighter);
|
||||
background: var(--color-primarydark);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-primarydark);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: var(--button-border-width) solid var(--color-surfacelighter);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-surfacelighter);
|
||||
background: var(--color-backgroundlighter);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
margin-bottom: calc(var(--spacing) * 2.5 + var(--button-border-width));
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: var(--button-border-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
margin: calc(var(--spacing) * 2.5 + var(--button-border-width)) calc(var(--spacing) * 2.5 + var(--button-border-width)) calc(var(--spacing) * 2.5 + var(--button-border-width)) 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
>:not(:first-child) {
|
||||
margin-top: calc(var(--spacing) * 2.5 + var(--button-border-width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,161 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import VideosList from './VideosList';
|
||||
import Icon from 'stremio-icons/dom';
|
||||
import styles from './styles';
|
||||
|
||||
class Detail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
logoLoaded: true
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.logoLoaded !== this.state.logoLoaded;
|
||||
}
|
||||
|
||||
renderSection({ title, links }) {
|
||||
return (
|
||||
<div className={styles['section-container']}>
|
||||
{
|
||||
title ?
|
||||
<div className={styles['title']}>{title}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{links.map(link => <a key={link} className={styles['link']}>{link}</a>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
return (
|
||||
<div style={this.props.metaItem.background.length > 0 ? { backgroundImage: 'url(' + this.props.metaItem.background + ')' } : { backgroundColor: colors.backgrounddarker }} className={styles['detail-container']}>
|
||||
<div className={styles['overlay-container']} />
|
||||
<div className={styles['info-container']}>
|
||||
{
|
||||
this.state.logoLoaded ?
|
||||
<img className={styles['logo']} src={this.props.metaItem.logo} onError={() => this.setState({ logoLoaded: false })} />
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['duration']}>{this.props.metaItem.duration}</div>
|
||||
<div className={styles['release-info']}>
|
||||
{
|
||||
this.props.metaItem.releaseInfo.length > 0 ?
|
||||
this.props.metaItem.releaseInfo
|
||||
:
|
||||
this.props.metaItem.released.getFullYear()
|
||||
}
|
||||
</div>
|
||||
<div className={styles['name']}>{this.props.metaItem.name}</div>
|
||||
<div className={styles['description']}>{this.props.metaItem.description}</div>
|
||||
{this.renderSection({ title: 'GENRES', links: this.props.metaItem.genres })}
|
||||
{this.renderSection({ title: 'WRITTEN BY', links: this.props.metaItem.writers })}
|
||||
{this.renderSection({ title: 'DIRECTED BY', links: this.props.metaItem.directors })}
|
||||
{this.renderSection({ title: 'CAST', links: this.props.metaItem.cast })}
|
||||
<div className={styles['action-buttons-container']}>
|
||||
<a href={this.props.metaItem.links.youtube} className={styles['action-button-container']}>
|
||||
<Icon className={styles['icon']} icon={'ic_movies'} />
|
||||
<div className={styles['label']}>Trailer</div>
|
||||
</a>
|
||||
<a href={this.props.metaItem.links.imdb} target={'_blank'} className={styles['action-button-container']}>
|
||||
<Icon className={styles['icon']} icon={'ic_imdb'} />
|
||||
<div className={styles['label']}>{this.props.metaItem.imdbRating} / 10</div>
|
||||
</a>
|
||||
<div className={styles['action-button-container']} onClick={this.props.toggleLibraryButton}>
|
||||
<Icon className={styles['icon']} icon={this.props.inLibrary ? 'ic_removelib' : 'ic_addlib'} />
|
||||
<div className={styles['label']}>{this.props.inLibrary ? 'Remove from Library' : 'Add to library'}</div>
|
||||
</div>
|
||||
<div className={styles['action-button-container']}>
|
||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||
<div className={styles['label']}>Share</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VideosList className={styles['videos-list']} videos={this.props.metaItem.videos}></VideosList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Detail.propTypes = {
|
||||
inLibrary: PropTypes.bool.isRequired,
|
||||
metaItem: PropTypes.object.isRequired,
|
||||
toggleLibraryButton: PropTypes.func
|
||||
};
|
||||
Detail.defaultProps = {
|
||||
inLibrary: false,
|
||||
metaItem: {
|
||||
logo: 'https://images.metahub.space/logo/medium/tt4123430/img',
|
||||
background: 'https://images.metahub.space/background/medium/tt4123430/img',
|
||||
duration: '134 min',
|
||||
releaseInfo: '2018',
|
||||
released: new Date(2018, 4, 23),
|
||||
imdbRating: '7.4',
|
||||
name: 'Fantastic Beasts and Where to Find Them: The Original Screenplay',
|
||||
description: 'In an effort to thwart Grindelwald' + 's plans ofraisingpurebloodwizardstoansofraisingpurebloodwizardstoansofraisingpurebloodwizardstoansofraising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards to rule over all non-magical beings, Albus Dumbledore enlists his former student Newt Scamander, who agrees to help, unaware of the dangers that lie ahead. Lines are drawn as love and loyalty are tested, even among the truest friends and family, in an increasingly divided wizarding world.',
|
||||
genres: ['Adventure', 'Family', 'Fantasy'],
|
||||
writers: ['J. K. Rowling'],
|
||||
directors: ['David Yates'],
|
||||
cast: ['Johny Depp', 'Kevin Guthrie', 'Carmen Ejogo', 'Wolf Roth'],
|
||||
videos: [
|
||||
{ id: '1', poster: 'https://www.stremio.com/websiste/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '2', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 3 },
|
||||
{ id: '3', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 5 },
|
||||
{ id: '4', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 4 },
|
||||
{ id: '5', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 },
|
||||
{ id: '6', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '7', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '8', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 },
|
||||
{ id: '9', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '10', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 },
|
||||
{ id: '11', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '12', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '13', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '14', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 },
|
||||
{ id: '15', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '16', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 },
|
||||
{ id: '17', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '18', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '19', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '20', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 },
|
||||
{ id: '21', poster: 'https://www.stremiocom/wsebsite/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '22', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 },
|
||||
{ id: '23', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '24', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '25', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '26', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 },
|
||||
{ id: '27', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '28', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 },
|
||||
{ id: '29', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '30', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '31', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '32', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 },
|
||||
{ id: '33', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '34', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '35', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '36', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '37', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '38', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 },
|
||||
{ id: '39', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '40', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '41', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 },
|
||||
{ id: '42', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 },
|
||||
{ id: '43', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 },
|
||||
{ id: '44', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 },
|
||||
{ id: '45', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 },
|
||||
{ id: '46', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }
|
||||
],
|
||||
links: {
|
||||
share: '',
|
||||
imdb: 'https://www.imdb.com/title/tt4123430/?ref_=fn_al_tt_3',
|
||||
youtube: '#/player'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Detail;
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ const renderProgress = (progress) => {
|
|||
|
||||
const Video = (props) => {
|
||||
return (
|
||||
<div onClick={props.onVideoClicked} className={classnames(styles['video-container'], props.className)}>
|
||||
<div className={classnames(styles['video-container'], props.className)} data-video-id={props.id} onClick={props.onClick}>
|
||||
<div className={styles['flex-row-container']}>
|
||||
{renderPoster(props.poster)}
|
||||
<div className={styles['info-container']}>
|
||||
|
|
@ -106,14 +106,16 @@ const Video = (props) => {
|
|||
|
||||
Video.propTypes = {
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string.isRequired,
|
||||
poster: PropTypes.string.isRequired,
|
||||
episode: PropTypes.number.isRequired,
|
||||
season: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
released: PropTypes.instanceOf(Date).isRequired,
|
||||
isWatched: PropTypes.bool.isRequired,
|
||||
isUpcoming: PropTypes.bool.isRequired,
|
||||
progress: PropTypes.number.isRequired,
|
||||
onVideoClicked: PropTypes.func
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
Video.defaultProps = {
|
||||
poster: '',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
.video-container {
|
||||
--video-width: 360px;
|
||||
--spacing: 8px;
|
||||
--title-font-size: 12px;
|
||||
--released-date-font-size: 11px;
|
||||
--label-font-size: 10px;
|
||||
--label-border-width: 2px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
width: var(--video-width);
|
||||
background-color: var(--color-backgroundlight);
|
||||
background-color: var(--color-surfacedarker60);
|
||||
|
||||
.flex-row-container {
|
||||
display: flex;
|
||||
|
|
@ -40,21 +31,21 @@
|
|||
|
||||
.info-container {
|
||||
flex: 3;
|
||||
min-height: calc(0.2 * var(--video-width));
|
||||
min-height: calc(var(--video-width) * 0.2);
|
||||
padding: var(--spacing);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
font-size: var(--title-font-size);
|
||||
color: var(--color-surfacelighter);
|
||||
line-height: 1.2em;
|
||||
color: var(--color-surfacelight);
|
||||
word-break: break-all; //Firefox doesn't support { break-word }
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.released-date {
|
||||
font-size: var(--released-date-font-size);
|
||||
font-size: 0.9em;
|
||||
color: var(--color-surface);
|
||||
}
|
||||
|
||||
|
|
@ -62,13 +53,13 @@
|
|||
display: flex;
|
||||
|
||||
.upcoming-label, .watched-label {
|
||||
font-size: var(--label-font-size);
|
||||
font-weight: 600;
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
border-width: var(--label-border-width);
|
||||
border-width: calc(var(--spacing) * 0.25);
|
||||
border-style: solid;
|
||||
padding: 0 0.6em;
|
||||
color: var(--color-surfacelighter);
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.upcoming-label {
|
||||
|
|
@ -82,25 +73,25 @@
|
|||
}
|
||||
|
||||
>:not(:last-child) {
|
||||
margin-bottom: calc(0.5 * var(--spacing));
|
||||
margin-bottom: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-container {
|
||||
width: calc(0.07 * var(--video-width));
|
||||
width: calc(var(--video-width) * 0.07);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing) var(--spacing) var(--spacing) 0;
|
||||
|
||||
.arrow {
|
||||
width: 100%;
|
||||
fill: var(--color-surfacelighter);
|
||||
fill: var(--color-surfacelight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
height: calc(0.5 * var(--spacing));
|
||||
height: calc(var(--spacing) * 0.5);
|
||||
background-color: var(--color-primarydark);
|
||||
|
||||
.progress {
|
||||
|
|
@ -111,6 +102,24 @@
|
|||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-surfacelighter20);
|
||||
background-color: var(--color-surfacedarker);
|
||||
|
||||
.info-container {
|
||||
.title {
|
||||
color: var(--color-surfacelighter);
|
||||
}
|
||||
|
||||
.label-container {
|
||||
.upcoming-label, .watched-label {
|
||||
color: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-container {
|
||||
.arrow {
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'stremio-icons/dom';
|
||||
import { Popup } from 'stremio-common';
|
||||
import Video from './Video';
|
||||
import styles from './styles';
|
||||
|
||||
|
|
@ -8,20 +10,25 @@ class VideosList extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.seasonsPopupRef = React.createRef();
|
||||
this.seasons = this.props.videos.map((video) => video.season)
|
||||
.filter((season, index, seasons) => seasons.indexOf(season) === index);
|
||||
|
||||
this.state = {
|
||||
selectedSeason: this.seasons[0]
|
||||
selectedSeason: this.seasons[0],
|
||||
selectedVideoId: 0,
|
||||
seasonsPopupOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
changeSeason = (event) => {
|
||||
this.setState({ selectedSeason: parseInt(event.target.value) });
|
||||
this.setState({ selectedSeason: parseInt(event.currentTarget.dataset.season) });
|
||||
this.seasonsPopupRef.current && this.seasonsPopupRef.current.close();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.selectedSeason !== this.state.selectedSeason;
|
||||
return nextState.selectedSeason !== this.state.selectedSeason ||
|
||||
nextState.seasonsPopupOpen !== this.state.seasonsPopupOpen;
|
||||
}
|
||||
|
||||
onPrevButtonClicked = () => {
|
||||
|
|
@ -34,20 +41,45 @@ class VideosList extends Component {
|
|||
this.setState({ selectedSeason: this.seasons[nextSeasonIndex] });
|
||||
}
|
||||
|
||||
onSeasonsPopupOpen = () => {
|
||||
this.setState({ seasonsPopupOpen: true });
|
||||
}
|
||||
|
||||
onSeasonsPopupClose = () => {
|
||||
this.setState({ seasonsPopupOpen: false });
|
||||
}
|
||||
|
||||
onClick = (event) => {
|
||||
this.setState({ selectedVideoId: event.currentTarget.dataset.videoId });
|
||||
console.log(event.currentTarget.dataset.videoId);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles['videos-list-container']}>
|
||||
<div className={classnames(styles['videos-list-container'], this.props.className)}>
|
||||
<div className={styles['seasons-bar']}>
|
||||
<div className={styles['button-container']} onClick={this.onPrevButtonClicked}>
|
||||
<Icon className={styles['button-icon']} icon={'ic_arrow_left'} />
|
||||
</div>
|
||||
<select value={this.state.selectedSeason} onChange={this.changeSeason}>
|
||||
{this.seasons.map((season) =>
|
||||
<option key={season} value={season}>
|
||||
{season}
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
<Popup ref={this.seasonsPopupRef} className={'detail-popup-container'} onOpen={this.onSeasonsPopupOpen} onClose={this.onSeasonsPopupClose}>
|
||||
<Popup.Label>
|
||||
<div className={classnames(styles['season-bar-button'], { 'active': this.state.seasonsPopupOpen })}>
|
||||
<div className={styles['season-label']}>Season</div>
|
||||
<div className={styles['season-number']}>{this.state.selectedSeason}</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_down'} />
|
||||
</div>
|
||||
</Popup.Label>
|
||||
<Popup.Menu>
|
||||
<div className={styles['popup-content']}>
|
||||
{this.seasons.map((season) =>
|
||||
<div className={classnames(styles['season'], { [styles['selected-season']]: this.state.selectedSeason === season })} key={season} data-season={season} onClick={this.changeSeason}>
|
||||
<div className={styles['season-label']}>Season</div>
|
||||
<div className={styles['season-number']}>{season}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popup.Menu>
|
||||
</Popup>
|
||||
<div className={styles['button-container']} onClick={this.onNextButtonClicked} >
|
||||
<Icon className={styles['button-icon']} icon={'ic_arrow_left'} />
|
||||
</div>
|
||||
|
|
@ -58,13 +90,16 @@ class VideosList extends Component {
|
|||
.map((video) =>
|
||||
<Video key={video.id}
|
||||
className={styles['video']}
|
||||
id={video.id}
|
||||
poster={video.poster}
|
||||
episode={video.episode}
|
||||
season={video.season}
|
||||
title={video.name}
|
||||
released={video.released}
|
||||
isWatched={video.isWatched}
|
||||
isUpcoming={video.isUpcoming}
|
||||
progress={video.progress}
|
||||
onClick={this.onClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -74,6 +109,7 @@ class VideosList extends Component {
|
|||
}
|
||||
|
||||
VideosList.propTypes = {
|
||||
className: PropTypes.string,
|
||||
videos: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
VideosList.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,69 @@
|
|||
.videos-list-container {
|
||||
--scroll-container-width: 392px;
|
||||
--seasons-bar-height: 50px;
|
||||
--spacing: 8px;
|
||||
}
|
||||
|
||||
.videos-list-container {
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
width: calc(var(--video-width) + var(--spacing) * 6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-background);
|
||||
align-items: center;
|
||||
background: var(--color-backgrounddarker40);
|
||||
|
||||
.seasons-bar {
|
||||
height: var(--seasons-bar-height);
|
||||
height: calc(var(--video-width) * 0.14);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing);
|
||||
|
||||
.button-container {
|
||||
width: calc(1.5 * var(--seasons-bar-height));
|
||||
.season-bar-button {
|
||||
cursor: pointer;
|
||||
width: calc(var(--video-width) * 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing);
|
||||
color: var(--color-surfacelight);
|
||||
|
||||
.season-label {
|
||||
max-width: 8em;
|
||||
max-height: 2.4em;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
text-align: end;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.season-number {
|
||||
margin: 0 calc(var(--spacing) * 1.5) 0 var(--spacing);
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: calc(var(--video-width) * 0.04);
|
||||
height: calc(var(--video-width) * 0.04);
|
||||
fill: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-surfacedarker60);
|
||||
|
||||
.icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
color: var(--color-backgrounddarker);
|
||||
background-color: var(--color-surfacelighter);
|
||||
|
||||
.icon {
|
||||
fill: var(--color-backgrounddarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-container {
|
||||
cursor: pointer;
|
||||
width: calc(var(--video-width) * 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -26,18 +71,22 @@
|
|||
.button-icon {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
fill: var(--color-surfacelighter);
|
||||
fill: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surfacelighter20);
|
||||
background-color: var(--color-surfacedarker60);
|
||||
|
||||
.button-icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
width: var(--scroll-container-width);
|
||||
width: calc(var(--video-width) + var(--spacing) * 4);
|
||||
padding: 0 calc(2 * var(--spacing));
|
||||
margin: 0 var(--spacing);
|
||||
overflow-y: auto;
|
||||
|
|
@ -49,14 +98,54 @@
|
|||
}
|
||||
|
||||
.scroll-container::-webkit-scrollbar {
|
||||
width: var(--spacing);
|
||||
width: var(--spacing) !important;
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-scrollbar-thumb {
|
||||
background-color: var(--color-secondarylighter80);
|
||||
background-color: var(--color-secondarylighter);
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-scrollbar-track {
|
||||
background-color: var(--color-backgroundlight);
|
||||
background-color: var(--color-backgroundlighter);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.detail-popup-container) {
|
||||
--border-color: var(--color-backgrounddarker80);
|
||||
|
||||
.popup-content {
|
||||
width: calc(var(--video-width) * 0.6);
|
||||
background-color: var(--color-surfacelighter);
|
||||
|
||||
.season {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: calc(var(--spacing) * 1.5);
|
||||
font-size: 1.2em;
|
||||
color: var(--color-backgrounddark);
|
||||
|
||||
.season-label {
|
||||
max-height: 2.4em;
|
||||
line-height: 1.2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.season-number {
|
||||
margin-left: var(--spacing);
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
&.selected-season {
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-primarydark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-primarylight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/routes/Detail/styles.less
Normal file
175
src/routes/Detail/styles.less
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
.detail-container, :global(.detail-popup-container) {
|
||||
--spacing: 8px;
|
||||
--action-button-width: 80px;
|
||||
--video-width: 360px;
|
||||
--stream-width: 360px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.overlay-container {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--color-backgrounddarker60);
|
||||
}
|
||||
|
||||
.info-container {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 40%;
|
||||
bottom: 0;
|
||||
padding: calc(var(--spacing) * 3);
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
height: calc(var(--action-button-width) * 1.2);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo-error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.duration {
|
||||
display: inline-block;
|
||||
max-width: 45%;
|
||||
margin-right: 1.2em;
|
||||
font-size: 1.15em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.release-info {
|
||||
display: inline-block;
|
||||
max-width: 45%;
|
||||
font-size: 1.15em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.name {
|
||||
max-height: 3em;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 1.5em;
|
||||
line-height: 1.5;
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.description {
|
||||
max-height: 10.5em;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
line-height: 1.5;
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.section-container {
|
||||
max-height: 3.2em;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
margin-bottom: 0.3em;
|
||||
font-size: 1.15em;
|
||||
color: var(--color-surface);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: none;
|
||||
max-width: 100%;
|
||||
padding: 0.3em 0.6em;
|
||||
font-size: 1.15em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-surfacelight);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-surface40);
|
||||
}
|
||||
|
||||
&:nth-child(-n+6) {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-container {
|
||||
position: absolute;
|
||||
left: calc(var(--spacing) * 3);
|
||||
bottom: calc(var(--spacing) * 3);
|
||||
|
||||
.action-button-container {
|
||||
cursor: pointer;
|
||||
width: var(--action-button-width);
|
||||
height: var(--action-button-width);
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
.icon {
|
||||
height: 30%;
|
||||
margin: 10% 0;
|
||||
fill: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
.label {
|
||||
height: 2.4em;
|
||||
padding: 0 1em;
|
||||
overflow-wrap: break-word;
|
||||
overflow: hidden;
|
||||
font-size: 1.05em;
|
||||
line-height: 1.2em;
|
||||
color: var(--color-surfacelight);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surfacedarker60);
|
||||
|
||||
.icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--color-surfacelighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>:not(:last-child) {
|
||||
margin-bottom: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.videos-list {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: calc(3 * var(--spacing)) 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { Addon, Checkbox, LibraryItemList, MetaItem, ShareAddon, UserPanel } from 'stremio-common';
|
||||
import { Checkbox, LibraryItemList, MetaItem, ShareAddon, UserPanel } from 'stremio-common';
|
||||
import Addon from '../src/routes/Addons/Addon';
|
||||
import Stream from '../src/routes/Detail/StreamsList/Stream';
|
||||
import Video from '../src/routes/Detail/VideosList/Video';
|
||||
import VideosList from '../src/routes/Detail/VideosList';
|
||||
|
|
@ -42,7 +43,6 @@ storiesOf('Addon', module)
|
|||
isInstalled={false}
|
||||
types={['Movies', 'Series']}
|
||||
description={'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.'}
|
||||
urls={['http://nfxaddon.strem.io/stremioget', 'http://127.0.0.1:11470/addons/com.stremio.subtitles/stremioget', 'http://127.0.0.1:11470/addons/com.stremio.localfiles/stremioget']}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -54,7 +54,6 @@ storiesOf('Addon', module)
|
|||
isInstalled={true}
|
||||
types={['Movies', 'Series']}
|
||||
description={'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.'}
|
||||
urls={['https://channels.strem.io/stremioget/stremio/v1', 'https://channels.strem.io/stremioget/stremio/v1']}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -67,7 +66,6 @@ storiesOf('Addon', module)
|
|||
isInstalled={true}
|
||||
types={['Channels', 'Videos']}
|
||||
description={'Watch your favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiourfavourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notiour favourite YouTube channels ad-free and get notified when they upload new videos.'}
|
||||
urls={['https://channels.strem.io/stremioget/stremio/v1', 'https://channels.strem.io/stremioget/stremio/v1']}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -79,7 +77,6 @@ storiesOf('Addon', module)
|
|||
isInstalled={false}
|
||||
types={['Movies', 'Series']}
|
||||
description={'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.'}
|
||||
urls={['https://channels.strem.io/stremioget/stremio/v1channels.strem.io/stremioget/stremio/v1channels.strem.io/stremioget/stremio/v1', 'https://channels.strem.io/stremioget/stremio/v1', 'http://127.0.0.1:11470/addons/com.stremio.subtitles/stremioget', 'https://channels.strem.io/stremioget/stremio/v1']}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
|
|
|||
Loading…
Reference in a new issue