Merge branch 'master' of github.com:Stremio/stremio-web into board

This commit is contained in:
NikolaBorislavovHristov 2019-01-17 11:35:27 +02:00
commit 15ca1fb614
14 changed files with 861 additions and 138 deletions

View file

@ -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 = { const config = {
views: [ views: [
@ -27,6 +27,10 @@ const config = {
{ {
path: '/settings', path: '/settings',
component: Settings component: Settings
},
{
path: '/detail',
component: Detail
} }
] ]
}, },

View file

@ -6,7 +6,6 @@ import MetadataItem from './MetadataItem';
import Router from './Router'; import Router from './Router';
import LibraryItemList from './LibraryItemList'; import LibraryItemList from './LibraryItemList';
import MetaItem from './MetaItem'; import MetaItem from './MetaItem';
import Addon from './Addon';
import ShareAddon from './ShareAddon'; import ShareAddon from './ShareAddon';
import UserPanel from './UserPanel'; import UserPanel from './UserPanel';
import Slider from './Slider'; import Slider from './Slider';
@ -20,7 +19,6 @@ export {
Router, Router,
LibraryItemList, LibraryItemList,
MetaItem, MetaItem,
Addon,
ShareAddon, ShareAddon,
UserPanel, UserPanel,
Slider Slider

View file

@ -32,12 +32,26 @@ const renderType = (types) => {
} }
return ( return (
<div className={styles['type']}> <div className={styles['types-container']}>
{types.length <= 1 ? types.join('') : types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]} <div className={styles['type']}>
{types.length <= 1 ? types.join('') : types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1]}
</div>
</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) => { const renderDescription = (description) => {
if (description.length === 0) { if (description.length === 0) {
return null; 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) => { const Addon = (props) => {
return ( return (
<div className={styles['addon']}> <div className={classnames(styles['addon'], props.className)}>
<div className={styles['logo-container']}> <div className={styles['logo-container']}>
<Icon className={styles['logo']} icon={props.logo.length === 0 ? 'ic_addons' : props.logo} /> <Icon className={styles['logo']} icon={props.logo.length === 0 ? 'ic_addons' : props.logo} />
</div> </div>
@ -77,28 +75,30 @@ const Addon = (props) => {
</div> </div>
</div> </div>
{renderType(props.types)} {renderType(props.types)}
{renderHostname(props.hostname)}
{renderDescription(props.description)} {renderDescription(props.description)}
{renderUrls(props.urls)}
<div className={styles['buttons']}> <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}> <div className={classnames(styles['button'], styles['share-button'])} onClick={props.shareAddon}>
<Icon className={styles['icon']} icon={'ic_share'} /> <Icon className={styles['icon']} icon={'ic_share'} />
<span className={styles['label']}>SHARE ADD-ON</span> <span className={styles['label']}>SHARE ADD-ON</span>
</div> </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>
</div> </div>
); );
} }
Addon.propTypes = { Addon.propTypes = {
className: PropTypes.string,
logo: PropTypes.string.isRequired, logo: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
version: PropTypes.string.isRequired, version: PropTypes.string.isRequired,
types: PropTypes.arrayOf(PropTypes.string).isRequired, types: PropTypes.arrayOf(PropTypes.string).isRequired,
hostname: PropTypes.string.isRequired,
description: PropTypes.string.isRequired, description: PropTypes.string.isRequired,
urls: PropTypes.arrayOf(PropTypes.string).isRequired, isOfficial: PropTypes.bool.isRequired,
isInstalled: PropTypes.bool.isRequired, isInstalled: PropTypes.bool.isRequired,
shareAddon: PropTypes.func.isRequired, shareAddon: PropTypes.func.isRequired,
onToggleClicked: PropTypes.func.isRequired onToggleClicked: PropTypes.func.isRequired
@ -108,9 +108,10 @@ Addon.defaultProps = {
name: '', name: '',
version: '', version: '',
types: [], types: [],
hostname: '',
description: '', description: '',
urls: [], isOfficial: false,
isInstalled: false isInstalled: false
}; };
export default Addon; export default Addon;

View file

@ -1,34 +1,22 @@
.addon { .addon {
--addon-width: 514px; display: inline-grid;
--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;
width: var(--addon-width); width: var(--addon-width);
padding: 2%; padding: 1.6em;
background-color: var(--color-background); background-color: var(--color-backgroundlighter);
grid-template-columns: 1fr 4fr; grid-template-columns: 1fr 5fr 4fr;
grid-template-rows: calc(0.04 * var(--addon-width)) calc(0.04 * var(--addon-width)) auto auto calc((0.08 * var(--addon-width))); 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: grid-template-areas:
"logo header" "logo header buttons"
"logo type" "logo type buttons"
"logo description" "logo hostname buttons"
"urls urls" "logo description buttons";
"buttons buttons";
.logo-container { .logo-container {
grid-area: logo; grid-area: logo;
.logo { .logo {
width: calc(0.2 * var(--addon-width)); width: calc(0.12 * var(--addon-width));
height: calc(0.2 * var(--addon-width)); height: calc(0.12 * var(--addon-width));
padding: 20%; padding: 20%;
fill: var(--color-surfacelighter); fill: var(--color-surfacelighter);
background-color: var(--color-backgrounddarker); background-color: var(--color-backgrounddarker);
@ -50,7 +38,7 @@
.name { .name {
max-width: 80%; max-width: 80%;
margin-right: 3%; margin-right: 3%;
font-size: var(--name-font-size); font-size: 1.8em;
color: var(--color-surfacelighter); color: var(--color-surfacelighter);
overflow: hidden; overflow: hidden;
white-space: pre; white-space: pre;
@ -59,7 +47,6 @@
.version { .version {
flex: 1; flex: 1;
font-size: var(--version-font-size);
color: var(--color-surface); color: var(--color-surface);
overflow: hidden; overflow: hidden;
white-space: pre; white-space: pre;
@ -68,44 +55,56 @@
} }
} }
.type { .types-container {
grid-area: type; grid-area: type;
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 4%; padding-left: 4%;
font-size: var(--type-font-size); overflow: hidden;
color: var(--color-secondarylighter); 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 { .description {
grid-area: description; grid-area: description;
padding-left: 4%; padding-left: 4%;
font-size: var(--description-font-size);
color: var(--color-surfacelighter); color: var(--color-surfacelighter);
word-break: break-all; //Firefox doesn't support { break-word } overflow: hidden;
word-break: break-word; overflow-wrap: 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);
}
} }
.buttons { .buttons {
grid-area: buttons; grid-area: buttons;
height: calc(0.12 * var(--addon-width));
display: flex; display: flex;
align-items: stretch; flex-direction: column;
align-items: flex-end;
justify-content: space-between; justify-content: space-between;
.button { .button {
width: 46%; width: 65%;
height: calc(0.05 * var(--addon-width));
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -114,11 +113,12 @@
border-style: solid; border-style: solid;
.icon { .icon {
width: 7%; width: 8%;
margin-right: 2%; margin-right: 3%;
} }
.label { .label {
font-size: 1.1em;
color: var(--color-surfacelighter); color: var(--color-surfacelighter);
} }
@ -134,7 +134,8 @@
} }
&:hover { &:hover {
background-color: var(--color-secondarylighter); border-color: var(--color-secondarylight);
background-color: var(--color-secondarylight);
.icon { .icon {
fill: var(--color-surfacelighter); fill: var(--color-surfacelighter);
@ -151,15 +152,23 @@
background-color: var(--color-signal5); background-color: var(--color-signal5);
&:hover { &:hover {
background-color: var(--color-signal580); background-color: var(--color-signal560);
} }
} }
&.uninstall-button { &.uninstall-button {
border-color: var(--color-surfacedark); border-color: var(--color-surfacedark);
.label {
color: var(--color-surfacedark);
}
&:hover { &:hover {
background-color: var(--color-surfacedark); background-color: var(--color-surfacedark);
.label {
color: var(--color-surfacelighter);
}
} }
} }

View file

@ -1,11 +1,154 @@
import React, { Component } from 'react'; 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 { 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() { render() {
return ( 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; export default Addons;

View 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));
}
}
}

View file

@ -1,9 +1,161 @@
import React, { Component } from 'react'; 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 { 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() { 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; export default Detail;

View file

@ -87,7 +87,7 @@ const renderProgress = (progress) => {
const Video = (props) => { const Video = (props) => {
return ( 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']}> <div className={styles['flex-row-container']}>
{renderPoster(props.poster)} {renderPoster(props.poster)}
<div className={styles['info-container']}> <div className={styles['info-container']}>
@ -106,14 +106,16 @@ const Video = (props) => {
Video.propTypes = { Video.propTypes = {
className: PropTypes.string, className: PropTypes.string,
id: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired, poster: PropTypes.string.isRequired,
episode: PropTypes.number.isRequired, episode: PropTypes.number.isRequired,
season: PropTypes.number.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
released: PropTypes.instanceOf(Date).isRequired, released: PropTypes.instanceOf(Date).isRequired,
isWatched: PropTypes.bool.isRequired, isWatched: PropTypes.bool.isRequired,
isUpcoming: PropTypes.bool.isRequired, isUpcoming: PropTypes.bool.isRequired,
progress: PropTypes.number.isRequired, progress: PropTypes.number.isRequired,
onVideoClicked: PropTypes.func onClick: PropTypes.func
}; };
Video.defaultProps = { Video.defaultProps = {
poster: '', poster: '',

View file

@ -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 { .video-container {
width: var(--video-width); width: var(--video-width);
background-color: var(--color-backgroundlight); background-color: var(--color-surfacedarker60);
.flex-row-container { .flex-row-container {
display: flex; display: flex;
@ -40,21 +31,21 @@
.info-container { .info-container {
flex: 3; flex: 3;
min-height: calc(0.2 * var(--video-width)); min-height: calc(var(--video-width) * 0.2);
padding: var(--spacing); padding: var(--spacing);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
.title { .title {
font-size: var(--title-font-size); line-height: 1.2em;
color: var(--color-surfacelighter); color: var(--color-surfacelight);
word-break: break-all; //Firefox doesn't support { break-word } word-break: break-all; //Firefox doesn't support { break-word }
word-break: break-word; word-break: break-word;
} }
.released-date { .released-date {
font-size: var(--released-date-font-size); font-size: 0.9em;
color: var(--color-surface); color: var(--color-surface);
} }
@ -62,13 +53,13 @@
display: flex; display: flex;
.upcoming-label, .watched-label { .upcoming-label, .watched-label {
font-size: var(--label-font-size); font-size: 0.8em;
font-weight: 600; font-weight: 500;
line-height: 1.5; line-height: 1.5;
border-width: var(--label-border-width); border-width: calc(var(--spacing) * 0.25);
border-style: solid; border-style: solid;
padding: 0 0.6em; padding: 0 0.6em;
color: var(--color-surfacelighter); color: var(--color-surfacelight);
} }
.upcoming-label { .upcoming-label {
@ -82,25 +73,25 @@
} }
>:not(:last-child) { >:not(:last-child) {
margin-bottom: calc(0.5 * var(--spacing)); margin-bottom: calc(var(--spacing) * 0.5);
} }
} }
.arrow-container { .arrow-container {
width: calc(0.07 * var(--video-width)); width: calc(var(--video-width) * 0.07);
display: flex; display: flex;
align-items: center; align-items: center;
padding: var(--spacing) var(--spacing) var(--spacing) 0; padding: var(--spacing) var(--spacing) var(--spacing) 0;
.arrow { .arrow {
width: 100%; width: 100%;
fill: var(--color-surfacelighter); fill: var(--color-surfacelight);
} }
} }
} }
.progress-container { .progress-container {
height: calc(0.5 * var(--spacing)); height: calc(var(--spacing) * 0.5);
background-color: var(--color-primarydark); background-color: var(--color-primarydark);
.progress { .progress {
@ -111,6 +102,24 @@
&:hover { &:hover {
cursor: pointer; 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);
}
}
} }
} }

View file

@ -1,6 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames';
import Icon from 'stremio-icons/dom'; import Icon from 'stremio-icons/dom';
import { Popup } from 'stremio-common';
import Video from './Video'; import Video from './Video';
import styles from './styles'; import styles from './styles';
@ -8,20 +10,25 @@ class VideosList extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.seasonsPopupRef = React.createRef();
this.seasons = this.props.videos.map((video) => video.season) this.seasons = this.props.videos.map((video) => video.season)
.filter((season, index, seasons) => seasons.indexOf(season) === index); .filter((season, index, seasons) => seasons.indexOf(season) === index);
this.state = { this.state = {
selectedSeason: this.seasons[0] selectedSeason: this.seasons[0],
selectedVideoId: 0,
seasonsPopupOpen: false
} }
} }
changeSeason = (event) => { 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) { shouldComponentUpdate(nextProps, nextState) {
return nextState.selectedSeason !== this.state.selectedSeason; return nextState.selectedSeason !== this.state.selectedSeason ||
nextState.seasonsPopupOpen !== this.state.seasonsPopupOpen;
} }
onPrevButtonClicked = () => { onPrevButtonClicked = () => {
@ -34,20 +41,45 @@ class VideosList extends Component {
this.setState({ selectedSeason: this.seasons[nextSeasonIndex] }); 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() { render() {
return ( 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['seasons-bar']}>
<div className={styles['button-container']} onClick={this.onPrevButtonClicked}> <div className={styles['button-container']} onClick={this.onPrevButtonClicked}>
<Icon className={styles['button-icon']} icon={'ic_arrow_left'} /> <Icon className={styles['button-icon']} icon={'ic_arrow_left'} />
</div> </div>
<select value={this.state.selectedSeason} onChange={this.changeSeason}> <Popup ref={this.seasonsPopupRef} className={'detail-popup-container'} onOpen={this.onSeasonsPopupOpen} onClose={this.onSeasonsPopupClose}>
{this.seasons.map((season) => <Popup.Label>
<option key={season} value={season}> <div className={classnames(styles['season-bar-button'], { 'active': this.state.seasonsPopupOpen })}>
{season} <div className={styles['season-label']}>Season</div>
</option> <div className={styles['season-number']}>{this.state.selectedSeason}</div>
)} <Icon className={styles['icon']} icon={'ic_arrow_down'} />
</select> </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} > <div className={styles['button-container']} onClick={this.onNextButtonClicked} >
<Icon className={styles['button-icon']} icon={'ic_arrow_left'} /> <Icon className={styles['button-icon']} icon={'ic_arrow_left'} />
</div> </div>
@ -58,13 +90,16 @@ class VideosList extends Component {
.map((video) => .map((video) =>
<Video key={video.id} <Video key={video.id}
className={styles['video']} className={styles['video']}
id={video.id}
poster={video.poster} poster={video.poster}
episode={video.episode} episode={video.episode}
season={video.season}
title={video.name} title={video.name}
released={video.released} released={video.released}
isWatched={video.isWatched} isWatched={video.isWatched}
isUpcoming={video.isUpcoming} isUpcoming={video.isUpcoming}
progress={video.progress} progress={video.progress}
onClick={this.onClick}
/> />
)} )}
</div> </div>
@ -74,6 +109,7 @@ class VideosList extends Component {
} }
VideosList.propTypes = { VideosList.propTypes = {
className: PropTypes.string,
videos: PropTypes.arrayOf(PropTypes.object).isRequired videos: PropTypes.arrayOf(PropTypes.object).isRequired
}; };
VideosList.defaultProps = { VideosList.defaultProps = {

View file

@ -1,24 +1,69 @@
.videos-list-container { .videos-list-container {
--scroll-container-width: 392px; width: calc(var(--video-width) + var(--spacing) * 6);
--seasons-bar-height: 50px; display: flex;
--spacing: 8px;
}
.videos-list-container {
height: 100%;
display: inline-flex;
flex-direction: column; flex-direction: column;
background: var(--color-background); align-items: center;
background: var(--color-backgrounddarker40);
.seasons-bar { .seasons-bar {
height: var(--seasons-bar-height); height: calc(var(--video-width) * 0.14);
width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: var(--spacing); margin-bottom: var(--spacing);
.button-container { .season-bar-button {
width: calc(1.5 * var(--seasons-bar-height));
cursor: pointer; 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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -26,18 +71,22 @@
.button-icon { .button-icon {
width: 60%; width: 60%;
height: 60%; height: 60%;
fill: var(--color-surfacelighter); fill: var(--color-surfacelight);
} }
&:hover { &:hover {
background-color: var(--color-surfacelighter20); background-color: var(--color-surfacedarker60);
.button-icon {
fill: var(--color-surfacelighter);
}
} }
} }
} }
.scroll-container { .scroll-container {
flex: 1; flex: 1;
width: var(--scroll-container-width); width: calc(var(--video-width) + var(--spacing) * 4);
padding: 0 calc(2 * var(--spacing)); padding: 0 calc(2 * var(--spacing));
margin: 0 var(--spacing); margin: 0 var(--spacing);
overflow-y: auto; overflow-y: auto;
@ -49,14 +98,54 @@
} }
.scroll-container::-webkit-scrollbar { .scroll-container::-webkit-scrollbar {
width: var(--spacing); width: var(--spacing) !important;
} }
.scroll-container::-webkit-scrollbar-thumb { .scroll-container::-webkit-scrollbar-thumb {
background-color: var(--color-secondarylighter80); background-color: var(--color-secondarylighter);
} }
.scroll-container::-webkit-scrollbar-track { .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);
}
}
} }
} }

View 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;
}
}

View file

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/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 Stream from '../src/routes/Detail/StreamsList/Stream';
import Video from '../src/routes/Detail/VideosList/Video'; import Video from '../src/routes/Detail/VideosList/Video';
import VideosList from '../src/routes/Detail/VideosList'; import VideosList from '../src/routes/Detail/VideosList';
@ -42,7 +43,6 @@ storiesOf('Addon', module)
isInstalled={false} isInstalled={false}
types={['Movies', 'Series']} types={['Movies', 'Series']}
description={'Find where to stream your favourite movies and shows amongst iTunes, Hulu, Amazon and other UK/US services.'} 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> </div>
)) ))
@ -54,7 +54,6 @@ storiesOf('Addon', module)
isInstalled={true} isInstalled={true}
types={['Movies', 'Series']} types={['Movies', 'Series']}
description={'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.'} 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> </div>
)) ))
@ -67,7 +66,6 @@ storiesOf('Addon', module)
isInstalled={true} isInstalled={true}
types={['Channels', 'Videos']} 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.'} 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> </div>
)) ))
@ -79,7 +77,6 @@ storiesOf('Addon', module)
isInstalled={false} isInstalled={false}
types={['Movies', 'Series']} types={['Movies', 'Series']}
description={'Watch your favourite YouTube channels ad-free and get notified when they upload new videos.'} 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> </div>
)); ));