From e8659cce32660d6ec7b8e6a5ff612c60d302461e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 17 Oct 2019 17:29:43 +0300 Subject: [PATCH 01/39] useAddons hook demo items replaced with data from the state container --- src/routes/Addons/useAddons.js | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 3435c0dae..1c37b52f6 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -1,4 +1,5 @@ const React = require('react'); +const { useServices } = require('stremio/services'); const CATEGORIES = ['official', 'community', 'my']; const DEFAULT_CATEGORY = 'community'; @@ -7,33 +8,25 @@ 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 addons = React.useMemo(() => { - return [ - { - id: 'com.linvo.cinemeta', - name: 'Cinemeta', - description: 'The official add-on for movie and series catalogs', - types: ['movie', 'series'], - version: '2.12.1', - transportUrl: 'https://v3-cinemeta.strem.io/manifest.json', - installed: true, - official: true - }, - { - id: 'com.linvo.cinemeta2', - name: 'Cinemeta2', - logo: '/images/intro_background.jpg', - description: 'The official add-on for movie and series catalogs', - types: ['movie', 'series'], - version: '2.12.2', - transportUrl: 'https://v2-cinemeta.strem.io/manifest.json', - installed: false, - official: false - } - ]; + const [addons, setAddons] = React.useState([]); + const { core } = useServices(); + React.useEffect(() => { + const onNewState = () => { + const state = core.getState(); + setAddons(state.ctx.content.addons); + }; + core.on('NewModel', onNewState); + core.dispatch({ + action: 'LoadCtx' + }); + onNewState(); + return () => { + core.off('NewModel', onNewState); + }; }, []); const onSelect = React.useCallback((event) => { - const { name, value } = event.currentTarget.dataset; + const { value } = event.reactEvent.currentTarget.dataset; + const name = event.dataset.name; if (name === 'category') { const nextCategory = CATEGORIES.includes(value) ? value : ''; window.location.replace(`#/addons/${nextCategory}/${type}`); @@ -47,7 +40,7 @@ const useAddons = (category, type) => { const options = CATEGORIES .map((category) => ({ label: category, value: category })); return { - name: 'category', + 'data-name': 'category', selected, options, onSelect @@ -60,7 +53,7 @@ const useAddons = (category, type) => { .filter((type, index, types) => types.indexOf(type) === index) .map((type) => ({ label: type, value: type })); return { - name: 'type', + 'data-name': 'type', selected, options, onSelect From fc89a083eadbf1d4bc54e3baf27f8e3185a48d16 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 11:40:40 +0300 Subject: [PATCH 02/39] Addons adapted to useAddons hook changes --- src/routes/Addons/Addons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 5146bc713..602aac2e1 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -48,9 +48,9 @@ const Addons = ({ urlParams, queryParams }) => {
{ - addons.filter(({ name }) => query.length === 0 || (typeof name === 'string' && name.includes(query))) + addons.filter((addon) => query.length === 0 || (typeof addon.manifest.name === 'string' && addon.manifest.name.includes(query))) .map((addon) => ( - + )) }
From 4f37dc09f90cadfd6c91fcc89bca9cf576068eed Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 13:31:46 +0300 Subject: [PATCH 03/39] Multiselect instead of Dropdown used --- src/routes/Addons/Addons.js | 6 +++--- src/routes/Addons/styles.less | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 602aac2e1..93e0ccef8 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -1,7 +1,7 @@ const React = require('react'); const Icon = require('stremio-icons/dom'); const { Modal } = require('stremio-router'); -const { Button, Dropdown, NavBar, TextInput } = require('stremio/common'); +const { Button, Multiselect, NavBar, TextInput } = require('stremio/common'); const Addon = require('./Addon'); const AddonPrompt = require('./AddonPrompt'); const useAddons = require('./useAddons'); @@ -32,8 +32,8 @@ const Addons = ({ urlParams, queryParams }) => {
Add addon
- {dropdowns.map((dropdown) => ( - + {dropdowns.map((dropdown, index) => ( + ))} -
+
{ - addons.filter((addon) => query.length === 0 || (typeof addon.manifest.name === 'string' && addon.manifest.name.includes(query))) + addons.filter((addon) => query.length === 0 || + ((typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(query.toLowerCase())) || + (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) + )) .map((addon) => ( )) From 8255c7fd20c00ee9cbe6e295618f7ef3d34f012a Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 16:54:41 +0300 Subject: [PATCH 05/39] return to default type when category changes --- src/routes/Addons/useAddons.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 1c37b52f6..b412fb6d8 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -16,9 +16,6 @@ const useAddons = (category, type) => { setAddons(state.ctx.content.addons); }; core.on('NewModel', onNewState); - core.dispatch({ - action: 'LoadCtx' - }); onNewState(); return () => { core.off('NewModel', onNewState); @@ -29,7 +26,7 @@ const useAddons = (category, type) => { const name = event.dataset.name; if (name === 'category') { const nextCategory = CATEGORIES.includes(value) ? value : ''; - window.location.replace(`#/addons/${nextCategory}/${type}`); + window.location.replace(`#/addons/${nextCategory}/${DEFAULT_TYPE}`); } else if (name === 'type') { const nextType = typeof value === 'string' ? value : ''; window.location.replace(`#/addons/${category}/${nextType}`); From 1ef7244a395b54d7183bc5386015996d90d8f202 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 17:39:57 +0300 Subject: [PATCH 06/39] Addon added to storybook --- storybook/stories/Addon/InstalledAddon.js | 16 +++++++++++++++- storybook/stories/Addon/NotInstalledAddon.js | 16 +++++++++++++++- storybook/stories/Addon/styles.less | 3 +++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 storybook/stories/Addon/styles.less diff --git a/storybook/stories/Addon/InstalledAddon.js b/storybook/stories/Addon/InstalledAddon.js index 65970946a..24400fdd7 100644 --- a/storybook/stories/Addon/InstalledAddon.js +++ b/storybook/stories/Addon/InstalledAddon.js @@ -1,6 +1,20 @@ const React = require('react'); const { storiesOf } = require('@storybook/react'); +const { action } = require('@storybook/addon-actions'); +const Addon = require('stremio/routes/Addons/Addon'); +const styles = require('./styles'); storiesOf('Addon', module).add('Installed', () => ( -
Installed addon
+ )); diff --git a/storybook/stories/Addon/NotInstalledAddon.js b/storybook/stories/Addon/NotInstalledAddon.js index 9c9a6a8de..5d5130198 100644 --- a/storybook/stories/Addon/NotInstalledAddon.js +++ b/storybook/stories/Addon/NotInstalledAddon.js @@ -1,6 +1,20 @@ const React = require('react'); const { storiesOf } = require('@storybook/react'); +const { action } = require('@storybook/addon-actions'); +const Addon = require('stremio/routes/Addons/Addon'); +const styles = require('./styles'); storiesOf('Addon', module).add('NotInstalled', () => ( -
Not installed addon
+ )); diff --git a/storybook/stories/Addon/styles.less b/storybook/stories/Addon/styles.less new file mode 100644 index 000000000..f300a05f0 --- /dev/null +++ b/storybook/stories/Addon/styles.less @@ -0,0 +1,3 @@ +.installed-addon-container, .not-installed-addon-container { + margin: 10px; +} \ No newline at end of file From 9ce3e28def4f3a0418c0a9e732eb26143db9806f Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 21 Oct 2019 13:57:45 +0300 Subject: [PATCH 07/39] filter addons in useAddons hook --- src/routes/Addons/useAddons.js | 86 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index b412fb6d8..832b80a9b 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -6,57 +6,63 @@ 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 [addons, setAddons] = React.useState([]); const { core } = useServices(); + const [addons, setAddons] = React.useState([[], []]); React.useEffect(() => { + category = CATEGORIES.includes(category) ? category : DEFAULT_CATEGORY; + type = typeof type === 'string' && type.length > 0 ? type : DEFAULT_TYPE; + const onSelect = (event) => { + const { value } = event.reactEvent.currentTarget.dataset; + const name = event.dataset.name; + if (name === 'category') { + const nextCategory = CATEGORIES.includes(value) ? value : ''; + window.location.replace(`#/addons/${nextCategory}/${DEFAULT_TYPE}`); + } else if (name === 'type') { + const nextType = typeof value === 'string' ? value : ''; + window.location.replace(`#/addons/${category}/${nextType}`); + } + }; + const categoryDropdown = () => { + const selected = CATEGORIES.includes(category) ? [category] : []; + const options = CATEGORIES + .map((category) => ({ label: category, value: category })); + return { + 'data-name': 'category', + selected, + options, + onSelect + }; + }; + const typeDropdown = () => { + 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 { + 'data-name': 'type', + selected, + options, + onSelect + }; + }; const onNewState = () => { const state = core.getState(); - setAddons(state.ctx.content.addons); + setAddons([ + state.ctx.content.addons.filter((addon) => { + return (categoryDropdown().selected.join('') === 'official' ? addon.flags.official : !addon.flags.official) && + (typeDropdown().selected.join('') === 'all' || addon.manifest.types.includes(typeDropdown().selected.join(''))); + }), + [categoryDropdown(), typeDropdown()] + ]); }; core.on('NewModel', onNewState); onNewState(); return () => { core.off('NewModel', onNewState); }; - }, []); - const onSelect = React.useCallback((event) => { - const { value } = event.reactEvent.currentTarget.dataset; - const name = event.dataset.name; - if (name === 'category') { - const nextCategory = CATEGORIES.includes(value) ? value : ''; - window.location.replace(`#/addons/${nextCategory}/${DEFAULT_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 { - 'data-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 { - 'data-name': 'type', - selected, - options, - onSelect - }; - }, [type, onSelect]); - return [addons, [categoryDropdown, typeDropdown]]; + return addons; }; module.exports = useAddons; From 0c4618b919ec49e9968953526d07e2304e513038 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 21 Oct 2019 15:26:47 +0300 Subject: [PATCH 08/39] addons types linked with stremio-core --- src/routes/Addons/useAddons.js | 74 ++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 832b80a9b..5a4bcd9d4 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -11,43 +11,47 @@ const useAddons = (category, type) => { React.useEffect(() => { category = CATEGORIES.includes(category) ? category : DEFAULT_CATEGORY; type = typeof type === 'string' && type.length > 0 ? type : DEFAULT_TYPE; - const onSelect = (event) => { - const { value } = event.reactEvent.currentTarget.dataset; - const name = event.dataset.name; - if (name === 'category') { - const nextCategory = CATEGORIES.includes(value) ? value : ''; - window.location.replace(`#/addons/${nextCategory}/${DEFAULT_TYPE}`); - } else if (name === 'type') { - const nextType = typeof value === 'string' ? value : ''; - window.location.replace(`#/addons/${category}/${nextType}`); - } - }; - const categoryDropdown = () => { - const selected = CATEGORIES.includes(category) ? [category] : []; - const options = CATEGORIES - .map((category) => ({ label: category, value: category })); - return { - 'data-name': 'category', - selected, - options, - onSelect - }; - }; - const typeDropdown = () => { - 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 { - 'data-name': 'type', - selected, - options, - onSelect - }; - }; const onNewState = () => { const state = core.getState(); + const onSelect = (event) => { + const { value } = event.reactEvent.currentTarget.dataset; + const name = event.dataset.name; + if (name === 'category') { + const nextCategory = CATEGORIES.includes(value) ? value : ''; + window.location.replace(`#/addons/${nextCategory}/${DEFAULT_TYPE}`); + } else if (name === 'type') { + const nextType = typeof value === 'string' ? value : ''; + window.location.replace(`#/addons/${category}/${nextType}`); + } + }; + const categoryDropdown = () => { + const selected = CATEGORIES.includes(category) ? [category] : []; + const options = CATEGORIES + .map((category) => ({ label: category, value: category })); + return { + 'data-name': 'category', + selected, + options, + onSelect + }; + }; + const typeDropdown = () => { + const selected = typeof type === 'string' && type.length > 0 ? [type] : []; + const options = [...new Set( + [].concat.apply([], + ['all'].concat( + state.ctx.content.addons.map(addon => addon.manifest.types), + selected + ) + ))] + .map((type) => ({ label: type, value: type })); + return { + 'data-name': 'type', + selected, + options, + onSelect + }; + }; setAddons([ state.ctx.content.addons.filter((addon) => { return (categoryDropdown().selected.join('') === 'official' ? addon.flags.official : !addon.flags.official) && From 9f50b545b73dcf5558beb426b6229531d841ec58 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 21 Oct 2019 15:40:01 +0300 Subject: [PATCH 09/39] spread syntax instead of concat.apply --- src/routes/Addons/useAddons.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 5a4bcd9d4..c678c8169 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -38,12 +38,11 @@ const useAddons = (category, type) => { const typeDropdown = () => { const selected = typeof type === 'string' && type.length > 0 ? [type] : []; const options = [...new Set( - [].concat.apply([], ['all'].concat( - state.ctx.content.addons.map(addon => addon.manifest.types), + ...state.ctx.content.addons.map(addon => addon.manifest.types), selected ) - ))] + )] .map((type) => ({ label: type, value: type })); return { 'data-name': 'type', From 82c7e09688a86c65f8487a1e4da6a687962be2b2 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 22 Oct 2019 11:40:42 +0300 Subject: [PATCH 10/39] use index instead of addon id --- src/routes/Addons/Addons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 2576cdf45..e85fad59d 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -52,8 +52,8 @@ const Addons = ({ urlParams, queryParams }) => { ((typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(query.toLowerCase())) || (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) )) - .map((addon) => ( - + .map((addon, index) => ( + )) }
From 836a2e1ad7ce60ba50ffe59bfcb92ad7b2fe1d4e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Wed, 23 Oct 2019 16:09:04 +0300 Subject: [PATCH 11/39] addons adapted to stremio-core changes --- src/common/routesRegexp.js | 2 +- src/routes/Addons/Addons.js | 2 +- src/routes/Addons/useAddons.js | 109 +++++++++++++++++---------------- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/common/routesRegexp.js b/src/common/routesRegexp.js index 7d66a0566..c91fd7f31 100644 --- a/src/common/routesRegexp.js +++ b/src/common/routesRegexp.js @@ -25,7 +25,7 @@ const routesRegexp = { }, addons: { regexp: /^\/addons(?:\/([^\/]*?))?(?:\/([^\/]*?))?\/?$/i, - urlParamsNames: ['category', 'type'] + urlParamsNames: ['type', 'category'] }, settings: { regexp: /^\/settings\/?$/i, diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index e85fad59d..93a1630dc 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -13,7 +13,7 @@ const Addons = ({ urlParams, queryParams }) => { const queryOnChange = React.useCallback((event) => { setQuery(event.currentTarget.value); }, []); - const [addons, dropdowns] = useAddons(urlParams.category, urlParams.type); + const [addons, dropdowns] = useAddons(urlParams, queryParams); const [selectedAddon, clearSelectedAddon] = useSelectedAddon(queryParams.get('addon')); const addonPromptModalBackgroundOnClick = React.useCallback((event) => { if (!event.nativeEvent.clearSelectedAddonPrevented) { diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index c678c8169..fc1e3764e 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -1,70 +1,75 @@ const React = require('react'); const { useServices } = require('stremio/services'); -const CATEGORIES = ['official', 'community', 'my']; -const DEFAULT_CATEGORY = 'community'; -const DEFAULT_TYPE = 'all'; +const DEFAULT_TYPE = 'movie'; +const DEFAULT_CATEGORY = 'thirdparty'; -const useAddons = (category, type) => { +const useAddons = (urlParams, queryParams) => { const { core } = useServices(); const [addons, setAddons] = React.useState([[], []]); React.useEffect(() => { - category = CATEGORIES.includes(category) ? category : DEFAULT_CATEGORY; - type = typeof type === 'string' && type.length > 0 ? type : DEFAULT_TYPE; + const type = typeof urlParams.type === 'string' && urlParams.type.length > 0 ? urlParams.type : DEFAULT_TYPE; + const category = typeof urlParams.category === 'string' && urlParams.category.length > 0 ? urlParams.category : DEFAULT_CATEGORY; const onNewState = () => { const state = core.getState(); - const onSelect = (event) => { - const { value } = event.reactEvent.currentTarget.dataset; - const name = event.dataset.name; - if (name === 'category') { - const nextCategory = CATEGORIES.includes(value) ? value : ''; - window.location.replace(`#/addons/${nextCategory}/${DEFAULT_TYPE}`); - } else if (name === 'type') { - const nextType = typeof value === 'string' ? value : ''; - window.location.replace(`#/addons/${category}/${nextType}`); - } - }; - const categoryDropdown = () => { - const selected = CATEGORIES.includes(category) ? [category] : []; - const options = CATEGORIES - .map((category) => ({ label: category, value: category })); - return { - 'data-name': 'category', - selected, - options, - onSelect - }; - }; - const typeDropdown = () => { - const selected = typeof type === 'string' && type.length > 0 ? [type] : []; - const options = [...new Set( - ['all'].concat( - ...state.ctx.content.addons.map(addon => addon.manifest.types), - selected - ) - )] - .map((type) => ({ label: type, value: type })); - return { + const selectInputs = [ + { 'data-name': 'type', - selected, - options, - onSelect - }; - }; - setAddons([ - state.ctx.content.addons.filter((addon) => { - return (categoryDropdown().selected.join('') === 'official' ? addon.flags.official : !addon.flags.official) && - (typeDropdown().selected.join('') === 'all' || addon.manifest.types.includes(typeDropdown().selected.join(''))); - }), - [categoryDropdown(), typeDropdown()] - ]); + selected: state.addons.types + .filter(({ is_selected }) => is_selected) + .map(({ load }) => JSON.stringify(load)), + options: state.addons.types + .map(({ type_name, load }) => ({ + value: JSON.stringify(load), + label: type_name + })), + onSelect: (event) => { + const load = JSON.parse(event.reactEvent.currentTarget.dataset.value); + window.location = `#/addons/${encodeURIComponent(load.path.type_name)}/${encodeURIComponent(load.path.id)}`; + } + }, + { + 'data-name': 'category', + selected: state.addons.catalogs + .filter(({ is_selected }) => is_selected) + .map(({ load }) => JSON.stringify(load)), + options: state.addons.catalogs + .filter(({ load: { path: { type_name } } }) => { + return type_name === type; + }) + .map(({ name, load }) => ({ + value: JSON.stringify(load), + label: name + })), + onSelect: (event) => { + const load = JSON.parse(event.reactEvent.currentTarget.dataset.value); + window.location = `#/addons/${encodeURIComponent(load.path.type_name)}/${encodeURIComponent(load.path.id)}` + } + } + ]; + const addonsItems = state.addons.content.type === 'Ready' ? state.addons.content.content : []; + setAddons([addonsItems, selectInputs]); }; core.on('NewModel', onNewState); - onNewState(); + core.dispatch({ + action: 'Load', + args: { + load: 'CatalogFiltered', + args: { + base: 'https://v3-cinemeta.strem.io/manifest.json', + path: { + resource: 'addon_catalog', + type_name: type, + id: category, + extra: [] // TODO + } + } + } + }); return () => { core.off('NewModel', onNewState); }; - }, [category, type]); + }, [urlParams, queryParams]); return addons; }; From 3de87ce5bddd7bcc4b2f8dad4e57f05e5339e285 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 24 Oct 2019 17:21:12 +0300 Subject: [PATCH 12/39] useRouteFocused hook instead of useFocusable --- src/routes/Addons/AddonPrompt/AddonPrompt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/AddonPrompt/AddonPrompt.js b/src/routes/Addons/AddonPrompt/AddonPrompt.js index a5511c71e..da722eb87 100644 --- a/src/routes/Addons/AddonPrompt/AddonPrompt.js +++ b/src/routes/Addons/AddonPrompt/AddonPrompt.js @@ -2,12 +2,12 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { useFocusable } = require('stremio-router'); +const { useRouteFocused } = require('stremio-router'); const { Button } = require('stremio/common'); const styles = require('./styles'); const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel }) => { - const focusable = useFocusable(); + const focusable = useRouteFocused(); React.useEffect(() => { const onKeyUp = (event) => { if (event.key === 'Escape') { From 30c3e4b6ed7cbc9fd6158c8028eb8f33bb206d78 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 24 Oct 2019 17:44:31 +0300 Subject: [PATCH 13/39] check for search and queryParams --- src/routes/Addons/useSelectedAddon.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index 501b1754e..7b6a15813 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -14,14 +14,20 @@ const useSelectedAddon = (transportUrl) => { fetch(transportUrl) .then((resp) => resp.json()) - .then((manifest) => setAddon({ ...manifest, transportUrl })); + .then((manifest) => setAddon({ manifest, transportUrl, flags: {} })); }, [transportUrl]); const clear = React.useCallback(() => { if (active) { const { pathname, search } = UrlUtils.parse(locationHash.slice(1)); const queryParams = new URLSearchParams(search); queryParams.delete('addon'); - window.location.replace(`#${pathname}?${queryParams.toString()}`); + if (search && queryParams) { + window.location.replace(`#${pathname}?${queryParams.toString()}`); + } + else { + window.location.replace(`#${pathname}`); + } + setAddon(null); } }, [active]); return [addon, clear]; From 7ac98e1ad2d34c528943ba52535e0a1f8226842e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 24 Oct 2019 17:45:12 +0300 Subject: [PATCH 14/39] locationHash added to callback dependencies --- src/routes/Addons/useSelectedAddon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index 7b6a15813..e770cc5b8 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -29,7 +29,7 @@ const useSelectedAddon = (transportUrl) => { } setAddon(null); } - }, [active]); + }, [active, locationHash]); return [addon, clear]; }; From 29260cafaa6cc2b8e2091feeeac94daa695e237a Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 25 Oct 2019 12:22:06 +0300 Subject: [PATCH 15/39] open/close AddonPrompt fixed --- src/routes/Addons/AddonPrompt/AddonPrompt.js | 6 +++--- src/routes/Addons/Addons.js | 8 ++++---- src/routes/Addons/useSelectedAddon.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/routes/Addons/AddonPrompt/AddonPrompt.js b/src/routes/Addons/AddonPrompt/AddonPrompt.js index da722eb87..34938be38 100644 --- a/src/routes/Addons/AddonPrompt/AddonPrompt.js +++ b/src/routes/Addons/AddonPrompt/AddonPrompt.js @@ -6,7 +6,7 @@ const { useRouteFocused } = require('stremio-router'); const { Button } = require('stremio/common'); const styles = require('./styles'); -const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel }) => { +const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel, onClick }) => { const focusable = useRouteFocused(); React.useEffect(() => { const onKeyUp = (event) => { @@ -22,7 +22,7 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, }; }, [cancel, focusable]); return ( -
+
@@ -104,7 +104,7 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, }
-
{ selectedAddon !== null ? -
- +
+
: diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index e770cc5b8..c0937277d 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -30,7 +30,7 @@ const useSelectedAddon = (transportUrl) => { setAddon(null); } }, [active, locationHash]); - return [addon, clear]; + return [addon, clear, setAddon]; }; module.exports = useSelectedAddon; From 01303a742a626234a81fd07d110af9a6c53a1e60 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 28 Oct 2019 12:01:40 +0200 Subject: [PATCH 16/39] install/uninstall addon works --- src/routes/Addons/AddonPrompt/AddonPrompt.js | 8 +++++--- src/routes/Addons/Addons.js | 10 +++++++--- src/routes/Addons/useAddons.js | 21 +++++++++++++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/routes/Addons/AddonPrompt/AddonPrompt.js b/src/routes/Addons/AddonPrompt/AddonPrompt.js index 34938be38..4f3b6dfaf 100644 --- a/src/routes/Addons/AddonPrompt/AddonPrompt.js +++ b/src/routes/Addons/AddonPrompt/AddonPrompt.js @@ -6,7 +6,7 @@ const { useRouteFocused } = require('stremio-router'); const { Button } = require('stremio/common'); const styles = require('./styles'); -const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel, onClick }) => { +const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel, onClick, toggle }) => { const focusable = useRouteFocused(); React.useEffect(() => { const onKeyUp = (event) => { @@ -107,7 +107,7 @@ const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, -
@@ -129,7 +129,9 @@ AddonPrompt.propTypes = { transportUrl: PropTypes.string, installed: PropTypes.bool, official: PropTypes.bool, - cancel: PropTypes.func + cancel: PropTypes.func, + onClick: PropTypes.func, + toggle: PropTypes.func }; module.exports = AddonPrompt; diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 576147ea0..206e8cba1 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -13,7 +13,7 @@ const Addons = ({ urlParams, queryParams }) => { const queryOnChange = React.useCallback((event) => { setQuery(event.currentTarget.value); }, []); - const [addons, dropdowns] = useAddons(urlParams, queryParams); + const [addons, dropdowns, installSelectedAddon, uninstallSelectedAddon, installedAddons] = useAddons(urlParams, queryParams); const [selectedAddon, clearSelectedAddon, setSelectedAddon] = useSelectedAddon(queryParams.get('addon')); const addonPromptModalBackgroundOnClick = React.useCallback((event) => { if (!event.nativeEvent.clearSelectedAddonPrevented) { @@ -23,6 +23,10 @@ const Addons = ({ urlParams, queryParams }) => { const addonPromptOnClick = React.useCallback((event) => { event.nativeEvent.clearSelectedAddonPrevented = true; }, []); + const setInstalledAddon = React.useCallback((currentAddon) => { + return installedAddons.some((installedAddon) => installedAddon.manifest.id === currentAddon.manifest.id && + installedAddon.transportUrl === currentAddon.transportUrl); + }, [installedAddons]); return (
@@ -53,7 +57,7 @@ const Addons = ({ urlParams, queryParams }) => { (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) )) .map((addon, index) => ( - setSelectedAddon(addon)} /> + setSelectedAddon(addon)} /> )) }
@@ -61,7 +65,7 @@ const Addons = ({ urlParams, queryParams }) => { selectedAddon !== null ?
- + setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} />
: diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index fc1e3764e..b8a19066e 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -7,6 +7,24 @@ const DEFAULT_CATEGORY = 'thirdparty'; const useAddons = (urlParams, queryParams) => { const { core } = useServices(); const [addons, setAddons] = React.useState([[], []]); + const installAddon = React.useCallback(descriptor => + core.dispatch({ + action: 'AddonOp', + args: { + addonOp: 'Install', + args: descriptor + } + }), []); + const uninstallAddon = React.useCallback(descriptor => + core.dispatch({ + action: 'AddonOp', + args: { + addonOp: 'Remove', + args: { + transport_url: descriptor.transportUrl + } + } + }), []); React.useEffect(() => { const type = typeof urlParams.type === 'string' && urlParams.type.length > 0 ? urlParams.type : DEFAULT_TYPE; const category = typeof urlParams.category === 'string' && urlParams.category.length > 0 ? urlParams.category : DEFAULT_CATEGORY; @@ -48,7 +66,8 @@ const useAddons = (urlParams, queryParams) => { } ]; const addonsItems = state.addons.content.type === 'Ready' ? state.addons.content.content : []; - setAddons([addonsItems, selectInputs]); + const installedAddons = state.ctx.is_loaded ? state.ctx.content.addons : []; + setAddons([addonsItems, selectInputs, installAddon, uninstallAddon, installedAddons]); }; core.on('NewModel', onNewState); core.dispatch({ From 307d0c571416a73d914904602416d64692f747e0 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 28 Oct 2019 17:13:31 +0200 Subject: [PATCH 17/39] 'my' category added --- src/routes/Addons/useAddons.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index b8a19066e..f45267c4c 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -30,6 +30,25 @@ const useAddons = (urlParams, queryParams) => { const category = typeof urlParams.category === 'string' && urlParams.category.length > 0 ? urlParams.category : DEFAULT_CATEGORY; const onNewState = () => { const state = core.getState(); + const myAddons = [...new Set( + [].concat(...state.ctx.content.addons.map(addon => addon.manifest.types)) + )] + .map((type) => ( + { + is_selected: urlParams.category === 'my', + name: 'my', + load: { + base: 'https://v3-cinemeta.strem.io/manifest.json', + path: { + resource: 'addon_catalog', + type_name: type, + id: 'my', + extra: [] + } + } + }) + ); + myAddons.forEach(addon => state.addons.catalogs.push(addon)); const selectInputs = [ { 'data-name': 'type', @@ -65,8 +84,11 @@ const useAddons = (urlParams, queryParams) => { } } ]; - const addonsItems = state.addons.content.type === 'Ready' ? state.addons.content.content : []; const installedAddons = state.ctx.is_loaded ? state.ctx.content.addons : []; + const addonsItems = urlParams.category === 'my' && state.ctx.is_loaded ? + installedAddons.filter(addon => addon.manifest.types.includes(urlParams.type)) + : + (state.addons.content.type === 'Ready' ? state.addons.content.content : []); setAddons([addonsItems, selectInputs, installAddon, uninstallAddon, installedAddons]); }; core.on('NewModel', onNewState); From f30c463486431d139fbdefe2286dfee21383b970 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 29 Oct 2019 14:04:48 +0200 Subject: [PATCH 18/39] addons selectInputs swapped --- src/common/routesRegexp.js | 2 +- src/routes/Addons/useAddons.js | 37 +++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/common/routesRegexp.js b/src/common/routesRegexp.js index c91fd7f31..7d66a0566 100644 --- a/src/common/routesRegexp.js +++ b/src/common/routesRegexp.js @@ -25,7 +25,7 @@ const routesRegexp = { }, addons: { regexp: /^\/addons(?:\/([^\/]*?))?(?:\/([^\/]*?))?\/?$/i, - urlParamsNames: ['type', 'category'] + urlParamsNames: ['category', 'type'] }, settings: { regexp: /^\/settings\/?$/i, diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index f45267c4c..4931cfbb3 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -35,7 +35,7 @@ const useAddons = (urlParams, queryParams) => { )] .map((type) => ( { - is_selected: urlParams.category === 'my', + is_selected: urlParams.type === type && urlParams.category === 'my', name: 'my', load: { base: 'https://v3-cinemeta.strem.io/manifest.json', @@ -51,36 +51,41 @@ const useAddons = (urlParams, queryParams) => { myAddons.forEach(addon => state.addons.catalogs.push(addon)); const selectInputs = [ { - 'data-name': 'type', - selected: state.addons.types + 'data-name': 'category', + selected: state.addons.catalogs .filter(({ is_selected }) => is_selected) - .map(({ load }) => JSON.stringify(load)), - options: state.addons.types - .map(({ type_name, load }) => ({ - value: JSON.stringify(load), - label: type_name + .map(({ load }) => load.path.id), + options: state.addons.catalogs + .filter((catalog, index, catalogs) => { + return catalogs.map(ctg => ctg.name).indexOf(catalog.name) === index; + }) + .map(({ name, load }) => ({ + value: load.path.id, + label: name })), onSelect: (event) => { - const load = JSON.parse(event.reactEvent.currentTarget.dataset.value); - window.location = `#/addons/${encodeURIComponent(load.path.type_name)}/${encodeURIComponent(load.path.id)}`; + const load = (state.addons.catalogs.find(({ load: { path: { id } } }) => { + return id === event.reactEvent.currentTarget.dataset.value; + }) || {}).load; + window.location = `#/addons/${encodeURIComponent(load.path.id)}/${encodeURIComponent(load.path.type_name)}`; } }, { - 'data-name': 'category', + 'data-name': 'type', selected: state.addons.catalogs .filter(({ is_selected }) => is_selected) .map(({ load }) => JSON.stringify(load)), options: state.addons.catalogs - .filter(({ load: { path: { type_name } } }) => { - return type_name === type; + .filter(({ load: { path: { id } } }) => { + return id === category; }) - .map(({ name, load }) => ({ + .map(({ load }) => ({ value: JSON.stringify(load), - label: name + label: load.path.type_name })), onSelect: (event) => { const load = JSON.parse(event.reactEvent.currentTarget.dataset.value); - window.location = `#/addons/${encodeURIComponent(load.path.type_name)}/${encodeURIComponent(load.path.id)}` + window.location = `#/addons/${encodeURIComponent(load.path.id)}/${encodeURIComponent(load.path.type_name)}`; } } ]; From 53e99910691987259cc0781d4320f15a07276936 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 29 Oct 2019 14:32:06 +0200 Subject: [PATCH 19/39] 'all' type added --- src/routes/Addons/useAddons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 4931cfbb3..e218f0afa 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -31,7 +31,7 @@ const useAddons = (urlParams, queryParams) => { const onNewState = () => { const state = core.getState(); const myAddons = [...new Set( - [].concat(...state.ctx.content.addons.map(addon => addon.manifest.types)) + ['all'].concat(...state.ctx.content.addons.map(addon => addon.manifest.types)) )] .map((type) => ( { @@ -91,7 +91,7 @@ const useAddons = (urlParams, queryParams) => { ]; const installedAddons = state.ctx.is_loaded ? state.ctx.content.addons : []; const addonsItems = urlParams.category === 'my' && state.ctx.is_loaded ? - installedAddons.filter(addon => addon.manifest.types.includes(urlParams.type)) + installedAddons.filter(addon => urlParams.type === 'all' || addon.manifest.types.includes(urlParams.type)) : (state.addons.content.type === 'Ready' ? state.addons.content.content : []); setAddons([addonsItems, selectInputs, installAddon, uninstallAddon, installedAddons]); From eb19a3b38886917b0861d1b59f681f55c554a786 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 29 Oct 2019 16:37:45 +0200 Subject: [PATCH 20/39] check for protected addon --- src/routes/Addons/Addons.js | 2 +- src/routes/Addons/useSelectedAddon.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 206e8cba1..a69ad3cb2 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -62,7 +62,7 @@ const Addons = ({ urlParams, queryParams }) => { }
{ - selectedAddon !== null ? + selectedAddon !== null && !selectedAddon.flags.protected ?
setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} /> diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index c0937277d..ce4f49ec5 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -12,7 +12,7 @@ const useSelectedAddon = (transportUrl) => { return; } - fetch(transportUrl) + fetch(transportUrl) // todo .then((resp) => resp.json()) .then((manifest) => setAddon({ manifest, transportUrl, flags: {} })); }, [transportUrl]); From 67fd80a63ecea3600c86a280d4b69e60e3d8a4b8 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 29 Oct 2019 17:20:09 +0200 Subject: [PATCH 21/39] isProtected property added --- src/routes/Addons/Addon/Addon.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addon/Addon.js b/src/routes/Addons/Addon/Addon.js index 496a27168..2866a4881 100644 --- a/src/routes/Addons/Addon/Addon.js +++ b/src/routes/Addons/Addon/Addon.js @@ -5,7 +5,7 @@ const Icon = require('stremio-icons/dom'); const { Button } = require('stremio/common'); const styles = require('./styles'); -const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, toggle }) => { +const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, isProtected, toggle }) => { const onKeyUp = React.useCallback((event) => { if (event.key === 'Enter' && typeof toggle === 'function') { toggle(event); @@ -52,7 +52,7 @@ const Addon = ({ className, id, name, logo, description, types, version, transpo }
-
{ - selectedAddon !== null && !selectedAddon.flags.protected ? + selectedAddon !== null ?
setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} /> diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index e218f0afa..a40f9a413 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -7,15 +7,16 @@ const DEFAULT_CATEGORY = 'thirdparty'; const useAddons = (urlParams, queryParams) => { const { core } = useServices(); const [addons, setAddons] = React.useState([[], []]); - const installAddon = React.useCallback(descriptor => + const installAddon = React.useCallback(descriptor => { core.dispatch({ action: 'AddonOp', args: { addonOp: 'Install', args: descriptor } - }), []); - const uninstallAddon = React.useCallback(descriptor => + }) + }, []); + const uninstallAddon = React.useCallback(descriptor => { core.dispatch({ action: 'AddonOp', args: { @@ -24,7 +25,8 @@ const useAddons = (urlParams, queryParams) => { transport_url: descriptor.transportUrl } } - }), []); + }) + }, []); React.useEffect(() => { const type = typeof urlParams.type === 'string' && urlParams.type.length > 0 ? urlParams.type : DEFAULT_TYPE; const category = typeof urlParams.category === 'string' && urlParams.category.length > 0 ? urlParams.category : DEFAULT_CATEGORY; @@ -49,6 +51,9 @@ const useAddons = (urlParams, queryParams) => { }) ); myAddons.forEach(addon => state.addons.catalogs.push(addon)); + const selectAddon = (transportUrl) => { + window.location = `#/addons/${category}/${type}?addon=${transportUrl}`; + } const selectInputs = [ { 'data-name': 'category', @@ -64,9 +69,9 @@ const useAddons = (urlParams, queryParams) => { label: name })), onSelect: (event) => { - const load = (state.addons.catalogs.find(({ load: { path: { id } } }) => { + const load = state.addons.catalogs.find(({ load: { path: { id } } }) => { return id === event.reactEvent.currentTarget.dataset.value; - }) || {}).load; + }).load; window.location = `#/addons/${encodeURIComponent(load.path.id)}/${encodeURIComponent(load.path.type_name)}`; } }, @@ -94,7 +99,7 @@ const useAddons = (urlParams, queryParams) => { installedAddons.filter(addon => urlParams.type === 'all' || addon.manifest.types.includes(urlParams.type)) : (state.addons.content.type === 'Ready' ? state.addons.content.content : []); - setAddons([addonsItems, selectInputs, installAddon, uninstallAddon, installedAddons]); + setAddons([addonsItems, selectInputs, selectAddon, installAddon, uninstallAddon, installedAddons]); }; core.on('NewModel', onNewState); core.dispatch({ From bb1a4886c3f9e9e2e58fb77fb9a34010f1ca2e06 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Wed, 30 Oct 2019 15:29:03 +0200 Subject: [PATCH 23/39] open/close SharePrompt works --- src/common/SharePrompt/SharePrompt.js | 15 ++++---- src/routes/Addons/Addon/Addon.js | 7 ++-- src/routes/Addons/Addons.js | 50 +++++++++++++++++++++++---- src/routes/Addons/styles.less | 15 +++++--- src/routes/Addons/useSelectedAddon.js | 2 +- 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/common/SharePrompt/SharePrompt.js b/src/common/SharePrompt/SharePrompt.js index 4a1abc80b..eb0ade0f1 100644 --- a/src/common/SharePrompt/SharePrompt.js +++ b/src/common/SharePrompt/SharePrompt.js @@ -2,14 +2,14 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { useFocusable } = require('stremio-router'); +const { useRouteFocused } = require('stremio-router'); const Button = require('stremio/common/Button'); const TextInput = require('stremio/common/TextInput'); const styles = require('./styles'); -const SharePrompt = ({ className, label, url, close }) => { +const SharePrompt = ({ className, label, url, close, onClick }) => { const inputRef = React.useRef(null); - const focusable = useFocusable(); + const focusable = useRouteFocused(); const copyToClipboard = React.useCallback(() => { inputRef.current.select(); document.execCommand('copy'); @@ -28,9 +28,9 @@ const SharePrompt = ({ className, label, url, close }) => { }; }, [close, focusable]); return ( -
-
{label}
@@ -60,7 +60,8 @@ SharePrompt.propTypes = { className: PropTypes.string, label: PropTypes.string.isRequired, url: PropTypes.string.isRequired, - close: PropTypes.func + close: PropTypes.func, + onClick: PropTypes.func }; module.exports = SharePrompt; diff --git a/src/routes/Addons/Addon/Addon.js b/src/routes/Addons/Addon/Addon.js index 2866a4881..765449bb8 100644 --- a/src/routes/Addons/Addon/Addon.js +++ b/src/routes/Addons/Addon/Addon.js @@ -5,7 +5,7 @@ const Icon = require('stremio-icons/dom'); const { Button } = require('stremio/common'); const styles = require('./styles'); -const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, isProtected, toggle }) => { +const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, isProtected, toggle, onShareButtonClicked }) => { const onKeyUp = React.useCallback((event) => { if (event.key === 'Enter' && typeof toggle === 'function') { toggle(event); @@ -55,7 +55,7 @@ const Addon = ({ className, id, name, logo, description, types, version, transpo - @@ -75,7 +75,8 @@ Addon.propTypes = { transportUrl: PropTypes.string, installed: PropTypes.bool, isProtected: PropTypes.bool, - toggle: PropTypes.func + toggle: PropTypes.func, + onShareButtonClicked: PropTypes.func }; module.exports = Addon; diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 4d8657b59..68e534245 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -1,7 +1,8 @@ const React = require('react'); +const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const { Modal } = require('stremio-router'); -const { Button, Multiselect, NavBar, TextInput } = require('stremio/common'); +const { Button, Multiselect, NavBar, TextInput, SharePrompt } = require('stremio/common'); const Addon = require('./Addon'); const AddonPrompt = require('./AddonPrompt'); const useAddons = require('./useAddons'); @@ -15,12 +16,14 @@ const Addons = ({ urlParams, queryParams }) => { }, []); const [addons, dropdowns, setSelectedAddon, installSelectedAddon, uninstallSelectedAddon, installedAddons] = useAddons(urlParams, queryParams); const [selectedAddon, clearSelectedAddon] = useSelectedAddon(queryParams.get('addon')); - const addonPromptModalBackgroundOnClick = React.useCallback((event) => { + const [sharedAddon, setSharedAddon] = React.useState(null); + const promptModalBackgroundOnClick = React.useCallback((event) => { if (!event.nativeEvent.clearSelectedAddonPrevented) { clearSelectedAddon(); + setSharedAddon(null); } }, []); - const addonPromptOnClick = React.useCallback((event) => { + const promptOnClick = React.useCallback((event) => { event.nativeEvent.clearSelectedAddonPrevented = true; }, []); const setInstalledAddon = React.useCallback((currentAddon) => { @@ -57,15 +60,48 @@ const Addons = ({ urlParams, queryParams }) => { (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) )) .map((addon, index) => ( - setSelectedAddon(addon.transportUrl)} /> + setSelectedAddon(addon.transportUrl)} + onShareButtonClicked={() => setSharedAddon(addon)} + /> )) }
{ selectedAddon !== null ? - -
- setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} /> + +
+ setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} + /> +
+
+ : + null + } + { + sharedAddon !== null ? + +
+ setSharedAddon(null)} + onClick={promptOnClick} + />
: diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index 8e32b6f70..2fbd3e928 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -117,25 +117,32 @@ } } -.addon-prompt-modal-container { +.prompt-modal-container { display: flex; align-items: center; justify-content: center; background-color: var(--color-background60); - .addon-prompt-container { + .prompt-container { flex: none; display: flex; flex-direction: column; justify-content: center; - width: 50rem; height: 80%; - .addon-prompt { + .prompt { flex-grow: 0; flex-shrink: 1; flex-basis: auto; align-self: stretch; } } + + .addon-prompt-container { + width: 50rem; + } + + .share-prompt-container { + width: 30rem; + } } \ No newline at end of file diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index ce4f49ec5..4d47137f5 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -21,7 +21,7 @@ const useSelectedAddon = (transportUrl) => { const { pathname, search } = UrlUtils.parse(locationHash.slice(1)); const queryParams = new URLSearchParams(search); queryParams.delete('addon'); - if (search && queryParams) { + if (!queryParams.values().next().done) { window.location.replace(`#${pathname}?${queryParams.toString()}`); } else { From e9745ea905bfac6c7e49721e3e5c88d325f669e4 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 31 Oct 2019 13:06:11 +0200 Subject: [PATCH 24/39] add addon prompt implemented --- src/common/NavBar/NavMenu/NavMenu.js | 2 +- src/routes/Addons/Addons.js | 60 +++++++++- src/routes/Addons/styles.less | 113 +++++++++++++++++++ src/routes/Addons/useSelectedAddon.js | 3 +- src/routes/Detail/StreamsList/StreamsList.js | 2 +- 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/src/common/NavBar/NavMenu/NavMenu.js b/src/common/NavBar/NavMenu/NavMenu.js index b8126116a..fa7f5db20 100644 --- a/src/common/NavBar/NavMenu/NavMenu.js +++ b/src/common/NavBar/NavMenu/NavMenu.js @@ -64,7 +64,7 @@ const NavMenu = ({ className }) => { {dropdowns.map((dropdown, index) => ( @@ -47,7 +71,7 @@ const Addons = ({ urlParams, queryParams }) => { @@ -72,6 +96,32 @@ const Addons = ({ urlParams, queryParams }) => { )) }
+ { + addedAddon ? + +
+
+ +
+
Add add-on
+ +
+ + +
+
+
+
+
+ : + null + } { selectedAddon !== null ? diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index 2fbd3e928..876e88301 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -138,6 +138,119 @@ } } + .add-addon-prompt-container { + width: 30rem; + + .add-addon-prompt { + position: relative; + z-index: 0; + display: flex; + flex-direction: column; + padding: 2.4rem 0; + background-color: var(--color-surfacelighter); + + .close-button-container { + position: absolute; + top: 0.4rem; + right: 0.4rem; + z-index: 1; + width: 2rem; + height: 2rem; + padding: 0.4rem; + + &:hover { + background-color: var(--color-surfacelight); + } + + .icon { + display: block; + width: 100%; + height: 100%; + fill: var(--color-backgrounddarker); + } + } + + .add-addon-prompt-content { + padding: 0 2.4rem; + + .add-addon-prompt-label { + margin-bottom: 1.4rem; + font-size: 1.3rem; + color: var(--color-backgrounddarker); + } + + .url-content { + flex: 1; + width: 100%; + padding: 0.5rem; + font-size: 0.9rem; + color: var(--color-surfacedark); + border: thin solid var(--color-surface); + } + + .buttons-container { + flex: none; + align-self: stretch; + display: flex; + flex-direction: row; + margin-top: 2rem; + + .button-container { + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 3rem; + padding: 0 1rem; + + &:first-child { + margin-right: 2rem; + } + + .label { + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; + max-height: 2.4em; + font-size: 1.2rem; + text-align: center; + } + } + + .cancel-button { + outline-color: var(--color-surfacedark); + outline-style: solid; + + &:hover, &:focus { + background-color: var(--color-surfacelight); + } + + .label { + color: var(--color-backgrounddarker); + } + } + + .add-button { + background-color: var(--color-signal5); + + &:hover, &:focus { + filter: brightness(1.2); + } + + &:focus { + outline-color: var(--color-surfacedarker); + } + + .label { + color: var(--color-surfacelighter); + } + } + } + } + } + } + .addon-prompt-container { width: 50rem; } diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index 4d47137f5..edf12cbc9 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -21,7 +21,8 @@ const useSelectedAddon = (transportUrl) => { const { pathname, search } = UrlUtils.parse(locationHash.slice(1)); const queryParams = new URLSearchParams(search); queryParams.delete('addon'); - if (!queryParams.values().next().done) { + queryParams.delete('null'); + if ([...queryParams].length !== 0) { window.location.replace(`#${pathname}?${queryParams.toString()}`); } else { diff --git a/src/routes/Detail/StreamsList/StreamsList.js b/src/routes/Detail/StreamsList/StreamsList.js index 4d0102d91..6b6ee8a26 100644 --- a/src/routes/Detail/StreamsList/StreamsList.js +++ b/src/routes/Detail/StreamsList/StreamsList.js @@ -31,7 +31,7 @@ const StreamsList = ({ className, metaItem }) => {
); From d96573b8d9727e98df11a1dc018138545fe2115f Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 31 Oct 2019 13:16:05 +0200 Subject: [PATCH 25/39] check the length of url --- src/routes/Addons/Addons.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index f5aa2c39c..7d435cca9 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -24,8 +24,12 @@ const Addons = ({ urlParams, queryParams }) => { setAddedAddon(true); }, []); const onAddButtonClicked = React.useCallback(() => { - setSelectedAddon(inputRef.current.value); - setAddedAddon(false); + if (inputRef.current.value.length > 0) { + setSelectedAddon(inputRef.current.value); + setAddedAddon(false); + } else { + alert('TODO: Error message'); + } }, [setSelectedAddon]); React.useEffect(() => { const onKeyUp = (event) => { From e204b2f7f01305d71df9f74309f3a9e02e4c2e75 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 31 Oct 2019 13:45:21 +0200 Subject: [PATCH 26/39] queryParams fixed --- src/routes/Addons/useSelectedAddon.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js index edf12cbc9..b0e6e6dc5 100644 --- a/src/routes/Addons/useSelectedAddon.js +++ b/src/routes/Addons/useSelectedAddon.js @@ -12,16 +12,15 @@ const useSelectedAddon = (transportUrl) => { return; } - fetch(transportUrl) // todo + fetch(transportUrl) // TODO .then((resp) => resp.json()) .then((manifest) => setAddon({ manifest, transportUrl, flags: {} })); }, [transportUrl]); const clear = React.useCallback(() => { if (active) { const { pathname, search } = UrlUtils.parse(locationHash.slice(1)); - const queryParams = new URLSearchParams(search); + const queryParams = new URLSearchParams(search || ''); queryParams.delete('addon'); - queryParams.delete('null'); if ([...queryParams].length !== 0) { window.location.replace(`#${pathname}?${queryParams.toString()}`); } From 1a01242fef7e6336eab24c531706f4c75eb4fccf Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 31 Oct 2019 14:45:13 +0200 Subject: [PATCH 27/39] isProtected property dropped --- src/routes/Addons/Addon/Addon.js | 5 ++--- src/routes/Addons/Addons.js | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/routes/Addons/Addon/Addon.js b/src/routes/Addons/Addon/Addon.js index 765449bb8..023c17732 100644 --- a/src/routes/Addons/Addon/Addon.js +++ b/src/routes/Addons/Addon/Addon.js @@ -5,7 +5,7 @@ const Icon = require('stremio-icons/dom'); const { Button } = require('stremio/common'); const styles = require('./styles'); -const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, isProtected, toggle, onShareButtonClicked }) => { +const Addon = ({ className, id, name, logo, description, types, version, transportUrl, installed, toggle, onShareButtonClicked }) => { const onKeyUp = React.useCallback((event) => { if (event.key === 'Enter' && typeof toggle === 'function') { toggle(event); @@ -52,7 +52,7 @@ const Addon = ({ className, id, name, logo, description, types, version, transpo }
-
Add add-on
-
{ - addons.filter((addon) => query.length === 0 || - ((typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(query.toLowerCase())) || - (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) - )) - .map((addon, index) => ( - setSelectedAddon(addon.transportUrl)} - onShareButtonClicked={() => setSharedAddon(addon)} - /> - )) + error !== null ? +
+ {error.type}{error.type === 'Other' ? ` - ${error.content}` : null} +
+ : + Array.isArray(addons) ? + addons.filter((addon) => query.length === 0 || + ((typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(query.toLowerCase())) || + (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(query.toLowerCase())) + )) + .map((addon, index) => ( + setSelectedAddon(addon.transportUrl)} + onShareButtonClicked={() => setSharedAddon(addon)} + /> + )) + : +
+ Loading +
}
{ diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index 876e88301..5a87e221d 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -113,6 +113,12 @@ width: 100%; margin-bottom: 2rem; } + + .message-container { + padding: 0 2rem; + font-size: 2rem; + color: var(--color-surfacelighter); + } } } } diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 659dbb612..ab58885ff 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -6,7 +6,7 @@ const DEFAULT_CATEGORY = 'thirdparty'; const useAddons = (urlParams, queryParams) => { const { core } = useServices(); - const [addons, setAddons] = React.useState([[], [], [], []]); + const [addons, setAddons] = React.useState([[], [], [], [], null]); const installAddon = React.useCallback(descriptor => { core.dispatch({ action: 'AddonOp', @@ -37,7 +37,7 @@ const useAddons = (urlParams, queryParams) => { )] .map((type) => ( { - is_selected: urlParams.type === type && urlParams.category === 'my', + is_selected: urlParams.category === 'my' && urlParams.type === type, name: 'my', load: { base: 'https://v3-cinemeta.strem.io/manifest.json', @@ -101,7 +101,8 @@ const useAddons = (urlParams, queryParams) => { state.addons.content.content : []; - setAddons([addonsItems, selectInputs, selectAddon, installedAddons]); + const error = state.addons.content.type === 'Err' ? state.addons.content.content : null; + setAddons([addonsItems, selectInputs, selectAddon, installedAddons, error]); }; core.on('NewModel', onNewState); core.dispatch({ From cdc6ca4049616c717f7af56ad7be5211f09b018b Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 12:05:20 +0200 Subject: [PATCH 33/39] check title value --- src/common/ModalDialog/ModalDialog.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/ModalDialog/ModalDialog.js b/src/common/ModalDialog/ModalDialog.js index 20128aa25..7d12c9ec0 100644 --- a/src/common/ModalDialog/ModalDialog.js +++ b/src/common/ModalDialog/ModalDialog.js @@ -38,7 +38,12 @@ const ModalDialog = ({ className, children, title, buttons, onCloseRequest }) => -

{title}

+ { + typeof title === 'string' && title.length > 0 ? +

{title}

+ : + null + }
{children}
From 86c7b746e3c8cedab70b8f8937b11717cdfd30aa Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 14:47:00 +0200 Subject: [PATCH 34/39] addon prompt, share prompt and addons adapted to the new modal dialog --- src/common/ModalDialog/styles.less | 11 +- src/common/SharePrompt/SharePrompt.js | 62 ++---- src/common/SharePrompt/styles.less | 208 ++++++++----------- src/routes/Addons/AddonPrompt/AddonPrompt.js | 170 ++++++--------- src/routes/Addons/AddonPrompt/styles.less | 177 ++++------------ src/routes/Addons/Addons.js | 139 ++++++------- src/routes/Addons/styles.less | 154 ++------------ 7 files changed, 311 insertions(+), 610 deletions(-) diff --git a/src/common/ModalDialog/styles.less b/src/common/ModalDialog/styles.less index 09196210b..9eb93b01f 100644 --- a/src/common/ModalDialog/styles.less +++ b/src/common/ModalDialog/styles.less @@ -45,15 +45,20 @@ } h1 { + margin-bottom: 1rem; font-size: 1.2rem; } .modal-dialog-content { - margin-top: 1rem; + padding: 1rem; + + >:not(:first-child) { + margin-top: 1rem; + } } .modal-dialog-buttons { - margin-top: 1rem; + margin: 1rem; display: flex; flex-direction: row; } @@ -94,6 +99,6 @@ } &:not(:last-child) { - margin-right: 1rem; + margin-right: 2rem; } } \ No newline at end of file diff --git a/src/common/SharePrompt/SharePrompt.js b/src/common/SharePrompt/SharePrompt.js index 292a33d2a..8a8dbefef 100644 --- a/src/common/SharePrompt/SharePrompt.js +++ b/src/common/SharePrompt/SharePrompt.js @@ -2,55 +2,34 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { useRouteFocused } = require('stremio-router'); const Button = require('stremio/common/Button'); const TextInput = require('stremio/common/TextInput'); const styles = require('./styles'); -const SharePrompt = ({ className, label, url, close, onClick }) => { +const SharePrompt = ({ className, url }) => { const inputRef = React.useRef(null); - const focusRoute = useRouteFocused(); const copyToClipboard = React.useCallback(() => { inputRef.current.select(); document.execCommand('copy'); }, []); - React.useEffect(() => { - const onKeyUp = (event) => { - if (event.key === 'Escape' && typeof close === 'function') { - close(); - } - }; - if (focusRoute) { - window.addEventListener('keyup', onKeyUp); - } - return () => { - window.removeEventListener('keyup', onKeyUp); - }; - }, [close, focusRoute]); return ( -
- -
-
{label}
-
- - -
-
- - -
+
+
+ + +
+
+ +
); @@ -58,10 +37,7 @@ const SharePrompt = ({ className, label, url, close, onClick }) => { SharePrompt.propTypes = { className: PropTypes.string, - label: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - close: PropTypes.func, - onClick: PropTypes.func + url: PropTypes.string.isRequired }; module.exports = SharePrompt; diff --git a/src/common/SharePrompt/styles.less b/src/common/SharePrompt/styles.less index 36c97ce7f..712f9d7cf 100644 --- a/src/common/SharePrompt/styles.less +++ b/src/common/SharePrompt/styles.less @@ -1,133 +1,107 @@ .share-prompt-container { - position: relative; - z-index: 0; - display: flex; - flex-direction: column; - padding: 2.4rem 0; - background-color: var(--color-surfacelighter); + .buttons-container { + flex: none; + align-self: stretch; + display: flex; + flex-direction: row; - .close-button-container { - position: absolute; - top: 0.4rem; - right: 0.4rem; - z-index: 1; - width: 2rem; - height: 2rem; - padding: 0.4rem; - - &:hover { - background-color: var(--color-surfacelight); - } - - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-backgrounddarker); - } - } - - .share-prompt-content { - padding: 0 2.4rem; - - .share-prompt-label { - font-size: 1.3rem; - color: var(--color-backgrounddarker); - } - - .buttons-container { - flex: none; - align-self: stretch; + .button-container { + flex-grow: 0; + flex-shrink: 1; + flex-basis: 14rem; display: flex; flex-direction: row; - margin: 1.4rem 0; + align-items: center; + justify-content: center; + padding: 0.6rem 1rem; - .button-container { - flex-grow: 0; - flex-shrink: 1; - flex-basis: 14rem; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - padding: 0.6rem 1rem; - - .icon { - flex: none; - width: 1.4rem; - height: 1.4rem; - margin-right: 0.6rem; - fill: var(--color-surfacelighter); - } - - .label { - flex-grow: 0; - flex-shrink: 1; - flex-basis: auto; - font-size: 0.8rem; - font-weight: 500; - color: var(--color-surfacelighter); - text-align: center; - } - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:not(:last-child) { - margin-right: 2rem; - } + .icon { + flex: none; + width: 1.4rem; + height: 1.4rem; + margin-right: 0.6rem; + fill: var(--color-surfacelighter); } - .facebook-button { - background-color: var(--color-facebook); - } - - .twitter-button { - background-color: var(--color-twitter); - } - } - - .url-container { - display: flex; - flex-direction: row; - border: thin solid var(--color-surface); - - .url-content { - flex: 1; - min-width: 12rem; - padding: 0.6rem 1rem; - font-size: 0.9rem; - color: var(--color-surfacedark); - text-align: center; - } - - .copy-button { + .label { flex-grow: 0; flex-shrink: 1; flex-basis: auto; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - padding: 0.6rem 1rem; - background-color: var(--color-surface); + font-size: 0.8rem; + font-weight: 500; + color: var(--color-surfacelighter); + text-align: center; + } - .icon { - flex: none; - width: 1.4rem; - height: 1.4rem; - margin-right: 0.6rem; - fill: var(--color-surfacedarker); - } + &:hover { + filter: brightness(1.2); + } - .label { - color: var(--color-surfacedarker); - } + &:focus { + outline: calc(1.5 * var(--focus-outline-size)) solid var(--color-surfacelighter); + outline-offset: calc(-2 * var(--focus-outline-size)); + } - &:hover, &:focus { - filter: brightness(1.2); - } + &:not(:last-child) { + margin-right: 2rem; + } + } + + .facebook-button { + background-color: var(--color-facebook); + } + + .twitter-button { + background-color: var(--color-twitter); + } + } + + .url-container { + display: flex; + flex-direction: row; + margin-top: 2rem; + border: thin solid var(--color-surface); + + .url-content { + flex: 1; + min-width: 12rem; + padding: 0.6rem 1rem; + font-size: 0.9rem; + color: var(--color-surfacedark); + text-align: center; + border-right: thin solid var(--color-surface); + } + + .copy-button { + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 0.6rem 1rem; + background-color: var(--color-surface); + + .icon { + flex: none; + width: 1.4rem; + height: 1.4rem; + margin-right: 0.6rem; + fill: var(--color-surfacedarker); + } + + .label { + color: var(--color-surfacedarker); + } + + &:hover { + filter: brightness(1.2); + } + + &:focus { + outline: calc(1.5 * var(--focus-outline-size)) solid var(--color-surfacelighter); + outline-offset: calc(-1.5 * var(--focus-outline-size)); } } } diff --git a/src/routes/Addons/AddonPrompt/AddonPrompt.js b/src/routes/Addons/AddonPrompt/AddonPrompt.js index 54b056a47..4d6f9ffc8 100644 --- a/src/routes/Addons/AddonPrompt/AddonPrompt.js +++ b/src/routes/Addons/AddonPrompt/AddonPrompt.js @@ -1,116 +1,86 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const Icon = require('stremio-icons/dom'); -const { useRouteFocused } = require('stremio-router'); -const { Button } = require('stremio/common'); const styles = require('./styles'); -const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, installed, official, cancel, onClick, toggle }) => { - const focusRoute = useRouteFocused(); - React.useEffect(() => { - const onKeyUp = (event) => { - if (event.key === 'Escape') { - cancel(); - } - }; - if (focusRoute) { - window.addEventListener('keyup', onKeyUp); - } - return () => { - window.removeEventListener('keyup', onKeyUp); - }; - }, [cancel, focusRoute]); +const AddonPrompt = ({ className, id, name, logo, description, types, catalogs, version, transportUrl, official }) => { return ( -
- -
-
0 })}> - { - typeof logo === 'string' && logo.length > 0 ? -
- {' -
- : - null - } - {typeof name === 'string' && name.length > 0 ? name : id} - {' '} - { - typeof version === 'string' && version.length > 0 ? - v.{version} - : - null - } -
+
+
0 })}> { - typeof description === 'string' && description.length > 0 ? -
- {description} + typeof logo === 'string' && logo.length > 0 ? +
+ {'
: null } + {typeof name === 'string' && name.length > 0 ? name : id} + {' '} { - typeof transportUrl === 'string' && transportUrl.length > 0 ? -
- URL: - {transportUrl} -
- : - null - } - { - Array.isArray(types) && types.length > 0 ? -
- Supported types: - - { - types.length === 1 ? - types[0] - : - types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1] - } - -
- : - null - } - { - Array.isArray(catalogs) && catalogs.length > 0 ? -
- Supported catalogs: - - { - catalogs.length === 1 ? - catalogs[0].name - : - catalogs.slice(0, -1).map(({ name }) => name).join(', ') + ' & ' + catalogs[catalogs.length - 1].name - } - -
- : - null - } - { - !official ? -
-
Using third-party add-ons will always be subject to your responsibility and the governing law of the jurisdiction you are located.
-
+ typeof version === 'string' && version.length > 0 ? + v.{version} : null }
-
- - -
+ { + typeof description === 'string' && description.length > 0 ? +
+ {description} +
+ : + null + } + { + typeof transportUrl === 'string' && transportUrl.length > 0 ? +
+ URL: + {transportUrl} +
+ : + null + } + { + Array.isArray(types) && types.length > 0 ? +
+ Supported types: + + { + types.length === 1 ? + types[0] + : + types.slice(0, -1).join(', ') + ' & ' + types[types.length - 1] + } + +
+ : + null + } + { + Array.isArray(catalogs) && catalogs.length > 0 ? +
+ Supported catalogs: + + { + catalogs.length === 1 ? + catalogs[0].name + : + catalogs.slice(0, -1).map(({ name }) => name).join(', ') + ' & ' + catalogs[catalogs.length - 1].name + } + +
+ : + null + } + { + !official ? +
+
Using third-party add-ons will always be subject to your responsibility and the governing law of the jurisdiction you are located.
+
+ : + null + }
); }; @@ -127,11 +97,7 @@ AddonPrompt.propTypes = { })), version: PropTypes.string, transportUrl: PropTypes.string, - installed: PropTypes.bool, - official: PropTypes.bool, - cancel: PropTypes.func, - onClick: PropTypes.func, - toggle: PropTypes.func + official: PropTypes.bool }; module.exports = AddonPrompt; diff --git a/src/routes/Addons/AddonPrompt/styles.less b/src/routes/Addons/AddonPrompt/styles.less index 11249348a..b58aafceb 100644 --- a/src/routes/Addons/AddonPrompt/styles.less +++ b/src/routes/Addons/AddonPrompt/styles.less @@ -1,153 +1,54 @@ .addon-prompt-container { - position: relative; - z-index: 0; - display: flex; - flex-direction: column; - padding: 3rem 0; - background-color: var(--color-surfacelighter); + .title-container { + font-size: 3rem; + font-weight: 300; + word-break: break-all; - .close-button-container { - position: absolute; - top: 0.5rem; - right: 0.5rem; - z-index: 1; - width: 2.5rem; - height: 2.5rem; - padding: 0.5rem; - - &:hover { - background-color: var(--color-surfacelight); + &.title-with-logo-container { + &::first-line { + line-height: 5rem; + } } - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-backgrounddarker); + .logo-container { + width: 5rem; + height: 5rem; + margin-right: 0.5rem; + background-color: var(--color-surfacelight20); + float: left; + + .logo { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; + } + } + + .version-container { + font-size: 1.5rem; + font-weight: 400; } } - .addon-prompt-content { - flex-grow: 0; - flex-shrink: 1; - flex-basis: auto; - align-self: stretch; - padding: 0 3rem; - overflow-y: auto; + .section-container { + margin-top: 1rem; - .title-container { - font-size: 3rem; + .section-header { + font-size: 1.2rem; + } + + .section-label { + font-size: 1.2rem; font-weight: 300; - word-break: break-all; - &.title-with-logo-container { - &::first-line { - line-height: 5rem; - } + &.transport-url-label { + user-select: text; } - .logo-container { - width: 5rem; - height: 5rem; - margin-right: 0.5rem; - background-color: var(--color-surfacelight20); - float: left; - - .logo { - display: block; - width: 100%; - height: 100%; - object-fit: contain; - object-position: center; - } - } - - .version-container { - font-size: 1.5rem; - font-weight: 400; - } - } - - .section-container { - margin-top: 1rem; - - .section-header { - font-size: 1.2rem; - } - - .section-label { - font-size: 1.2rem; - font-weight: 300; - - &.transport-url-label { - user-select: text; - } - - &.disclaimer-label { - font-style: italic; - } - } - } - } - - .buttons-container { - flex: none; - align-self: stretch; - display: flex; - flex-direction: row; - margin-top: 2rem; - padding: 0 3rem; - - .button-container { - flex: 1; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - height: 4rem; - padding: 0 1rem; - - &:first-child { - margin-right: 2rem; - } - - .label { - flex-grow: 0; - flex-shrink: 1; - flex-basis: auto; - max-height: 2.4em; - font-size: 1.3rem; - font-weight: 500; - text-align: center; - } - } - - .cancel-button, .uninstall-button { - outline-color: var(--color-surfacedark); - outline-style: solid; - - &:hover, &:focus { - background-color: var(--color-surfacelight); - } - - .label { - color: var(--color-backgrounddarker); - } - } - - .install-button { - background-color: var(--color-signal5); - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .label { - color: var(--color-surfacelighter); + &.disclaimer-label { + font-style: italic; } } } diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index ab5100085..e50f98116 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -1,8 +1,6 @@ const React = require('react'); -const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { Modal, useRouteFocused } = require('stremio-router'); -const { Button, Multiselect, NavBar, TextInput, SharePrompt } = require('stremio/common'); +const { Button, Multiselect, NavBar, TextInput, SharePrompt, ModalDialog } = require('stremio/common'); const Addon = require('./Addon'); const AddonPrompt = require('./AddonPrompt'); const useAddons = require('./useAddons'); @@ -11,7 +9,6 @@ const styles = require('./styles'); const Addons = ({ urlParams, queryParams }) => { const inputRef = React.useRef(null); - const focusRoute = useRouteFocused(); const [query, setQuery] = React.useState(''); const queryOnChange = React.useCallback((event) => { setQuery(event.currentTarget.value); @@ -29,32 +26,13 @@ const Addons = ({ urlParams, queryParams }) => { setAddAddonModalOpened(false); } }, [setSelectedAddon]); - React.useEffect(() => { - const onKeyUp = (event) => { - if (event.key === 'Escape' && typeof close === 'function') { - setAddAddonModalOpened(false); - } - }; - if (focusRoute) { - window.addEventListener('keyup', onKeyUp); - } - return () => { - window.removeEventListener('keyup', onKeyUp); - }; - }, [close, focusRoute]); - const promptModalBackgroundOnClick = React.useCallback((event) => { - if (!event.nativeEvent.clearSelectedAddonPrevented) { - clearSelectedAddon(); - setAddAddonModalOpened(false); - setSharedAddon(null); - } - }, [clearSelectedAddon]); - const promptOnClick = React.useCallback((event) => { - event.nativeEvent.clearSelectedAddonPrevented = true; - }, []); const setInstalledAddon = React.useCallback((currentAddon) => { return installedAddons.some((installedAddon) => installedAddon.transportUrl === currentAddon.transportUrl); }, [installedAddons]); + const toggleAddon = React.useCallback(() => { + setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon); + clearSelectedAddon(); + }); return (
@@ -108,62 +86,77 @@ const Addons = ({ urlParams, queryParams }) => {
{ addAddonModalOpened ? - -
-
- -
-
Add add-on
- -
- - -
-
-
-
-
+ setAddAddonModalOpened(false) + } + }, + { + label: 'Add', + props: { + title: 'Add', + onClick: onAddButtonClicked + } + } + ]} + onCloseRequest={() => setAddAddonModalOpened(false)} + > + + : null } { selectedAddon !== null ? - -
- setInstalledAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon)} - /> -
-
+ + + : null } { sharedAddon !== null ? - -
- setSharedAddon(null)} - onClick={promptOnClick} - /> -
-
+ setSharedAddon(null)}> + setSharedAddon(null)} + /> + : null } diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index 5a87e221d..d97a130ea 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -123,145 +123,31 @@ } } -.prompt-modal-container { - display: flex; - align-items: center; - justify-content: center; - background-color: var(--color-background60); +.add-addon-prompt-container { + width: 30rem; - .prompt-container { - flex: none; - display: flex; - flex-direction: column; - justify-content: center; - height: 80%; - - .prompt { - flex-grow: 0; - flex-shrink: 1; - flex-basis: auto; - align-self: stretch; - } + .url-content { + flex: 1; + width: 100%; + padding: 0.5rem; + font-size: 0.9rem; + color: var(--color-surfacedark); + border: thin solid var(--color-surface); } - .add-addon-prompt-container { - width: 30rem; - - .add-addon-prompt { - position: relative; - z-index: 0; - display: flex; - flex-direction: column; - padding: 2.4rem 0; - background-color: var(--color-surfacelighter); - - .close-button-container { - position: absolute; - top: 0.4rem; - right: 0.4rem; - z-index: 1; - width: 2rem; - height: 2rem; - padding: 0.4rem; - - &:hover { - background-color: var(--color-surfacelight); - } - - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-backgrounddarker); - } - } - - .add-addon-prompt-content { - padding: 0 2.4rem; - - .add-addon-prompt-label { - margin-bottom: 1.4rem; - font-size: 1.3rem; - color: var(--color-backgrounddarker); - } - - .url-content { - flex: 1; - width: 100%; - padding: 0.5rem; - font-size: 0.9rem; - color: var(--color-surfacedark); - border: thin solid var(--color-surface); - } - - .buttons-container { - flex: none; - align-self: stretch; - display: flex; - flex-direction: row; - margin-top: 2rem; - - .button-container { - flex: 1; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - height: 3rem; - padding: 0 1rem; - - &:first-child { - margin-right: 2rem; - } - - .label { - flex-grow: 0; - flex-shrink: 1; - flex-basis: auto; - max-height: 2.4em; - font-size: 1.2rem; - text-align: center; - } - } - - .cancel-button { - outline-color: var(--color-surfacedark); - outline-style: solid; - - &:hover, &:focus { - background-color: var(--color-surfacelight); - } - - .label { - color: var(--color-backgrounddarker); - } - } - - .add-button { - background-color: var(--color-signal5); - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .label { - color: var(--color-surfacelighter); - } - } - } - } - } + .cancel-button { + background-color: var(--color-surfacedark); } +} - .addon-prompt-container { - width: 50rem; - } +.addon-prompt-container { + width: 50rem; - .share-prompt-container { - width: 30rem; + .cancel-button { + background-color: var(--color-surfacedark); } +} + +.share-prompt-container { + width: 30rem; } \ No newline at end of file From 9f97f96e9eded57010e6cdd63e16fff8ee54c383 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 15:06:42 +0200 Subject: [PATCH 35/39] incorrect usage of dispatchCloseRequestEvent func removed --- src/common/ModalDialog/ModalDialog.js | 37 +++++++++++++++------------ 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/common/ModalDialog/ModalDialog.js b/src/common/ModalDialog/ModalDialog.js index 7d12c9ec0..89e36e209 100644 --- a/src/common/ModalDialog/ModalDialog.js +++ b/src/common/ModalDialog/ModalDialog.js @@ -7,39 +7,44 @@ const { Modal } = require('stremio-router'); const styles = require('./styles'); const ModalDialog = ({ className, children, title, buttons, onCloseRequest }) => { - const dispatchCloseRequestEvent = React.useCallback(event => { - if (typeof onCloseRequest === 'function') { - onCloseRequest({ - type: 'closeRequest', - reactEvent: event, - nativeEvent: event.nativeEvent - }); - } - }, [onCloseRequest]); React.useEffect(() => { const onKeyDown = (event) => { if (event.key === 'Escape') { - dispatchCloseRequestEvent(event); + onCloseRequest({ + type: 'close', + nativeEvent: event + }); } }; window.addEventListener('keydown', onKeyDown); return () => { window.removeEventListener('keydown', onKeyDown); }; - }, [dispatchCloseRequestEvent]); - const onModalContainerMouseDown = React.useCallback(event => { + }, [onCloseRequest]); + const closeButtonOnClick = React.useCallback((event) => { + onCloseRequest({ + type: 'close', + reactEvent: event, + nativeEvent: event.nativeEvent + }); + }, [onCloseRequest]) + const onModalContainerMouseDown = React.useCallback((event) => { if (event.target === event.currentTarget) { - dispatchCloseRequestEvent(event); + onCloseRequest({ + type: 'close', + reactEvent: event, + nativeEvent: event.nativeEvent + }); } - }, [dispatchCloseRequestEvent]); + }, [onCloseRequest]); return (
- { - typeof title === 'string' && title.length > 0 ? + typeof title === 'string' && title.length > 0 ?

{title}

: null From 84b8c543177e3573e7906d85c7c8e2eed44d3d7e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 15:07:51 +0200 Subject: [PATCH 36/39] semicolon added --- src/common/ModalDialog/ModalDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/ModalDialog/ModalDialog.js b/src/common/ModalDialog/ModalDialog.js index 89e36e209..bb632fc6b 100644 --- a/src/common/ModalDialog/ModalDialog.js +++ b/src/common/ModalDialog/ModalDialog.js @@ -27,7 +27,7 @@ const ModalDialog = ({ className, children, title, buttons, onCloseRequest }) => reactEvent: event, nativeEvent: event.nativeEvent }); - }, [onCloseRequest]) + }, [onCloseRequest]); const onModalContainerMouseDown = React.useCallback((event) => { if (event.target === event.currentTarget) { onCloseRequest({ From f27624a8a0a7b134e04f7be6f466cb2fb9a98f02 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 15:25:04 +0200 Subject: [PATCH 37/39] setInstalledAddon func renamed to installedAddon --- src/common/NavBar/NavMenu/NavMenu.js | 2 +- src/routes/Addons/Addons.js | 26 ++++++++++---------- src/routes/Detail/StreamsList/StreamsList.js | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/common/NavBar/NavMenu/NavMenu.js b/src/common/NavBar/NavMenu/NavMenu.js index fa7f5db20..b8126116a 100644 --- a/src/common/NavBar/NavMenu/NavMenu.js +++ b/src/common/NavBar/NavMenu/NavMenu.js @@ -64,7 +64,7 @@ const NavMenu = ({ className }) => { {dropdowns.map((dropdown, index) => ( @@ -50,7 +50,7 @@ const Addons = ({ urlParams, queryParams }) => { @@ -72,7 +72,7 @@ const Addons = ({ urlParams, queryParams }) => { setSelectedAddon(addon.transportUrl)} onShareButtonClicked={() => setSharedAddon(addon)} @@ -88,7 +88,7 @@ const Addons = ({ urlParams, queryParams }) => { addAddonModalOpened ? { } }, { - label: setInstalledAddon(selectedAddon) ? 'Uninstall' : 'Install', + label: installedAddon(selectedAddon) ? 'Uninstall' : 'Install', props: { - title: setInstalledAddon(selectedAddon) ? 'Uninstall' : 'Install', + title: installedAddon(selectedAddon) ? 'Uninstall' : 'Install', onClick: toggleAddon } } @@ -139,7 +139,7 @@ const Addons = ({ urlParams, queryParams }) => { { } { sharedAddon !== null ? - setSharedAddon(null)}> + setSharedAddon(null)}> {
); From 36eef2b050e568262d40a67a149398b0a3b18620 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 15:29:05 +0200 Subject: [PATCH 38/39] use event.value --- src/routes/Addons/useAddons.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index ab58885ff..eb6ffb746 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -68,9 +68,8 @@ const useAddons = (urlParams, queryParams) => { label: name })), onSelect: (event) => { - // TODO event.value const load = state.addons.catalogs.find(({ load: { path: { id } } }) => { - return id === event.reactEvent.currentTarget.dataset.value; + return id === event.value; }).load; window.location = `#/addons/${encodeURIComponent(load.path.id)}/${encodeURIComponent(load.path.type_name)}`; } From f4d3b30983ad38c9f15e1a9b7c5b7c2a948419ac Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 16:08:25 +0200 Subject: [PATCH 39/39] error case fixed --- src/routes/Addons/useAddons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index eb6ffb746..154af9940 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -87,7 +87,7 @@ const useAddons = (urlParams, queryParams) => { label: load.path.type_name })), onSelect: (event) => { - const load = JSON.parse(event.reactEvent.currentTarget.dataset.value); + const load = JSON.parse(event.value); window.location = `#/addons/${encodeURIComponent(load.path.id)}/${encodeURIComponent(load.path.type_name)}`; } } @@ -100,7 +100,7 @@ const useAddons = (urlParams, queryParams) => { state.addons.content.content : []; - const error = state.addons.content.type === 'Err' ? state.addons.content.content : null; + const error = state.addons.content.type === 'Err' && !state.ctx.is_loaded ? state.addons.content.content : null; setAddons([addonsItems, selectInputs, selectAddon, installedAddons, error]); }; core.on('NewModel', onNewState);