addons filter dropdowns implemented

This commit is contained in:
NikolaBorislavovHristov 2019-09-16 15:56:36 +03:00
parent 17a42d6947
commit e113f7acb6
4 changed files with 107 additions and 360 deletions

View file

@ -1,189 +1,27 @@
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 React = require('react');
const Icon = require('stremio-icons/dom');
const { Button, Dropdown, NavBar } = require('stremio/common');
const useAddons = require('./useAddons');
const styles = require('./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;
}
renderAddonPlaceholders() {
const addonPlaceholders = [];
for (let placeholderNumber = 0; placeholderNumber < 20; placeholderNumber++) {
addonPlaceholders.push(
<div key={placeholderNumber} className={styles['placeholder']}>
<div className={styles['logo-placeholder']}>
<div className={styles['logo']} />
</div>
<div className={styles['text-placeholder']}>
<div className={styles['header-placeholder']}>
<div className={styles['name']} />
<div className={styles['version']} />
</div>
<div className={styles['types-placeholder']} />
<div className={styles['hostname-placeholder']} />
<div className={styles['description-placeholder']} />
</div>
<div className={styles['buttons-placeholder']}>
<div className={styles['button']} />
<div className={styles['button']} />
</div>
</div>
)
}
return addonPlaceholders;
}
render() {
return (
<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.length === 0
?
this.renderAddonPlaceholders()
:
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}
/>
)
}
const Addons = ({ urlParams }) => {
const [addons, dropdowns] = useAddons(urlParams.category, urlParams.type);
return (
<div className={styles['addons-container']}>
<NavBar className={styles['nav-bar']} backButton={true} title={'Addons'} />
<div className={styles['addons-content']}>
<div className={styles['top-bar-container']}>
<Button className={styles['add-button-container']} title={'Add addon'}>
<Icon className={styles['icon']} icon={'ic_plus'} />
<div className={styles['add-button-label']}>Add addon</div>
</Button>
{dropdowns.map((dropdown) => (
<Dropdown {...dropdown} key={dropdown.name} className={styles['dropdown']} />
))}
</div>
</div>
);
}
}
Addons.propTypes = {
addons: PropTypes.arrayOf(PropTypes.object).isRequired
};
Addons.defaultProps = {
addons: [
{ logo: '', name: '', version: '', isOfficial: true, isInstalled: false, types: [], hostname: '', description: '' },
{ 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.' }
]
</div>
);
};
export default Addons;
module.exports = Addons;

View file

@ -1,3 +1,3 @@
import Addons from './Addons';
const Addons = require('./Addons');
export default Addons;
module.exports = Addons;

View file

@ -1,201 +1,63 @@
.addons-container {
--addon-width: 960px;
--label-width: 196px;
--label-height: 44px;
--spacing: 8px;
--button-border-width: 2px;
font-size: 14px;
}
.addons-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
background-color: var(--color-background);
.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);
}
}
.nav-bar {
flex: none;
align-self: stretch;
}
.scroll-container {
.addons-content {
flex: 1;
align-self: stretch;
padding: 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;
.placeholder {
.top-bar-container {
display: flex;
flex-direction: row;
position: relative;
z-index: 0;
width: var(--addon-width);
padding: 1.6em;
overflow: hidden;
background-color: var(--color-backgroundlighter);
margin: 2rem;
.logo-placeholder {
flex: 1;
background-color: var(--color-secondarylighter20);
.dropdown, .add-button-container {
flex-grow: 0;
flex-shrink: 1;
flex-basis: 17rem;
height: 3rem;
.logo {
width: calc(0.12 * var(--addon-width));
height: calc(0.12 * var(--addon-width));
&:not(:last-child) {
margin-right: 1rem;
}
}
.text-placeholder {
flex: 5;
margin-left: 3em;
.add-button-container {
flex-basis: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
flex-direction: row;
align-items: center;
max-width: 17rem;
padding: 0 1rem;
background-color: var(--color-signal5);
.header-placeholder {
height: 15%;
display: flex;
flex-direction: row;
justify-content: space-between;
.name {
flex: 2;
margin-right: 3em;
background-color: var(--color-secondarylighter20);
}
.version {
flex: 1;
background-color: var(--color-secondarylighter20);
}
&:hover {
filter: brightness(1.1);
}
.types-placeholder, .hostname-placeholder, .description-placeholder {
height: 15%;
background-color: var(--color-secondarylighter20);
}
}
.buttons-placeholder {
flex: 4;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
.button {
width: 65%;
height: calc(0.05 * var(--addon-width));
background-color: var(--color-secondarylighter20);
}
}
&:after {
content: " ";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 50%;
width: 500%;
margin-left: -250%;
animation: placeholderAnimation 1s linear infinite;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 42%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0) 58%);
}
@keyframes placeholderAnimation {
0% {
transform: translate3d(-30%, 0, 0);
.icon {
flex: none;
width: 1.5rem;
height: 1.5rem;
margin-right: 1rem;
fill: var(--color-surfacelighter);
}
100% {
transform: translate3d(30%, 0, 0);
.add-button-label {
flex: 1;
max-height: 2.4em;
font-size: 1.1rem;
color: var(--color-surfacelighter);
}
}
&:nth-child(n+8) {
display: none;
}
}
>:not(:first-child) {
margin-top: calc(var(--spacing) * 2.5 + var(--button-border-width));
}
}
}

View file

@ -0,0 +1,47 @@
const React = require('react');
const CATEGORIES = ['official', 'community', 'my'];
const DEFAULT_CATEGORY = 'community';
const DEFAULT_TYPE = 'all';
const useAddons = (category, type) => {
category = CATEGORIES.includes(category) ? category : DEFAULT_CATEGORY;
type = typeof type === 'string' && type.length > 0 ? type : DEFAULT_TYPE;
const onSelect = React.useCallback((event) => {
const { name, value } = event.currentTarget.dataset;
if (name === 'category') {
const nextCategory = CATEGORIES.includes(value) ? value : '';
window.location.replace(`#/addons/${nextCategory}/${type}`);
} else if (name === 'type') {
const nextType = typeof value === 'string' ? value : '';
window.location.replace(`#/addons/${category}/${nextType}`);
}
}, [category, type]);
const categoryDropdown = React.useMemo(() => {
const selected = CATEGORIES.includes(category) ? [category] : [];
const options = CATEGORIES
.map((category) => ({ label: category, value: category }));
return {
name: 'category',
selected,
options,
onSelect
};
}, [category, onSelect]);
const typeDropdown = React.useMemo(() => {
const selected = typeof type === 'string' && type.length > 0 ? [type] : [];
const options = ['all', 'movie', 'series', 'channel']
.concat(selected)
.filter((type, index, types) => types.indexOf(type) === index)
.map((type) => ({ label: type, value: type }));
return {
name: 'type',
selected,
options,
onSelect
};
}, [type, onSelect]);
return [[], [categoryDropdown, typeDropdown]];
};
module.exports = useAddons;