diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index b39a12c9f..dd2828ee7 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -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( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ) - } - - return addonPlaceholders; - } - - render() { - return ( -
-
-
- -
Add new Add-On
-
- {Object.keys(ADDON_CATEGORIES).map((category) => -
- {category} -
- )} -
- All -
- {this.getAddonTypes().map((type) => -
- {type} -
- )} -
-
- { - 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) => - - ) - } +const Addons = ({ urlParams }) => { + const [addons, dropdowns] = useAddons(urlParams.category, urlParams.type); + return ( +
+ +
+
+ + {dropdowns.map((dropdown) => ( + + ))}
- ); - } -} - -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.' } - ] +
+ ); }; -export default Addons; +module.exports = Addons; diff --git a/src/routes/Addons/index.js b/src/routes/Addons/index.js index 284509fdc..234a37cdb 100644 --- a/src/routes/Addons/index.js +++ b/src/routes/Addons/index.js @@ -1,3 +1,3 @@ -import Addons from './Addons'; +const Addons = require('./Addons'); -export default Addons; +module.exports = Addons; diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index 315baef0c..37b2c4153 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -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)); } } } \ No newline at end of file diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js new file mode 100644 index 000000000..04187d2ec --- /dev/null +++ b/src/routes/Addons/useAddons.js @@ -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;