From c3abf5844bef5b863919ca667af0c93350983d5c Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 4 Oct 2019 18:15:59 +0300 Subject: [PATCH 01/81] Constants start with ID first --- src/routes/Settings/constants.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index 563bbd8c2..e94d481cf 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -1,24 +1,25 @@ const settingsSections = { 'General': [ - { 'type': 'user' }, - { 'header': 'UI Language', 'label': 'UI Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }], 'id': 'ui_language' }, + { 'id': 'user', 'type': 'user' }, + { 'id': 'language', 'header': 'UI Language', 'label': 'UI Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }] }, ], 'Player': [ - { 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'id': 'add-ons', 'href': '#/addons' }, - { 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }], 'id': 'default_subtitles_language' }, - { 'header': 'Default Subtitles Size', 'label': 'Default Subtitles Size', 'type': 'select', 'options': [{ 'label': '72%', 'value': '72%' }, { 'label': '80%', 'value': '80%' }, { 'label': '100%', 'value': '100%' }, { 'label': '120%', 'value': '120%' }, { 'label': '140%', 'value': '140%' }, { 'label': '160%', 'value': '160%' }, { 'label': '180%', 'value': '180%' }], 'id': 'default_subtitles_size' }, - { 'header': 'Subtitles Background', 'label': 'Subtitles background', 'type': 'select', 'options': [{ 'label': 'None', 'value': '' }, { 'label': 'Solid', 'value': 'solid' }, { 'label': 'Transparent', 'value': 'transparent' }], 'id': 'subtitles_background' }, - { 'header': 'Subtitles color', 'label': 'Subtitles color', 'type': 'color', 'id': 'subtitles_color' }, - { 'header': 'Subtitles outline color', 'label': 'Subtitles outline color', 'type': 'color', 'id': 'subtitles_outline_color' }, - { 'label': 'Auto-play next episode', 'type': 'checkbox', 'id': 'auto-play_next_episode' }, - { 'label': 'Pause playback when minimized', 'type': 'checkbox', 'id': 'pause_playback_when_minimized' }, - { 'label': 'Hardware-accelerated decoding', 'type': 'checkbox', 'id': 'hardware-accelerated_decoding' }, - { 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox', 'id': 'launch_player_in_a_separate_window_(advanced)' }, + { 'id': 'add-ons', 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'href': '#/addons' }, + { 'id': 'subtitles_language', 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }] }, + { 'id': 'subtitles_size', 'header': 'Default Subtitles Size', 'label': 'Default Subtitles Size', 'type': 'select', 'options': [{ 'label': '72%', 'value': '72%' }, { 'label': '80%', 'value': '80%' }, { 'label': '100%', 'value': '100%' }, { 'label': '120%', 'value': '120%' }, { 'label': '140%', 'value': '140%' }, { 'label': '160%', 'value': '160%' }, { 'label': '180%', 'value': '180%' }] }, + { 'id': 'subtitles_background', 'header': 'Subtitles Background', 'label': 'Subtitles background', 'type': 'select', 'options': [{ 'label': 'None', 'value': '' }, { 'label': 'Solid', 'value': 'solid' }, { 'label': 'Transparent', 'value': 'transparent' }] }, + { 'id': 'subtitles_color', 'header': 'Subtitles color', 'label': 'Subtitles color', 'type': 'color' }, + { 'id': 'subtitles_outline_color', 'header': 'Subtitles outline color', 'label': 'Subtitles outline color', 'type': 'color' }, + { 'id': 'autoplay_next_vid', 'label': 'Auto-play next episode', 'type': 'checkbox' }, + { 'id': 'pause_on_lost_focus', 'label': 'Pause playback when not in focus', 'type': 'checkbox' }, + { 'id': 'hardware-accelerated_decoding', 'label': 'Hardware-accelerated decoding', 'type': 'checkbox' }, + { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], 'Streaming': [ - { 'header': 'Caching', 'label': 'Caching', 'type': 'select', 'options': [{ 'label': 'No Caching', 'value': '' }, { 'label': '2GB', 'value': '2048' }, { 'label': '5GB', 'value': '5120' }, { 'label': '10GB', 'value': '10240' }], 'id': 'caching' }, + { 'id': 'caching', 'header': 'Caching', 'label': 'Caching', 'type': 'select', 'options': [{ 'label': 'No Caching', 'value': '' }, { 'label': '2GB', 'value': '2048' }, { 'label': '5GB', 'value': '5120' }, { 'label': '10GB', 'value': '10240' }] }, { 'header': 'Torrent Profile', 'label': 'Torrent Profile', 'type': 'select', 'options': [{ 'label': 'Default', 'value': 'profile-default' }, { 'label': 'Soft', 'value': 'profile-soft' }, { 'label': 'Fast', 'value': 'profile-fast' }], 'id': 'torrent_profile' }, - { 'header': 'Streaming server URL: http://127.0.0.1:11470', 'label': 'Streaming server is available.', 'type': 'static-text', 'icon': 'ic_check', 'id': 'streaming_server_is_available.' } + { 'id': 'server_url', 'header': 'Streaming server URL:', 'type': 'info' }, + { 'id': 'streaming_server_is_available.', 'label': 'Streaming server is available.', 'type': 'static-text', 'icon': 'ic_check' } ] }; From 315e616edee27c12bb00a8dfdc242523e5e01fc1 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 4 Oct 2019 18:16:32 +0300 Subject: [PATCH 02/81] Checkboxes string values --- src/routes/Settings/SectionsList/SectionsList.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index c835738b2..4dc4aed53 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -7,7 +7,7 @@ const styles = require('./styles'); const SectionsList = React.forwardRef(({ className, sections, preferences, onPreferenceChanged, onScroll }, ref) => { const toggleCheckbox = (id) => { - onPreferenceChanged(id, !preferences[id]); + onPreferenceChanged(id, preferences[id] === 'true' ? 'false' : 'true'); }; const colorChanged = React.useCallback((event) => { @@ -134,7 +134,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre return (
{input.header ?
{input.header}
: null} - +
{input.label}
@@ -156,6 +156,12 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre ); + } else if (input.type === 'info') { + return ( +
+
{input.header} {preferences[input.id]}
+
+ ); } })} From 0a41977641fc58d30e34c79dd47ef803d0d214a2 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 4 Oct 2019 18:16:49 +0300 Subject: [PATCH 03/81] Load/store data from state container --- src/routes/Settings/useSettings.js | 82 ++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index bca76cb46..847f99a6c 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -1,22 +1,64 @@ const React = require('react'); +const { useServices } = require('stremio/services'); + +module.exports = (devTestWithUser) => { + const { core } = useServices(); + // TODO: move these values in the state container + const [settings, setSettings] = React.useState({}); + /* + { + 'user': devTestWithUser ? { + '_id': 'neo', + 'email': 'neo@example.com', + 'avatar': 'https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg', + } : null, + 'ui_language': "eng", + 'subtitles_language': 'bul', + 'subtitles_size': '100%', + 'subtitles_background': '', + 'subtitles_color': '#ffffff', + 'subtitles_outline_color': '#000', + 'auto-play_next_episode': 'true', + 'pause_playback_when_minimized': 'false', + 'hardware-accelerated_decoding': 'true', + 'launch_player_in_a_separate_window_(advanced)': 'true', + 'caching': '2048', + 'torrent_profile': 'profile-default', + 'streaming_server_is_available.': 'true', + }); + */ + + React.useEffect(() => { + const onNewState = () => { + const state = core.getState(); + try { + setSettings({ ...settings, ...state.ctx.content.settings }); + } catch (e) { } + }; + window.top.core = core; + core.on('NewModel', onNewState); + core.dispatch({ + action: 'Load', + args: { + load: 'CatalogGrouped', + args: { extra: [] } + } + }); + return () => { + // Destructor function + core.off('NewModel', onNewState); + }; + }, []); + + const setTheSettings = React.useCallback(newSettings => { + const { user, ...args } = { ...newSettings }; + console.log('Save', args); + if(args.language === "bul") throw new Error('why') + Object.keys(args).forEach(key=> args[key] = args[key].toString()) + setSettings(newSettings); + core.dispatch({ action: 'Settings', args: { settings: 'Store', args } }); + }, [settings]) + + return [settings, setTheSettings]; +}; -module.exports = (devTestWithUser) => React.useState({ - "user": devTestWithUser ? { - "_id": "neo", - "email": "neo@example.com", - "avatar": "https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg?f=16x9&w=1200&$p$f$w=5867e40", - } : null, - "ui_language": "eng", - "default_subtitles_language": "bul", - "default_subtitles_size": "100%", - "subtitles_background": "", - "subtitles_color": "#ffffff", - "subtitles_outline_color": "#000", - "auto-play_next_episode": true, - "pause_playback_when_minimized": false, - "hardware-accelerated_decoding": true, - "launch_player_in_a_separate_window_(advanced)": true, - "caching": "2048", - "torrent_profile": "profile-default", - "streaming_server_is_available.": true, -}); \ No newline at end of file From 19dcbc648004584c6327786183e6d697cb977115 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 7 Oct 2019 12:39:45 +0300 Subject: [PATCH 04/81] Handle properly the state container --- src/routes/Settings/useSettings.js | 73 ++++++++++++++---------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index 847f99a6c..c8346039a 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -2,48 +2,41 @@ const React = require('react'); const { useServices } = require('stremio/services'); module.exports = (devTestWithUser) => { + const IGNORED_SETTINGS = Object.freeze(['user']); + const { core } = useServices(); - // TODO: move these values in the state container const [settings, setSettings] = React.useState({}); - /* - { - 'user': devTestWithUser ? { - '_id': 'neo', - 'email': 'neo@example.com', - 'avatar': 'https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg', - } : null, - 'ui_language': "eng", - 'subtitles_language': 'bul', - 'subtitles_size': '100%', - 'subtitles_background': '', - 'subtitles_color': '#ffffff', - 'subtitles_outline_color': '#000', - 'auto-play_next_episode': 'true', - 'pause_playback_when_minimized': 'false', - 'hardware-accelerated_decoding': 'true', - 'launch_player_in_a_separate_window_(advanced)': 'true', - 'caching': '2048', - 'torrent_profile': 'profile-default', - 'streaming_server_is_available.': 'true', - }); - */ React.useEffect(() => { - const onNewState = () => { - const state = core.getState(); + const updateState = (state) => { try { - setSettings({ ...settings, ...state.ctx.content.settings }); - } catch (e) { } - }; - window.top.core = core; - core.on('NewModel', onNewState); - core.dispatch({ - action: 'Load', - args: { - load: 'CatalogGrouped', - args: { extra: [] } + setSettings({ + ...settings, ...state.ctx.content.settings, user: devTestWithUser ? { + '_id': 'neo', + 'email': 'neo@example.com', + 'avatar': 'https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg', + } : state.ctx.content.auth && state.ctx.content.auth.user + }); + } catch (e) { + console.log('Cannot update settings state', e); } - }); + } + + const state = core.getState(); + try { + if (state.ctx.is_loaded) { + updateState(state); + return; + } + } catch (e) { + console.log('Cannot find state context', e); + } + + const onNewState = () => { + updateState(core.getState()); + }; + core.on('NewModel', onNewState); + core.dispatch({ action: 'LoadCtx' }); return () => { // Destructor function core.off('NewModel', onNewState); @@ -51,10 +44,10 @@ module.exports = (devTestWithUser) => { }, []); const setTheSettings = React.useCallback(newSettings => { - const { user, ...args } = { ...newSettings }; - console.log('Save', args); - if(args.language === "bul") throw new Error('why') - Object.keys(args).forEach(key=> args[key] = args[key].toString()) + const args = {}; + Object.keys(newSettings) + .filter(prop => !IGNORED_SETTINGS.includes(prop)) + .forEach(key => args[key] = newSettings[key].toString()) setSettings(newSettings); core.dispatch({ action: 'Settings', args: { settings: 'Store', args } }); }, [settings]) From d16c9adcf6b571107b523513b1272e2717d25063 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 7 Oct 2019 12:40:10 +0300 Subject: [PATCH 05/81] Use the user from the state container if available --- src/routes/Settings/Settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 4884722dd..f805076b5 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -6,7 +6,7 @@ const SectionsList = require('./SectionsList'); const { settingsSections } = require('./constants'); const useSettings = require('./useSettings'); -const devTestWithUser = true; +const devTestWithUser = false; const Settings = () => { const [preferences, setPreferences] = useSettings(devTestWithUser); From 61598f0e8fcd5d1193f362a1d4d2c56a1c2f970f Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 7 Oct 2019 16:16:55 +0300 Subject: [PATCH 06/81] Fetch settings options from the streaming server --- src/routes/Settings/Settings.js | 51 ++++++++++++++++++++++++++++++-- src/routes/Settings/constants.js | 5 +--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index f805076b5..a9d2cbb5b 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -10,15 +10,60 @@ const devTestWithUser = false; const Settings = () => { const [preferences, setPreferences] = useSettings(devTestWithUser); - const sections = React.useMemo(()=>Object.keys(settingsSections) + const [dynamicSections, setDynamicSections] = React.useState(settingsSections); + // TODO: The Streaming section should be handled separately + const sections = React.useMemo(()=>Object.keys(dynamicSections) .map((section) => ({ id: section, - inputs: settingsSections[section], + inputs: dynamicSections[section], ref: React.createRef() - })), []); + })), [dynamicSections]); + const [selectedSectionId, setSelectedSectionId] = React.useState(sections[0].id); const scrollContainerRef = React.useRef(null); + React.useEffect(() => { + const shouldFetch = preferences.server_url && preferences.server_url.length > 0 + ? + Promise.resolve(preferences.server_url + 'settings') + : + Promise.reject(); + + shouldFetch + .then(fetch) + .then(response => response.json()) + .then(serverPrefs => serverPrefs.options + .map(opt => ({ + id: opt.id, + label: opt.label, + header: opt.label, + type: opt.type, + options: opt.selections.map(sel => ({ label: sel.name, value: JSON.stringify(sel.val) })) + })) + .concat({ + id: 'torrent_profile', + label: 'Torrent Profile', + header: 'Torrent Profile', + type: 'select', + options: [{ 'label': 'Default', 'value': 'profile-default' }, { 'label': 'Soft', 'value': 'profile-soft' }, { 'label': 'Fast', 'value': 'profile-fast' }], + }) + ) + .catch(() => []).then(serverInputs => { + const additionalServerSettings = [ + { 'id': 'server_url', 'header': 'Streaming server URL:', 'type': 'info' }, + { 'id': 'streaming_server_is_available.', 'label': 'Streaming server is ' + (serverInputs.length !== 0 ? '' : 'not ') + 'available.', 'type': 'static-text', 'icon': serverInputs.length !== 0 ? 'ic_check' : 'ic_x' } + ]; + setDynamicSections({ + ...dynamicSections, + Streaming: [ + ...dynamicSections.Streaming, + ...serverInputs, + ...additionalServerSettings + ] + }); + }); + }, [preferences.server_url]); + ///////////////// const updatePreference = (option, value) => { diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index e94d481cf..e709b5dbf 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -2,6 +2,7 @@ const settingsSections = { 'General': [ { 'id': 'user', 'type': 'user' }, { 'id': 'language', 'header': 'UI Language', 'label': 'UI Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }] }, + { 'id': 'show_vid_overview', 'label': 'Show videos overview', 'type': 'checkbox' }, ], 'Player': [ { 'id': 'add-ons', 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'href': '#/addons' }, @@ -16,10 +17,6 @@ const settingsSections = { { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], 'Streaming': [ - { 'id': 'caching', 'header': 'Caching', 'label': 'Caching', 'type': 'select', 'options': [{ 'label': 'No Caching', 'value': '' }, { 'label': '2GB', 'value': '2048' }, { 'label': '5GB', 'value': '5120' }, { 'label': '10GB', 'value': '10240' }] }, - { 'header': 'Torrent Profile', 'label': 'Torrent Profile', 'type': 'select', 'options': [{ 'label': 'Default', 'value': 'profile-default' }, { 'label': 'Soft', 'value': 'profile-soft' }, { 'label': 'Fast', 'value': 'profile-fast' }], 'id': 'torrent_profile' }, - { 'id': 'server_url', 'header': 'Streaming server URL:', 'type': 'info' }, - { 'id': 'streaming_server_is_available.', 'label': 'Streaming server is available.', 'type': 'static-text', 'icon': 'ic_check' } ] }; From 05bbf3e721a9cb841e06f2d137e726f65b7a08ce Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 7 Oct 2019 16:44:56 +0300 Subject: [PATCH 07/81] Formatting --- src/routes/Settings/Settings.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index a9d2cbb5b..480bb8157 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -45,13 +45,17 @@ const Settings = () => { label: 'Torrent Profile', header: 'Torrent Profile', type: 'select', - options: [{ 'label': 'Default', 'value': 'profile-default' }, { 'label': 'Soft', 'value': 'profile-soft' }, { 'label': 'Fast', 'value': 'profile-fast' }], + options: [ + { label: 'Default', value: 'profile-default' }, + { label: 'Soft', value: 'profile-soft' }, + { label: 'Fast', value: 'profile-fast' } + ], }) ) .catch(() => []).then(serverInputs => { const additionalServerSettings = [ - { 'id': 'server_url', 'header': 'Streaming server URL:', 'type': 'info' }, - { 'id': 'streaming_server_is_available.', 'label': 'Streaming server is ' + (serverInputs.length !== 0 ? '' : 'not ') + 'available.', 'type': 'static-text', 'icon': serverInputs.length !== 0 ? 'ic_check' : 'ic_x' } + { id: 'server_url', header: 'Streaming server URL:', type: 'info' }, + { id: 'streaming_server_is_available.', label: 'Streaming server is ' + (serverInputs.length !== 0 ? '' : 'not ') + 'available.', type: 'static-text', icon: serverInputs.length !== 0 ? 'ic_check' : 'ic_x' } ]; setDynamicSections({ ...dynamicSections, From c1c7bb50dec55edbfecd58e6991d803ac205458d Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 9 Oct 2019 13:10:39 +0300 Subject: [PATCH 08/81] Drop dynamic streaming server settings --- src/routes/Settings/Settings.js | 27 ++------------------------- src/routes/Settings/constants.js | 2 ++ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 480bb8157..bfb5cdfe8 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -6,10 +6,8 @@ const SectionsList = require('./SectionsList'); const { settingsSections } = require('./constants'); const useSettings = require('./useSettings'); -const devTestWithUser = false; - const Settings = () => { - const [preferences, setPreferences] = useSettings(devTestWithUser); + const [preferences, setPreferences] = useSettings(); const [dynamicSections, setDynamicSections] = React.useState(settingsSections); // TODO: The Streaming section should be handled separately const sections = React.useMemo(()=>Object.keys(dynamicSections) @@ -31,27 +29,7 @@ const Settings = () => { shouldFetch .then(fetch) - .then(response => response.json()) - .then(serverPrefs => serverPrefs.options - .map(opt => ({ - id: opt.id, - label: opt.label, - header: opt.label, - type: opt.type, - options: opt.selections.map(sel => ({ label: sel.name, value: JSON.stringify(sel.val) })) - })) - .concat({ - id: 'torrent_profile', - label: 'Torrent Profile', - header: 'Torrent Profile', - type: 'select', - options: [ - { label: 'Default', value: 'profile-default' }, - { label: 'Soft', value: 'profile-soft' }, - { label: 'Fast', value: 'profile-fast' } - ], - }) - ) + .then(() => ['OK']) .catch(() => []).then(serverInputs => { const additionalServerSettings = [ { id: 'server_url', header: 'Streaming server URL:', type: 'info' }, @@ -61,7 +39,6 @@ const Settings = () => { ...dynamicSections, Streaming: [ ...dynamicSections.Streaming, - ...serverInputs, ...additionalServerSettings ] }); diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index e709b5dbf..493f51e17 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -17,6 +17,8 @@ const settingsSections = { { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], 'Streaming': [ + { "id": "cacheSize", "label": "Caching", "header": "Caching", "type": "select", "options": [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "null" }] }, + { "id": "torrent_profile", "label": "Torrent Profile", "header": "Torrent Profile", "type": "select", "options": [{ "label": "Default", "value": "profile-default" }, { "label": "Soft", "value": "profile-soft" }, { "label": "Fast", "value": "profile-fast" }] } ] }; From d31a07c4990f94dbdc9e58d52f254e307f208d88 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 9 Oct 2019 13:11:42 +0300 Subject: [PATCH 09/81] Load core context on boot; Drop development test user data --- src/App/App.js | 3 +++ src/routes/Settings/useSettings.js | 39 +++++++++--------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/App/App.js b/src/App/App.js index ffb33d4af..81778d1e7 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -22,6 +22,9 @@ const App = () => { }; const onCoreStateChanged = () => { setCoreInitialized(services.core.active || services.core.error instanceof Error); + if (services.core.active) { + services.core.dispatch({ action: 'LoadCtx' }); + } }; services.shell.on('stateChanged', onShellStateChanged); services.core.on('stateChanged', onCoreStateChanged); diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index c8346039a..25c5347f2 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -1,45 +1,31 @@ const React = require('react'); const { useServices } = require('stremio/services'); -module.exports = (devTestWithUser) => { +module.exports = () => { const IGNORED_SETTINGS = Object.freeze(['user']); const { core } = useServices(); const [settings, setSettings] = React.useState({}); React.useEffect(() => { - const updateState = (state) => { - try { + const updateState = () => { + const state = core.getState(); + if (state.ctx && state.ctx.is_loaded && state.ctx.content && state.ctx.content.settings) { setSettings({ - ...settings, ...state.ctx.content.settings, user: devTestWithUser ? { - '_id': 'neo', - 'email': 'neo@example.com', - 'avatar': 'https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg', - } : state.ctx.content.auth && state.ctx.content.auth.user + ...settings, + ...state.ctx.content.settings, + user: state.ctx.content.auth ? state.ctx.content.auth.user : null }); - } catch (e) { - console.log('Cannot update settings state', e); } } - const state = core.getState(); - try { - if (state.ctx.is_loaded) { - updateState(state); - return; - } - } catch (e) { - console.log('Cannot find state context', e); - } + core.on('NewModel', updateState); + + updateState(); - const onNewState = () => { - updateState(core.getState()); - }; - core.on('NewModel', onNewState); - core.dispatch({ action: 'LoadCtx' }); return () => { // Destructor function - core.off('NewModel', onNewState); + core.off('NewModel', updateState); }; }, []); @@ -48,9 +34,8 @@ module.exports = (devTestWithUser) => { Object.keys(newSettings) .filter(prop => !IGNORED_SETTINGS.includes(prop)) .forEach(key => args[key] = newSettings[key].toString()) - setSettings(newSettings); core.dispatch({ action: 'Settings', args: { settings: 'Store', args } }); - }, [settings]) + }, []) return [settings, setTheSettings]; }; From e8659cce32660d6ec7b8e6a5ff612c60d302461e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 17 Oct 2019 17:29:43 +0300 Subject: [PATCH 10/81] 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 388412f394297eabe2d46ff999389be4128b8f22 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 17 Oct 2019 17:32:36 +0300 Subject: [PATCH 11/81] Do not fetch the streaming server --- src/routes/Settings/Settings.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index bfb5cdfe8..970236064 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -20,33 +20,6 @@ const Settings = () => { const [selectedSectionId, setSelectedSectionId] = React.useState(sections[0].id); const scrollContainerRef = React.useRef(null); - React.useEffect(() => { - const shouldFetch = preferences.server_url && preferences.server_url.length > 0 - ? - Promise.resolve(preferences.server_url + 'settings') - : - Promise.reject(); - - shouldFetch - .then(fetch) - .then(() => ['OK']) - .catch(() => []).then(serverInputs => { - const additionalServerSettings = [ - { id: 'server_url', header: 'Streaming server URL:', type: 'info' }, - { id: 'streaming_server_is_available.', label: 'Streaming server is ' + (serverInputs.length !== 0 ? '' : 'not ') + 'available.', type: 'static-text', icon: serverInputs.length !== 0 ? 'ic_check' : 'ic_x' } - ]; - setDynamicSections({ - ...dynamicSections, - Streaming: [ - ...dynamicSections.Streaming, - ...additionalServerSettings - ] - }); - }); - }, [preferences.server_url]); - - ///////////////// - const updatePreference = (option, value) => { setPreferences({ ...preferences, [option]: value }); } From 0e52f2dd370e304a8e1dc294600b1d5a248d5ff7 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 17 Oct 2019 17:36:57 +0300 Subject: [PATCH 12/81] Streaming server settings from stremio-core --- src/routes/Settings/useSettings.js | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index c8346039a..ee7ed3642 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -1,21 +1,20 @@ const React = require('react'); const { useServices } = require('stremio/services'); -module.exports = (devTestWithUser) => { - const IGNORED_SETTINGS = Object.freeze(['user']); +module.exports = () => { + const IGNORED_SETTINGS = Object.freeze(['user', 'streaming']); const { core } = useServices(); - const [settings, setSettings] = React.useState({}); + const [settings, setSettings] = React.useState({ streaming: {} }); React.useEffect(() => { const updateState = (state) => { + console.log(state) try { setSettings({ - ...settings, ...state.ctx.content.settings, user: devTestWithUser ? { - '_id': 'neo', - 'email': 'neo@example.com', - 'avatar': 'https://www.thenational.ae/image/policy:1.891803:1566372420/AC17-Matrix-20-04.jpg', - } : state.ctx.content.auth && state.ctx.content.auth.user + ...settings, ...state.ctx.content.settings, + user: state.ctx.content.auth && state.ctx.content.auth.user, + streaming: state.streaming_server_settings || {}, }); } catch (e) { console.log('Cannot update settings state', e); @@ -44,12 +43,19 @@ module.exports = (devTestWithUser) => { }, []); const setTheSettings = React.useCallback(newSettings => { - const args = {}; - Object.keys(newSettings) - .filter(prop => !IGNORED_SETTINGS.includes(prop)) - .forEach(key => args[key] = newSettings[key].toString()) - setSettings(newSettings); - core.dispatch({ action: 'Settings', args: { settings: 'Store', args } }); + const event = { action: 'Settings', args: { args: {} } }; + // This can be done with React.useEffect and newSettings.streaming as dependency + const streamingServerSettingChanged = Object.keys(newSettings.streaming) + .some(prop => settings.streaming[prop] !== newSettings.streaming[prop]); + if (streamingServerSettingChanged) { + event.args = { settings: 'StoreStreamingServer', args: newSettings.streaming }; + } else { + event.args.settings = 'Store'; + Object.keys(newSettings) + .filter(prop => !IGNORED_SETTINGS.includes(prop)) + .forEach(key => event.args.args[key] = newSettings[key].toString()); + } + core.dispatch(event); }, [settings]) return [settings, setTheSettings]; From 4a43bf51549d94d3e3579c2ba45b162f9a90947e Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 17 Oct 2019 17:38:36 +0300 Subject: [PATCH 13/81] Hardcoded streaming server settings --- .../Settings/SectionsList/SectionsList.js | 47 ++++++++++++++++++- src/routes/Settings/constants.js | 3 +- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 4dc4aed53..49e9f950f 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -17,12 +17,18 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre }, [onPreferenceChanged]); const updateDropdown = React.useCallback((event) => { - var data = event.currentTarget.dataset; + const data = event.currentTarget.dataset; onPreferenceChanged(data.name, data.value); }, [onPreferenceChanged]); + const updateStreamingDropdown = React.useCallback((event) => { + const data = event.currentTarget.dataset; + const newPrefs = { ...preferences.streaming, [data.name]: data.value }; + onPreferenceChanged('streaming', newPrefs); + }, [onPreferenceChanged]); + const checkUser = React.useCallback((event) => { - if(! preferences.user) { + if (!preferences.user) { // Here in Stremio 4 we show a toast with a message, asking the anonymous user to log in/register console.log('No user found'); event.preventDefault(); @@ -37,6 +43,10 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const changePasswordUrl = preferences.user && 'https://www.strem.io/reset-password/' + preferences.user.email; const webCalUrl = preferences.user && 'webcal://www.strem.io/calendar/' + preferences.user._id + '.ics'; + // TODO: move these out of here + const cachingOptions = [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "null" }]; + const streamingProfiles = [{ "label": "Default", "value": "default" }, { "label": "Soft", "value": "soft" }, { "label": "Fast", "value": "fast" }]; + const sectionsElements = sections.map((section) =>
{section.id}
@@ -104,6 +114,39 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
); + } else if (input.type === 'streaming') { + return ( + + + { + // The streaming server settings are shown only if server is available + preferences.streaming.isLoaded + ? + +
+
Caching
+ +
+
+
Torrent Profile
+ +
+
+ : + null + } + {/* From here there is only presentation */} +
+
Streaming server URL: {preferences.server_url}
+
+
+
+ +
{'Streaming server is ' + (preferences.streaming.isLoaded ? '' : 'not ') + 'available.'}
+
+
+
+ ); } else if (input.type === 'select') { return (
diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index 493f51e17..f1e154d9c 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -17,8 +17,7 @@ const settingsSections = { { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], 'Streaming': [ - { "id": "cacheSize", "label": "Caching", "header": "Caching", "type": "select", "options": [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "null" }] }, - { "id": "torrent_profile", "label": "Torrent Profile", "header": "Torrent Profile", "type": "select", "options": [{ "label": "Default", "value": "profile-default" }, { "label": "Soft", "value": "profile-soft" }, { "label": "Fast", "value": "profile-fast" }] } + { 'id': 'streaming', 'type': 'streaming' }, ] }; From 830ae9faf6b86c1b12b62ca5deb50dd140f41bfc Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 17 Oct 2019 18:05:02 +0300 Subject: [PATCH 14/81] Fixed icon style for offline streaming server --- src/routes/Settings/SectionsList/SectionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 49e9f950f..c1ce5aa4e 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -141,7 +141,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
- +
{'Streaming server is ' + (preferences.streaming.isLoaded ? '' : 'not ') + 'available.'}
From e8d5356fcdc4cd81df9e3f3cd4154a34a98e561b Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 18 Oct 2019 10:50:33 +0300 Subject: [PATCH 15/81] Improved styles --- .../Settings/SectionsList/SectionsList.js | 2 +- src/routes/Settings/SectionsList/styles.less | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index c1ce5aa4e..b51834314 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -136,7 +136,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre null } {/* From here there is only presentation */} -
+
Streaming server URL: {preferences.server_url}
diff --git a/src/routes/Settings/SectionsList/styles.less b/src/routes/Settings/SectionsList/styles.less index d7b647535..0bc15dab1 100644 --- a/src/routes/Settings/SectionsList/styles.less +++ b/src/routes/Settings/SectionsList/styles.less @@ -3,16 +3,18 @@ } .section { - padding: 4rem 2rem; + padding: 4rem 0; + margin: 0 2rem; + width: var(--input-width); .section-header { - margin: 0 1.5rem 1.5rem 1.5rem; + margin: 1.5rem 0; font-size: 2rem; color: var(--color-surfacelighter); } .input-container { - margin: 1.5rem; + margin: 2rem 0; display: flex; flex-direction: column; @@ -51,14 +53,14 @@ &.select-container { .dropdown { height: 3rem; - width: var(--input-width); + // width: var(--input-width); } } &.link-container { - margin: 1rem 1.5rem; - + margin: 0; .link { + padding: .75rem 0; display: block; color: var(--color-secondarylight); @@ -75,7 +77,6 @@ &.button-container { .button { padding: 0.7rem; - width: var(--input-width); min-height: calc(var(--input-width) * 0.09); display: flex; align-items: center; @@ -132,10 +133,12 @@ } &.text-container { + margin: 0; .text { display: flex; flex-direction: row; align-items: center; + padding: .75rem 0; .icon { margin-right: 0.5rem; @@ -160,7 +163,7 @@ &.color-container { .color-picker { - width: var(--input-width); + box-shadow: inset 0px 0px .2rem 0px var(--color-surfacelighter20); height: calc(var(--input-width) * 0.08); cursor: pointer; From fc89a083eadbf1d4bc54e3baf27f8e3185a48d16 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 11:40:40 +0300 Subject: [PATCH 16/81] 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 17/81] 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 19/81] 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 b1ef2a7e3f8bceef1fc09a2ce96449c82ecec6d9 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Fri, 18 Oct 2019 17:29:46 +0300 Subject: [PATCH 20/81] Use the Infinity word for the infinite cache size --- src/routes/Settings/SectionsList/SectionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index b51834314..8bdabba33 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -44,7 +44,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const webCalUrl = preferences.user && 'webcal://www.strem.io/calendar/' + preferences.user._id + '.ics'; // TODO: move these out of here - const cachingOptions = [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "null" }]; + const cachingOptions = [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "Infinity" }]; const streamingProfiles = [{ "label": "Default", "value": "default" }, { "label": "Soft", "value": "soft" }, { "label": "Fast", "value": "fast" }]; const sectionsElements = sections.map((section) => From 1ef7244a395b54d7183bc5386015996d90d8f202 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 18 Oct 2019 17:39:57 +0300 Subject: [PATCH 21/81] 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 22/81] 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 23/81] 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 24/81] 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 ef85c480cecabf743983a6126fda064b0fdffb74 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 21 Oct 2019 16:34:10 +0300 Subject: [PATCH 25/81] Handle non-standard streaming profile --- src/routes/Settings/SectionsList/SectionsList.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 8bdabba33..65fff5ba7 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -45,7 +45,17 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre // TODO: move these out of here const cachingOptions = [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "Infinity" }]; - const streamingProfiles = [{ "label": "Default", "value": "default" }, { "label": "Soft", "value": "soft" }, { "label": "Fast", "value": "fast" }]; + const supportedProfiles = ['default', 'soft', 'fast']; + const mkProfiles = profiles => profiles.map(profile => ({ + label: profile[0].toUpperCase() + profile.slice(1).toLowerCase(), + value: profile, + })) + const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); + React.useEffect(() => { + if (preferences.streaming.profile && !supportedProfiles.includes(preferences.streaming.profile)) { + setStreamingProfiles(mkProfiles(supportedProfiles.concat(preferences.streaming.profile))); + } + }, [preferences.streaming.profile]); const sectionsElements = sections.map((section) =>
@@ -141,7 +151,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
- +
{'Streaming server is ' + (preferences.streaming.isLoaded ? '' : 'not ') + 'available.'}
From 82c7e09688a86c65f8487a1e4da6a687962be2b2 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 22 Oct 2019 11:40:42 +0300 Subject: [PATCH 26/81] 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 bbce6112522086ccaba3b213481353581427eaf0 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 22 Oct 2019 14:12:46 +0300 Subject: [PATCH 27/81] Dynamic dropdowns for the streaming server settings --- .../Settings/SectionsList/SectionsList.js | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 65fff5ba7..2b9f0f42f 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -43,14 +43,39 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const changePasswordUrl = preferences.user && 'https://www.strem.io/reset-password/' + preferences.user.email; const webCalUrl = preferences.user && 'webcal://www.strem.io/calendar/' + preferences.user._id + '.ics'; - // TODO: move these out of here - const cachingOptions = [{ "label": "no caching", "value": "0" }, { "label": "2GB", "value": "2147483648" }, { "label": "5GB", "value": "5368709120" }, { "label": "10GB", "value": "10737418240" }, { "label": "∞", "value": "Infinity" }]; + const formatBytes = inBytes => { + if (inBytes === '0') return 'no caching'; + if (inBytes === 'Infinity') return '∞'; + + const bytes = parseInt(inBytes, 10); + + const kilo = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const power = Math.floor(Math.log(bytes) / Math.log(kilo)); + + // More than 1024 yotta bytes + if(power >= sizes.length) { + power = sizes.length - 1; + } + return parseFloat((bytes / Math.pow(kilo, power)).toFixed(2)) + ' ' + sizes[power]; + } + const cacheSizes = ['0', '2147483648', '5368709120', '10737418240', 'Infinity']; + const mkCacheSizeOptions = sizes => sizes.map(size => ({ + label: formatBytes(size), // TODO: translation + value: size.toString(), + })) const supportedProfiles = ['default', 'soft', 'fast']; const mkProfiles = profiles => profiles.map(profile => ({ - label: profile[0].toUpperCase() + profile.slice(1).toLowerCase(), + label: profile[0].toUpperCase() + profile.slice(1).toLowerCase(), // TODO: translation value: profile, })) + const [cachingOptions, setCachingOptions] = React.useState(mkProfiles(supportedProfiles)); const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); + React.useEffect(() => { + if(typeof preferences.streaming.cacheSize === 'undefined') return; + setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.cacheSize))])); + }, [preferences.streaming.cacheSize]); React.useEffect(() => { if (preferences.streaming.profile && !supportedProfiles.includes(preferences.streaming.profile)) { setStreamingProfiles(mkProfiles(supportedProfiles.concat(preferences.streaming.profile))); From 6b2c6819701a2c58cba7bcd267a55b3a27077e45 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 23 Oct 2019 15:51:34 +0300 Subject: [PATCH 28/81] Adapt for the new format in stremio-core --- .../Settings/SectionsList/SectionsList.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 2b9f0f42f..20699ba3f 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -23,7 +23,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const updateStreamingDropdown = React.useCallback((event) => { const data = event.currentTarget.dataset; - const newPrefs = { ...preferences.streaming, [data.name]: data.value }; + const newPrefs = { ...preferences.streaming.ready, [data.name]: data.value }; onPreferenceChanged('streaming', newPrefs); }, [onPreferenceChanged]); @@ -73,14 +73,14 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const [cachingOptions, setCachingOptions] = React.useState(mkProfiles(supportedProfiles)); const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); React.useEffect(() => { - if(typeof preferences.streaming.cacheSize === 'undefined') return; - setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.cacheSize))])); - }, [preferences.streaming.cacheSize]); + if(!preferences.streaming.ready || typeof preferences.streaming.ready.cacheSize === 'undefined') return; + setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.ready.cacheSize))])); + }, [preferences.streaming.ready && preferences.streaming.ready.cacheSize]); React.useEffect(() => { - if (preferences.streaming.profile && !supportedProfiles.includes(preferences.streaming.profile)) { + if (preferences.streaming.ready && preferences.streaming.ready.profile && !supportedProfiles.includes(preferences.streaming.ready.profile)) { setStreamingProfiles(mkProfiles(supportedProfiles.concat(preferences.streaming.profile))); } - }, [preferences.streaming.profile]); + }, [preferences.streaming.ready && preferences.streaming.ready.profile]); const sectionsElements = sections.map((section) =>
@@ -155,16 +155,16 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre { // The streaming server settings are shown only if server is available - preferences.streaming.isLoaded + preferences.streaming.ready ?
Caching
- +
Torrent Profile
- +
: @@ -176,8 +176,8 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
- -
{'Streaming server is ' + (preferences.streaming.isLoaded ? '' : 'not ') + 'available.'}
+ +
{'Streaming server is ' + (preferences.streaming.ready ? '' : 'not ') + 'available.'}
From b2de5707225f4edcd6ff98dce1bae0bab947c93b Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 23 Oct 2019 15:52:17 +0300 Subject: [PATCH 29/81] Adapt for the new format in stremio-core --- src/routes/Settings/useSettings.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index f9f9be6a1..e773d9064 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -21,14 +21,21 @@ module.exports = () => { console.log('Cannot update settings state', e); } } + const onStoreError = ({ event, args }) => { + if (event !== "SettingsStoreError") return; + // TODO: Notify with maybe a toast? + console.log(args) + } core.on('NewModel', onNewState); + core.on('Event', onStoreError); onNewState(); return () => { // Destructor function core.off('NewModel', onNewState); + core.off('Event', onStoreError); }; }, []); @@ -36,7 +43,7 @@ module.exports = () => { const event = { action: 'Settings', args: { args: {} } }; // This can be done with React.useEffect and newSettings.streaming as dependency const streamingServerSettingChanged = Object.keys(newSettings.streaming) - .some(prop => settings.streaming[prop] !== newSettings.streaming[prop]); + .some(prop => settings.streaming.ready[prop] !== newSettings.streaming[prop]); if (streamingServerSettingChanged) { event.args = { settings: 'StoreStreamingServer', args: newSettings.streaming }; } else { From 836a2e1ad7ce60ba50ffe59bfcb92ad7b2fe1d4e Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Wed, 23 Oct 2019 16:09:04 +0300 Subject: [PATCH 30/81] 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 effe1336a0bf51e2a8f75cca5e82e4a3f884d48c Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 24 Oct 2019 16:29:51 +0300 Subject: [PATCH 31/81] Multiselect from master. Simpler settings object --- .../Settings/SectionsList/SectionsList.js | 46 ++++++++++--------- src/routes/Settings/SectionsList/styles.less | 11 ++++- src/routes/Settings/useSettings.js | 8 ++-- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 20699ba3f..66fc632cc 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -1,6 +1,6 @@ const React = require('react'); const PropTypes = require('prop-types'); -const { Button, Dropdown, Checkbox, ColorInput } = require('stremio/common'); +const { Button, Multiselect, Checkbox, ColorInput } = require('stremio/common'); const Icon = require('stremio-icons/dom'); const classnames = require('classnames'); const styles = require('./styles'); @@ -11,21 +11,23 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre }; const colorChanged = React.useCallback((event) => { - const id = event.currentTarget.dataset.id; - const color = event.nativeEvent.value; + const id = event.dataset.id; + const color = event.value; onPreferenceChanged(id, color); }, [onPreferenceChanged]); const updateDropdown = React.useCallback((event) => { - const data = event.currentTarget.dataset; - onPreferenceChanged(data.name, data.value); + const name = event.dataset.name; + const value = event.reactEvent.currentTarget.dataset.value; + onPreferenceChanged(name, value); }, [onPreferenceChanged]); const updateStreamingDropdown = React.useCallback((event) => { - const data = event.currentTarget.dataset; - const newPrefs = { ...preferences.streaming.ready, [data.name]: data.value }; + const name = event.dataset.name; + const value = event.reactEvent.currentTarget.dataset.value; + const newPrefs = { ...preferences.streaming, [name]: value }; onPreferenceChanged('streaming', newPrefs); - }, [onPreferenceChanged]); + }, [onPreferenceChanged, preferences.streaming]); const checkUser = React.useCallback((event) => { if (!preferences.user) { @@ -73,14 +75,14 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const [cachingOptions, setCachingOptions] = React.useState(mkProfiles(supportedProfiles)); const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); React.useEffect(() => { - if(!preferences.streaming.ready || typeof preferences.streaming.ready.cacheSize === 'undefined') return; - setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.ready.cacheSize))])); - }, [preferences.streaming.ready && preferences.streaming.ready.cacheSize]); + if(!preferences.streaming || typeof preferences.streaming.cacheSize === 'undefined') return; + setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.cacheSize))])); + }, [preferences.streaming && preferences.streaming.cacheSize]); React.useEffect(() => { - if (preferences.streaming.ready && preferences.streaming.ready.profile && !supportedProfiles.includes(preferences.streaming.ready.profile)) { + if (preferences.streaming && preferences.streaming.profile && !supportedProfiles.includes(preferences.streaming.profile)) { setStreamingProfiles(mkProfiles(supportedProfiles.concat(preferences.streaming.profile))); } - }, [preferences.streaming.ready && preferences.streaming.ready.profile]); + }, [preferences.streaming && preferences.streaming.profile]); const sectionsElements = sections.map((section) =>
@@ -155,20 +157,20 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre { // The streaming server settings are shown only if server is available - preferences.streaming.ready + preferences.streaming_error ? + null + :
Caching
- +
Torrent Profile
- +
- : - null } {/* From here there is only presentation */}
@@ -176,8 +178,8 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
- -
{'Streaming server is ' + (preferences.streaming.ready ? '' : 'not ') + 'available.'}
+ +
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available. Reason: '+preferences.streaming_error}
@@ -186,7 +188,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre return (
{input.header ?
{input.header}
: null} - +
); } else if (input.type === 'link') { @@ -231,7 +233,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre return (
{input.header ?
{input.header}
: null} - +
); } else if (input.type === 'info') { diff --git a/src/routes/Settings/SectionsList/styles.less b/src/routes/Settings/SectionsList/styles.less index 0bc15dab1..85e687d99 100644 --- a/src/routes/Settings/SectionsList/styles.less +++ b/src/routes/Settings/SectionsList/styles.less @@ -1,11 +1,20 @@ :import('~stremio/common/Checkbox/styles.less') { checkbox-icon: icon; } +:import('~stremio/common/Multiselect/styles.less') { + menu-container: menu-container; +} + +.menu-container { + max-height: 30rem; + overflow-y: auto; +} .section { padding: 4rem 0; margin: 0 2rem; width: var(--input-width); + overflow: visible; .section-header { margin: 1.5rem 0; @@ -17,6 +26,7 @@ margin: 2rem 0; display: flex; flex-direction: column; + overflow: visible; .input-header { margin-bottom: 0.5rem; @@ -53,7 +63,6 @@ &.select-container { .dropdown { height: 3rem; - // width: var(--input-width); } } diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index e773d9064..2780ee37f 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -15,7 +15,8 @@ module.exports = () => { ...settings, ...state.ctx.content.settings, user: state.ctx.content.auth ? state.ctx.content.auth.user : null, - streaming: state.streaming_server_settings || {}, + streaming: state.streaming_server_settings && state.streaming_server_settings.ready || {}, + streaming_error: state.streaming_server_settings && state.streaming_server_settings.error || "", }); } catch (e) { console.log('Cannot update settings state', e); @@ -42,8 +43,8 @@ module.exports = () => { const setTheSettings = React.useCallback(newSettings => { const event = { action: 'Settings', args: { args: {} } }; // This can be done with React.useEffect and newSettings.streaming as dependency - const streamingServerSettingChanged = Object.keys(newSettings.streaming) - .some(prop => settings.streaming.ready[prop] !== newSettings.streaming[prop]); + const streamingServerSettingChanged = settings.streaming && Object.keys(newSettings.streaming) + .some(prop => settings.streaming[prop] !== newSettings.streaming[prop]); if (streamingServerSettingChanged) { event.args = { settings: 'StoreStreamingServer', args: newSettings.streaming }; } else { @@ -57,4 +58,3 @@ module.exports = () => { return [settings, setTheSettings]; }; - From a3798775c2e5147d74000663917b92715802dd31 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Thu, 24 Oct 2019 16:30:08 +0300 Subject: [PATCH 32/81] Testing data with more languages --- src/routes/Settings/constants.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index f1e154d9c..0d3439446 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -1,3 +1,5 @@ +const languageOptions = [ { "label": "аҧсуа бызшәа", "value": "abk" }, { "label": "Afaraf", "value": "aar" }, { "label": "Afrikaans", "value": "afr" }, { "label": "Akan", "value": "aka" }, { "label": "gjuha shqipe", "value": "sqi" }, { "label": "አማርኛ", "value": "amh" }, { "label": "العربية", "value": "ara" }, { "label": "aragonés", "value": "arg" }, { "label": "Հայերեն", "value": "hye" }, { "label": "অসমীয়া", "value": "asm" }, { "label": "авар мацӀ", "value": "ava" }, { "label": "avesta", "value": "ave" }, { "label": "aymar aru", "value": "aym" }, { "label": "azərbaycan dili", "value": "aze" }, { "label": "bamanankan", "value": "bam" }, { "label": "башҡорт теле", "value": "bak" }, { "label": "euskara", "value": "eus" }, { "label": "беларуская мова", "value": "bel" }, { "label": "বাংলা", "value": "ben" }, { "label": "भोजपुरी", "value": "bih" }, { "label": "Bislama", "value": "bis" }, { "label": "bosanski jezik", "value": "bos" }, { "label": "brezhoneg", "value": "bre" }, { "label": "български език", "value": "bul" }, { "label": "ဗမာစာ", "value": "mya" }, { "label": "català", "value": "cat" }, { "label": "Chamoru", "value": "cha" }, { "label": "нохчийн мотт", "value": "che" }, { "label": "chiCheŵa", "value": "nya" }, { "label": "中文 (Zhōngwén)", "value": "zho" }, { "label": "чӑваш чӗлхи", "value": "chv" }, { "label": "Kernewek", "value": "cor" }, { "label": "corsu", "value": "cos" }, { "label": "ᓀᐦᐃᔭᐍᐏᐣ", "value": "cre" }, { "label": "hrvatski jezik", "value": "hrv" }, { "label": "čeština", "value": "ces" }, { "label": "dansk", "value": "dan" }, { "label": "ދިވެހި", "value": "div" }, { "label": "Nederlands", "value": "nld" }, { "label": "རྫོང་ཁ", "value": "dzo" }, { "label": "English", "value": "eng" }, { "label": "Esperanto", "value": "epo" }, { "label": "eesti", "value": "est" }, { "label": "Eʋegbe", "value": "ewe" }, { "label": "føroyskt", "value": "fao" }, { "label": "vosa Vakaviti", "value": "fij" }, { "label": "suomi", "value": "fin" }, { "label": "français", "value": "fre" }, { "label": "Fulfulde", "value": "ful" }, { "label": "galego", "value": "glg" }, { "label": "ქართული", "value": "kat" }, { "label": "Deutsch", "value": "ger" }, { "label": "ελληνικά", "value": "ell" }, { "label": "Avañe'ẽ", "value": "grn" }, { "label": "ગુજરાતી", "value": "guj" }, { "label": "Kreyòl ayisyen", "value": "hat" }, { "label": "Hausa", "value": "hau" }, { "label": "עברית", "value": "heb" }, { "label": "Otjiherero", "value": "her" }, { "label": "हिन्दी", "value": "hin" }, { "label": "Hiri Motu", "value": "hmo" }, { "label": "magyar", "value": "hun" }, { "label": "Interlingua", "value": "ina" }, { "label": "Bahasa Indonesia", "value": "ind" }, { "label": "Interlingue", "value": "ile" }, { "label": "Gaeilge", "value": "gle" }, { "label": "Asụsụ Igbo", "value": "ibo" }, { "label": "Iñupiaq", "value": "ipk" }, { "label": "Ido", "value": "ido" }, { "label": "Íslenska", "value": "isl" }, { "label": "italiano", "value": "ita" }, { "label": "ᐃᓄᒃᑎᑐᑦ", "value": "iku" }, { "label": "日本語 (にほんご)", "value": "jpn" }, { "label": "basa Jawa", "value": "jav" }, { "label": "kalaallisut", "value": "kal" }, { "label": "ಕನ್ನಡ", "value": "kan" }, { "label": "Kanuri", "value": "kau" }, { "label": "कश्मीरी", "value": "kas" }, { "label": "қазақ тілі", "value": "kaz" }, { "label": "ខ្មែរ", "value": "khm" }, { "label": "Gĩkũyũ", "value": "kik" }, { "label": "Ikinyarwanda", "value": "kin" }, { "label": "Кыргызча", "value": "kir" }, { "label": "коми кыв", "value": "kom" }, { "label": "KiKongo", "value": "kon" }, { "label": "한국어 (韓國語)", "value": "kor" }, { "label": "Kurdî", "value": "kur" }, { "label": "Kuanyama", "value": "kua" }, { "label": "latine", "value": "lat" }, { "label": "Lëtzebuergesch", "value": "ltz" }, { "label": "Luganda", "value": "lug" }, { "label": "Limburgs", "value": "lim" }, { "label": "Lingála", "value": "lin" }, { "label": "ພາສາລາວ", "value": "lao" }, { "label": "lietuvių kalba", "value": "lit" }, { "label": "Tshiluba", "value": "lub" }, { "label": "latviešu valoda", "value": "lav" }, { "label": "Gaelg", "value": "glv" }, { "label": "македонски јазик", "value": "mkd" }, { "label": "fiteny malagasy", "value": "mlg" }, { "label": "bahasa Melayu", "value": "msa" }, { "label": "മലയാളം", "value": "mal" }, { "label": "Malti", "value": "mlt" }, { "label": "te reo Māori", "value": "mri" }, { "label": "मराठी", "value": "mar" }, { "label": "Kajin M̧ajeļ", "value": "mah" }, { "label": "монгол", "value": "mon" }, { "label": "Ekakairũ Naoero", "value": "nau" }, { "label": "Diné bizaad", "value": "nav" }, { "label": "Norsk bokmål", "value": "nob" }, { "label": "isiNdebele", "value": "nde" }, { "label": "नेपाली", "value": "nep" }, { "label": "Owambo", "value": "ndo" }, { "label": "Norsk nynorsk", "value": "nno" }, { "label": "Norsk", "value": "nor" }, { "label": "ꆈꌠ꒿ Nuosuhxop", "value": "iii" }, { "label": "isiNdebele", "value": "nbl" }, { "label": "occitan", "value": "oci" }, { "label": "ᐊᓂᔑᓈᐯᒧᐎᓐ", "value": "oji" }, { "label": "ѩзыкъ словѣньскъ", "value": "chu" }, { "label": "Afaan Oromoo", "value": "orm" }, { "label": "ଓଡ଼ିଆ", "value": "ori" }, { "label": "ирон æвзаг", "value": "oss" }, { "label": "ਪੰਜਾਬੀ", "value": "pan" }, { "label": "पाऴि", "value": "pli" }, { "label": "فارسی", "value": "fas" }, { "label": "język polski", "value": "pol" }, { "label": "پښتو", "value": "pus" }, { "label": "português", "value": "por" }, { "label": "português Brazil", "value": "pob" }, { "label": "Runa Simi", "value": "que" }, { "label": "rumantsch grischun", "value": "roh" }, { "label": "Ikirundi", "value": "run" }, { "label": "limba română", "value": "ron" }, { "label": "русский язык", "value": "rus" }, { "label": "संस्कृतम्", "value": "san" }, { "label": "sardu", "value": "srd" }, { "label": "सिन्धी", "value": "snd" }, { "label": "Davvisámegiella", "value": "sme" }, { "label": "gagana fa'a Samoa", "value": "smo" }, { "label": "yângâ tî sängö", "value": "sag" }, { "label": "српски језик", "value": "srp" }, { "label": "Gàidhlig", "value": "gla" }, { "label": "chiShona", "value": "sna" }, { "label": "සිංහල", "value": "sin" }, { "label": "slovenčina", "value": "slk" }, { "label": "slovenski jezik", "value": "slv" }, { "label": "Soomaaliga", "value": "som" }, { "label": "Sesotho", "value": "sot" }, { "label": "español", "value": "spa" }, { "label": "Basa Sunda", "value": "sun" }, { "label": "Kiswahili", "value": "swa" }, { "label": "SiSwati", "value": "ssw" }, { "label": "Svenska", "value": "swe" }, { "label": "தமிழ்", "value": "tam" }, { "label": "తెలుగు", "value": "tel" }, { "label": "тоҷикӣ", "value": "tgk" }, { "label": "ไทย", "value": "tha" }, { "label": "ትግርኛ", "value": "tir" }, { "label": "བོད་ཡིག", "value": "bod" }, { "label": "Türkmen", "value": "tuk" }, { "label": "Wikang Tagalog", "value": "tgl" }, { "label": "Setswana", "value": "tsn" }, { "label": "faka Tonga", "value": "ton" }, { "label": "Türkçe", "value": "tur" }, { "label": "Xitsonga", "value": "tso" }, { "label": "татар теле", "value": "tat" }, { "label": "Twi", "value": "twi" }, { "label": "Reo Tahiti", "value": "tah" }, { "label": "Uyƣurqə", "value": "uig" }, { "label": "українська мова", "value": "ukr" }, { "label": "اردو", "value": "urd" }, { "label": "O'zbek", "value": "uzb" }, { "label": "Tshivenḓa", "value": "ven" }, { "label": "Tiếng Việt", "value": "vie" }, { "label": "Volapük", "value": "vol" }, { "label": "walon", "value": "wln" }, { "label": "Cymraeg", "value": "cym" }, { "label": "Wollof", "value": "wol" }, { "label": "Frysk", "value": "fry" }, { "label": "isiXhosa", "value": "xho" }, { "label": "ייִדיש", "value": "yid" }, { "label": "Yorùbá", "value": "yor" }, { "label": "Saɯ cueŋƅ", "value": "zha" }, { "label": "isiZulu", "value": "zul" } ]; + const settingsSections = { 'General': [ { 'id': 'user', 'type': 'user' }, @@ -6,7 +8,7 @@ const settingsSections = { ], 'Player': [ { 'id': 'add-ons', 'label': 'ADD-ONS', 'type': 'button', 'icon': 'ic_addons', 'href': '#/addons' }, - { 'id': 'subtitles_language', 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': [{ 'label': 'Български език', 'value': 'bul' }, { 'label': 'English', 'value': 'eng' }, { 'label': 'Deutsch', 'value': 'ger' }, { 'label': 'Español', 'value': 'esp' }, { 'label': 'Italiano', 'value': 'ita' }] }, + { 'id': 'subtitles_language', 'header': 'Default Subtitles Language', 'label': 'Default Subtitles Language', 'type': 'select', 'options': languageOptions }, { 'id': 'subtitles_size', 'header': 'Default Subtitles Size', 'label': 'Default Subtitles Size', 'type': 'select', 'options': [{ 'label': '72%', 'value': '72%' }, { 'label': '80%', 'value': '80%' }, { 'label': '100%', 'value': '100%' }, { 'label': '120%', 'value': '120%' }, { 'label': '140%', 'value': '140%' }, { 'label': '160%', 'value': '160%' }, { 'label': '180%', 'value': '180%' }] }, { 'id': 'subtitles_background', 'header': 'Subtitles Background', 'label': 'Subtitles background', 'type': 'select', 'options': [{ 'label': 'None', 'value': '' }, { 'label': 'Solid', 'value': 'solid' }, { 'label': 'Transparent', 'value': 'transparent' }] }, { 'id': 'subtitles_color', 'header': 'Subtitles color', 'label': 'Subtitles color', 'type': 'color' }, @@ -21,6 +23,7 @@ const settingsSections = { ] }; + module.exports = { settingsSections, }; From 3de87ce5bddd7bcc4b2f8dad4e57f05e5339e285 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 24 Oct 2019 17:21:12 +0300 Subject: [PATCH 33/81] 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 34/81] 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 35/81] 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 36/81] 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 12c5ab900da1845f362a850ed0e0fe35bcd162fe Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 28 Oct 2019 11:29:17 +0200 Subject: [PATCH 37/81] Handle notLoaded state in the streaming server settings --- .../Settings/SectionsList/SectionsList.js | 69 ++++++++++--------- src/routes/Settings/useSettings.js | 6 +- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 66fc632cc..509c0a498 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -50,14 +50,14 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre if (inBytes === 'Infinity') return '∞'; const bytes = parseInt(inBytes, 10); - + const kilo = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - + const power = Math.floor(Math.log(bytes) / Math.log(kilo)); // More than 1024 yotta bytes - if(power >= sizes.length) { + if (power >= sizes.length) { power = sizes.length - 1; } return parseFloat((bytes / Math.pow(kilo, power)).toFixed(2)) + ' ' + sizes[power]; @@ -75,7 +75,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre const [cachingOptions, setCachingOptions] = React.useState(mkProfiles(supportedProfiles)); const [streamingProfiles, setStreamingProfiles] = React.useState(mkProfiles(supportedProfiles)); React.useEffect(() => { - if(!preferences.streaming || typeof preferences.streaming.cacheSize === 'undefined') return; + if (!preferences.streaming || typeof preferences.streaming.cacheSize === 'undefined') return; setCachingOptions(mkCacheSizeOptions([...new Set(cacheSizes.concat(preferences.streaming.cacheSize))])); }, [preferences.streaming && preferences.streaming.cacheSize]); React.useEffect(() => { @@ -153,36 +153,41 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre ); } else if (input.type === 'streaming') { return ( - - - { - // The streaming server settings are shown only if server is available - preferences.streaming_error - ? - null - : - -
-
Caching
- -
-
-
Torrent Profile
- -
-
- } - {/* From here there is only presentation */} -
-
Streaming server URL: {preferences.server_url}
-
-
-
- -
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available. Reason: '+preferences.streaming_error}
+ preferences.streaming_loaded + ? + + { + // The streaming server settings are shown only if server is available + preferences.streaming_error + ? + null + : + +
+
Caching
+ +
+
+
Torrent Profile
+ +
+
+ } + {/* From here there is only presentation */} +
+
Streaming server URL: {preferences.server_url}
+
+
+ +
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available. Reason: ' + preferences.streaming_error}
+
+
+
+ : +
+
Loading streaming settgins...
- ); } else if (input.type === 'select') { return ( diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index 2780ee37f..9af135cd4 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -11,13 +11,15 @@ module.exports = () => { const onNewState = () => { const state = core.getState() try { - setSettings({ + const newSettings = { ...settings, ...state.ctx.content.settings, user: state.ctx.content.auth ? state.ctx.content.auth.user : null, streaming: state.streaming_server_settings && state.streaming_server_settings.ready || {}, + streaming_loaded: state.streaming_server_settings && (state.streaming_server_settings.error || state.streaming_server_settings.ready), streaming_error: state.streaming_server_settings && state.streaming_server_settings.error || "", - }); + }; + setSettings(newSettings); } catch (e) { console.log('Cannot update settings state', e); } From 01303a742a626234a81fd07d110af9a6c53a1e60 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 28 Oct 2019 12:01:40 +0200 Subject: [PATCH 38/81] 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 39/81] '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 40/81] 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 41/81] '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 188e64dff6969587553abdd8510ff5ab75aea9fc Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 29 Oct 2019 14:49:46 +0200 Subject: [PATCH 42/81] Removed useless option --- src/routes/Settings/constants.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/Settings/constants.js b/src/routes/Settings/constants.js index 0d3439446..9a3a95b9d 100644 --- a/src/routes/Settings/constants.js +++ b/src/routes/Settings/constants.js @@ -15,7 +15,6 @@ const settingsSections = { { 'id': 'subtitles_outline_color', 'header': 'Subtitles outline color', 'label': 'Subtitles outline color', 'type': 'color' }, { 'id': 'autoplay_next_vid', 'label': 'Auto-play next episode', 'type': 'checkbox' }, { 'id': 'pause_on_lost_focus', 'label': 'Pause playback when not in focus', 'type': 'checkbox' }, - { 'id': 'hardware-accelerated_decoding', 'label': 'Hardware-accelerated decoding', 'type': 'checkbox' }, { 'id': 'use_external_player', 'label': 'Launch player in a separate window (advanced)', 'type': 'checkbox' }, ], 'Streaming': [ From 0c62d79312b50b7854997da894e27c737d1d788a Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 29 Oct 2019 14:50:35 +0200 Subject: [PATCH 43/81] New type of core messages. Better events --- src/services/Core/Core.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/services/Core/Core.js b/src/services/Core/Core.js index 1489be668..0999b75f0 100644 --- a/src/services/Core/Core.js +++ b/src/services/Core/Core.js @@ -1,6 +1,34 @@ -const EventEmitter = require('events'); +// const EventEmitter = require('events'); const { default: init, ContainerService } = require('stremio-core-web'); +// Slightly better event handling +class EventEmitter { + constructor() { + this._handlers = {}; + } + _handlersFor(event) { + return this._handlers[event] || []; + } + on(event, handler) { + this._handlers[event] = this._handlersFor(event).concat(handler); + } + off(event, handler) { + console.log('Before off', this._handlersFor(event)) + this._handlers[event] = this._handlersFor(event).filter(event => event !== handler); + console.log('After off', this._handlersFor(event)) + } + emit(event, args) { + this._handlersFor(event).forEach(handler => { + try { + handler(args); + } + catch (e) { + console.log('Event error', event, args, e); + } + }); + } +} + function Core() { let active = false; let error = null; @@ -52,12 +80,12 @@ function Core() { function off(name, listener) { events.off(name, listener); } - function dispatch({ action, args } = {}) { + function dispatch(args, model = 'All') { if (!active) { return; } - containerService.dispatch({ action, args }); + containerService.dispatch({ model, args }); } function getState() { if (!active) { From b1f917e3a93d92c867c2daadf4e6b6faad1eb514 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 29 Oct 2019 14:51:03 +0200 Subject: [PATCH 44/81] New loaded state --- .../Settings/SectionsList/SectionsList.js | 2 +- src/routes/Settings/useSettings.js | 26 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/routes/Settings/SectionsList/SectionsList.js b/src/routes/Settings/SectionsList/SectionsList.js index 509c0a498..d56de298a 100644 --- a/src/routes/Settings/SectionsList/SectionsList.js +++ b/src/routes/Settings/SectionsList/SectionsList.js @@ -180,7 +180,7 @@ const SectionsList = React.forwardRef(({ className, sections, preferences, onPre
-
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available. Reason: ' + preferences.streaming_error}
+
{'Streaming server is ' + (preferences.streaming_error ? 'not ' : '') + 'available.'}{preferences.streaming_error && ' Reason: ' + preferences.streaming_error}
diff --git a/src/routes/Settings/useSettings.js b/src/routes/Settings/useSettings.js index 9af135cd4..4f71eba0e 100644 --- a/src/routes/Settings/useSettings.js +++ b/src/routes/Settings/useSettings.js @@ -1,29 +1,35 @@ const React = require('react'); const { useServices } = require('stremio/services'); -module.exports = () => { - const IGNORED_SETTINGS = Object.freeze(['user', 'streaming']); +const IGNORED_SETTINGS = Object.freeze(['user', 'streaming']); +module.exports = () => { const { core } = useServices(); - const [settings, setSettings] = React.useState({ streaming: {} }); + + const [settings, setSettings] = React.useState({ + user: null, + streaming: {}, + streaming_loaded: false, + streaming_error: "" + }); React.useEffect(() => { const onNewState = () => { - const state = core.getState() + const { ctx, streaming_server_settings } = core.getState() try { const newSettings = { ...settings, - ...state.ctx.content.settings, - user: state.ctx.content.auth ? state.ctx.content.auth.user : null, - streaming: state.streaming_server_settings && state.streaming_server_settings.ready || {}, - streaming_loaded: state.streaming_server_settings && (state.streaming_server_settings.error || state.streaming_server_settings.ready), - streaming_error: state.streaming_server_settings && state.streaming_server_settings.error || "", + ...ctx.content.settings, + user: ctx.content.auth ? ctx.content.auth.user : null, + streaming: streaming_server_settings && streaming_server_settings.ready || {}, + streaming_loaded: streaming_server_settings && !!(streaming_server_settings.error || streaming_server_settings.ready), + streaming_error: streaming_server_settings && streaming_server_settings.error || "", }; setSettings(newSettings); } catch (e) { console.log('Cannot update settings state', e); } - } + }; const onStoreError = ({ event, args }) => { if (event !== "SettingsStoreError") return; // TODO: Notify with maybe a toast? From eb19a3b38886917b0861d1b59f681f55c554a786 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 29 Oct 2019 16:37:45 +0200 Subject: [PATCH 45/81] 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 46/81] 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 48/81] 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 49/81] 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 50/81] 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 51/81] 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 52/81] 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 2093f8118eec052d686322ce432d795c53ea5978 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 4 Nov 2019 14:44:35 +0200 Subject: [PATCH 59/81] Make use of the modal dialog --- src/common/ColorInput/ColorInput.js | 44 +++++----------- src/common/ColorInput/styles.less | 81 ----------------------------- 2 files changed, 12 insertions(+), 113 deletions(-) delete mode 100644 src/common/ColorInput/styles.less diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index eefcc2c18..ab43c3050 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -1,41 +1,32 @@ const React = require('react'); const PropTypes = require('prop-types'); const AColorPicker = require('a-color-picker'); -const Icon = require('stremio-icons/dom'); -const { Modal } = require('stremio-router'); const Button = require('stremio/common/Button'); const useBinaryState = require('stremio/common/useBinaryState'); +const ModalDialog = require('stremio/common/ModalDialog'); const useDataset = require('stremio/common/useDataset'); const ColorPicker = require('./ColorPicker'); -const styles = require('./styles'); const COLOR_FORMAT = 'hexcss4'; const ColorInput = ({ className, value, onChange, ...props }) => { value = AColorPicker.parseColor(value, COLOR_FORMAT); const dataset = useDataset(props); - const [modalOpen, openModal, closeModal] = useBinaryState(false); + const [modalOpen, setModalOpen, setModalClsoed] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); + const closeModal = (event) => { + event.nativeEvent.openModalPrevented = true; + setModalClsoed(); + }; const pickerLabelOnClick = React.useCallback((event) => { if (typeof props.onClick === 'function') { props.onClick(event); } if (!event.nativeEvent.openModalPrevented) { - openModal(); + setModalOpen(); } }, [props.onClick]); - const modalContainerOnClick = React.useCallback((event) => { - event.nativeEvent.openModalPrevented = true; - }, []); - const modalContainerOnMouseDown = React.useCallback((event) => { - if (!event.nativeEvent.closeModalPrevented) { - closeModal(); - } - }, []); - const modalContentOnMouseDown = React.useCallback((event) => { - event.nativeEvent.closeModalPrevented = true; - }, []); const colorPickerOnInput = React.useCallback((event) => { setTempValue(event.value); }, []); @@ -50,8 +41,8 @@ const ColorInput = ({ className, value, onChange, ...props }) => { }); } - closeModal(); - }, [onChange, tempValue, dataset]); + closeModal(event); + }, [onChange, tempValue, dataset, closeModal]); React.useEffect(() => { setTempValue(value); }, [value, modalOpen]); @@ -59,20 +50,9 @@ const ColorInput = ({ className, value, onChange, ...props }) => { -
- - -
-
+ + + : null } diff --git a/src/common/ColorInput/styles.less b/src/common/ColorInput/styles.less deleted file mode 100644 index 8242c6e67..000000000 --- a/src/common/ColorInput/styles.less +++ /dev/null @@ -1,81 +0,0 @@ -.color-input-modal-container { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - pointer-events: auto; - background-color: var(--color-backgrounddarker40); - - .color-input-container { - flex: none; - display: flex; - flex-direction: column; - align-items: center; - max-width: 25rem; - padding: 1rem; - background-color: var(--color-surfacelighter); - - .header-container { - flex: none; - align-self: stretch; - display: flex; - flex-direction: row; - align-items: flex-start; - - .title { - flex: 1; - margin-right: 1rem; - font-size: 1.2rem; - max-height: 2.4em; - } - - .close-button-container { - flex: none; - width: 1.5rem; - height: 1.5rem; - padding: 0.25rem; - - &:hover, &:focus { - background-color: var(--color-surfacedark20); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-surfacedarker); - } - } - } - - .color-picker { - flex: none; - margin: 1rem; - } - - .submit-button-container { - flex: none; - align-self: stretch; - padding: 1rem; - background-color: var(--color-signal5); - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:focus { - outline-color: var(--color-surfacedarker); - } - - .label { - max-height: 2.4em; - text-align: center; - color: var(--color-surfacelighter); - } - } - } -} \ No newline at end of file From b4c9b527d0d75b870df8111319ad6a7b0ed26c1d Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 4 Nov 2019 15:32:06 +0200 Subject: [PATCH 60/81] Fixed a typo --- src/common/ColorInput/ColorInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index ab43c3050..661dae50c 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -12,11 +12,11 @@ const COLOR_FORMAT = 'hexcss4'; const ColorInput = ({ className, value, onChange, ...props }) => { value = AColorPicker.parseColor(value, COLOR_FORMAT); const dataset = useDataset(props); - const [modalOpen, setModalOpen, setModalClsoed] = useBinaryState(false); + const [modalOpen, setModalOpen, setModalClosed] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); const closeModal = (event) => { event.nativeEvent.openModalPrevented = true; - setModalClsoed(); + setModalClosed(); }; const pickerLabelOnClick = React.useCallback((event) => { if (typeof props.onClick === 'function') { From cdc6ca4049616c717f7af56ad7be5211f09b018b Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Tue, 5 Nov 2019 12:05:20 +0200 Subject: [PATCH 61/81] 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 62/81] 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 63/81] 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 64/81] 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 65/81] 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 66/81] 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 67/81] 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); From 07cfad8cd841272fc8a625d1cfe76967a486d0d4 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 5 Nov 2019 16:13:25 +0200 Subject: [PATCH 68/81] Multiselect autofocus first selected item --- src/common/Multiselect/Multiselect.js | 16 ++++++++++------ src/common/Popup/Popup.js | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/common/Multiselect/Multiselect.js b/src/common/Multiselect/Multiselect.js index 7a251849b..f5b81f751 100644 --- a/src/common/Multiselect/Multiselect.js +++ b/src/common/Multiselect/Multiselect.js @@ -98,12 +98,16 @@ const Multiselect = ({ className, direction, title, renderLabelContent, renderLa
{ options.length > 0 ? - options.map(({ label, value }) => ( - - )) + options.map(({ label, value }) => { + const isSelected = selected.includes(value); + const title = typeof label === 'string' ? label : value; + return ( + + ) + }) :
No options available
diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index dc5c9bc16..5b957392d 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -68,7 +68,7 @@ const Popup = ({ open, direction, renderLabel, renderMenu, onCloseRequest, ...pr ref: labelRef, className: styles['label-container'], children: open ? - + {renderMenu()} : From e6163085b5b25980d2cb5c5d7fc955975ab20745 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 5 Nov 2019 17:46:44 +0200 Subject: [PATCH 69/81] Simplified ColorInput --- src/common/ColorInput/ColorInput.js | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index 661dae50c..f9a682eb9 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -14,19 +14,6 @@ const ColorInput = ({ className, value, onChange, ...props }) => { const dataset = useDataset(props); const [modalOpen, setModalOpen, setModalClosed] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); - const closeModal = (event) => { - event.nativeEvent.openModalPrevented = true; - setModalClosed(); - }; - const pickerLabelOnClick = React.useCallback((event) => { - if (typeof props.onClick === 'function') { - props.onClick(event); - } - - if (!event.nativeEvent.openModalPrevented) { - setModalOpen(); - } - }, [props.onClick]); const colorPickerOnInput = React.useCallback((event) => { setTempValue(event.value); }, []); @@ -40,23 +27,23 @@ const ColorInput = ({ className, value, onChange, ...props }) => { nativeEvent: event.nativeEvent }); } - - closeModal(event); - }, [onChange, tempValue, dataset, closeModal]); + setModalClosed(); + }, [onChange, tempValue, dataset]); React.useEffect(() => { setTempValue(value); }, [value, modalOpen]); return ( - + ); }; From 3aa7cf6ad5e64effe4efc568d4cd51fd397fd668 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Wed, 6 Nov 2019 13:23:45 +0200 Subject: [PATCH 70/81] Do not force auto focus --- src/common/Popup/Popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 5b957392d..dc5c9bc16 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -68,7 +68,7 @@ const Popup = ({ open, direction, renderLabel, renderMenu, onCloseRequest, ...pr ref: labelRef, className: styles['label-container'], children: open ? - + {renderMenu()} : From 807cf9f0573865aebe25d81e7a49851128f1e205 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 7 Nov 2019 16:19:53 +0200 Subject: [PATCH 71/81] intro linked with stremio-core (login, logout, register) --- src/common/NavBar/NavMenu/NavMenu.js | 20 ++++++-- src/common/useUser.js | 12 +++-- src/routes/Intro/Intro.js | 75 +++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/common/NavBar/NavMenu/NavMenu.js b/src/common/NavBar/NavMenu/NavMenu.js index b8126116a..329e93dfb 100644 --- a/src/common/NavBar/NavMenu/NavMenu.js +++ b/src/common/NavBar/NavMenu/NavMenu.js @@ -2,14 +2,16 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); +const { useServices } = require('stremio/services'); const Button = require('stremio/common/Button'); const Popup = require('stremio/common/Popup'); const useBinaryState = require('stremio/common/useBinaryState'); const useFullscreen = require('stremio/common/useFullscreen'); -const useUser = require('./useUser'); +const useUser = require('stremio/common/useUser'); const styles = require('./styles'); const NavMenu = ({ className }) => { + const { core } = useServices(); const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false); const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen(); const user = useUser(); @@ -21,6 +23,14 @@ const NavMenu = ({ className }) => { const popupMenuOnClick = React.useCallback((event) => { event.nativeEvent.togglePopupPrevented = true; }, []); + const logoutButtonOnClick = React.useCallback(() => { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Logout' + } + }); + }, []); return ( {
-
{user.anonymous ? 'Anonymous user' : user.email}
+
{!user ? 'Anonymous user' : user.email}
-
diff --git a/src/common/useUser.js b/src/common/useUser.js index 0c5391b26..fe685894f 100644 --- a/src/common/useUser.js +++ b/src/common/useUser.js @@ -3,14 +3,16 @@ const { useServices } = require('stremio/services'); const useUser = () => { const { core } = useServices(); - const [user, setUser] = React.useState(state.ctx.auth ? state.ctx.auth.user : null); + const state = core.getState(); + const [user, setUser] = React.useState(state.ctx.content.auth ? state.ctx.content.auth.user : null); React.useEffect(() => { - const onNewModel = () => { - setUser(state.ctx.auth ? state.ctx.auth.user : null); + const onNewState = () => { + setUser(state.ctx.content.auth ? state.ctx.content.auth.user : null); }; - core.on('NewModel', onNewModel); + core.on('NewModel', onNewState); + onNewState(); return () => { - core.off('NewModel', onNewModel); + core.off('NewModel', onNewState); }; }, []); return user; diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 9f1ad474f..396c6cee1 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -3,14 +3,16 @@ const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const { useRouteFocused } = require('stremio-router'); const { Button } = require('stremio/common'); +const { useServices } = require('stremio/services'); const CredentialsTextInput = require('./CredentialsTextInput'); const ConsentCheckbox = require('./ConsentCheckbox'); const styles = require('./styles'); const LOGIN_FORM = 'LOGIN_FORM'; const SIGNUP_FORM = 'SIGNUP_FORM'; - +// TODO queryparams for signup and login const Intro = () => { + const { core } = useServices(); const routeFocused = useRouteFocused(); const emailRef = React.useRef(); const passwordRef = React.useRef(); @@ -65,6 +67,23 @@ const Intro = () => { error: '' } ); + React.useEffect(() => { + const onEvent = ({ event, args }) => { + if (event === 'CtxActionErr') { + dispatch({ type: 'error', error: args[1].args.message }); + } + if (event === 'CtxChanged') { + const state = core.getState(); + if (state.ctx.content.auth !== null) { + window.location.replace('/'); + } + } + }; + core.on('Event', onEvent); + return () => { + core.off('Event', onEvent); + }; + }, []); const loginWithFacebook = React.useCallback(() => { alert('TODO: Facebook login'); }, []); @@ -73,19 +92,61 @@ const Intro = () => { dispatch({ type: 'error', error: 'Invalid email' }); return; } - - alert('TODO: Login'); + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Login', + args: { + email: state.email, + password: state.password + } + } + }); }, [state.email, state.password]); const loginAsGuest = React.useCallback(() => { if (!state.termsAccepted) { dispatch({ type: 'error', error: 'You must accept the Terms of Service' }); return; + } else { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Logout' + } + }); + location = '#/'; } - - alert('TODO: Guest login'); - }, [state.termsAccepted, state.privacyPolicyAccepted, state.marketingAccepted]); + }, [state.termsAccepted]); const signup = React.useCallback(() => { - alert('TODO: Signup'); + if (!state.termsAccepted) { + dispatch({ type: 'error', error: 'You must accept the Terms of Service' }); + return; + } + if (!state.privacyPolicyAccepted) { + dispatch({ type: 'error', error: 'You must accept the Privacy Policy' }); + return; + } + if (state.password !== state.confirmPassword) { + dispatch({ type: 'error', error: 'Passwords do not match' }); + return; + } + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Register', + args: { + email: state.email, + password: state.password, + gdpr_consent: { + tos: state.termsAccepted, + privacy: state.privacyPolicyAccepted, + marketing: state.marketingAccepted, + time: new Date(), + from: 'web' + } + } + } + }); }, [state.email, state.password, state.confirmPassword, state.termsAccepted, state.privacyPolicyAccepted, state.marketingAccepted]); const emailOnChange = React.useCallback((event) => { dispatch({ From 62e6ddde968ca81cc45a2aa29a2e8bb5fbe9b942 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Thu, 7 Nov 2019 16:30:38 +0200 Subject: [PATCH 72/81] query params for signup and login used --- src/routes/Intro/Intro.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 396c6cee1..1989cf6ea 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -8,10 +8,9 @@ const CredentialsTextInput = require('./CredentialsTextInput'); const ConsentCheckbox = require('./ConsentCheckbox'); const styles = require('./styles'); -const LOGIN_FORM = 'LOGIN_FORM'; -const SIGNUP_FORM = 'SIGNUP_FORM'; -// TODO queryparams for signup and login -const Intro = () => { +const SIGNUP_FORM = 'signup'; + +const Intro = ({ queryParams }) => { const { core } = useServices(); const routeFocused = useRouteFocused(); const emailRef = React.useRef(); @@ -21,12 +20,12 @@ const Intro = () => { const privacyPolicyRef = React.useRef(); const marketingRef = React.useRef(); const errorRef = React.useRef(); + const [selectedForm, setSelectedForm] = React.useState(queryParams.get('form')); const [state, dispatch] = React.useReducer( (state, action) => { switch (action.type) { - case 'switch-form': + case 'reset-form': return { - form: state.form === SIGNUP_FORM ? LOGIN_FORM : SIGNUP_FORM, email: '', password: '', confirmPassword: '', @@ -57,7 +56,6 @@ const Intro = () => { } }, { - form: SIGNUP_FORM, email: '', password: '', confirmPassword: '', @@ -166,12 +164,12 @@ const Intro = () => { }); }, []); const passwordOnSubmit = React.useCallback(() => { - if (state.form === SIGNUP_FORM) { + if (selectedForm === SIGNUP_FORM) { confirmPasswordRef.current.focus(); } else { loginWithEmail(); } - }, [state.form, loginWithEmail]); + }, [selectedForm, loginWithEmail]); const confirmPasswordOnChange = React.useCallback((event) => { dispatch({ type: 'change-credentials', @@ -191,9 +189,10 @@ const Intro = () => { const toggleMarketingAccepted = React.useCallback(() => { dispatch({ type: 'toggle-checkbox', name: 'marketingAccepted' }); }, []); - const switchForm = React.useCallback(() => { - dispatch({ type: 'switch-form' }); - }, []); + React.useEffect(() => { + dispatch({ type: 'reset-form' }); + setSelectedForm(queryParams.get('form')); + }, [queryParams]); React.useEffect(() => { if (typeof state.error === 'string' && state.error.length > 0) { errorRef.current.scrollIntoView(); @@ -203,7 +202,7 @@ const Intro = () => { if (routeFocused) { emailRef.current.focus(); } - }, [state.form, routeFocused]); + }, [selectedForm, routeFocused]); return (
@@ -231,7 +230,7 @@ const Intro = () => { onSubmit={passwordOnSubmit} /> { - state.form === SIGNUP_FORM ? + selectedForm === SIGNUP_FORM ? { : null } - { - state.form === SIGNUP_FORM ? + selectedForm === SIGNUP_FORM ? : null } -
From 8718d868bfc991a549c5e6508a4bd3f7772a5c0c Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Fri, 8 Nov 2019 15:59:45 +0200 Subject: [PATCH 73/81] login with fb implemented --- src/index.html | 11 +++++++++++ src/routes/Intro/Intro.js | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 55a8694ad..919a94851 100755 --- a/src/index.html +++ b/src/index.html @@ -12,6 +12,17 @@
+ + diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 1989cf6ea..062198661 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -82,8 +82,39 @@ const Intro = ({ queryParams }) => { core.off('Event', onEvent); }; }, []); + const statusChangeCallback = React.useCallback((response) => { + if (response.status === 'connected') { + fetch(baseUrl + "/fb-login-with-token/" + encodeURIComponent(response.authResponse.accessToken), { timeout: 10 * 1000 }) + .then((resp) => { + if (resp.status < 200 || resp.status >= 300) { + throw new Error('Login failed at getting token from Stremio with status ' + resp.status); + } else { + return resp.json(); + } + }) + .then(function() { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Login', + args: { + email: state.email, + password: response.authResponse.accessToken + } + } + }); + }) + .catch(function(err) { + console.log(err); + }); + } else { + console.log('Please log into this app.'); + } + }, [state.email, state.password]); const loginWithFacebook = React.useCallback(() => { - alert('TODO: Facebook login'); + FB.login(function(response) { + statusChangeCallback(response); + }); }, []); const loginWithEmail = React.useCallback(() => { if (typeof state.email !== 'string' || state.email.length === 0) { From 5c349c4fcaae5d785b317d1373ec483890247cae Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 09:35:38 +0200 Subject: [PATCH 74/81] getState called inside onNewState --- src/common/useUser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/useUser.js b/src/common/useUser.js index fe685894f..f5681c122 100644 --- a/src/common/useUser.js +++ b/src/common/useUser.js @@ -7,6 +7,7 @@ const useUser = () => { const [user, setUser] = React.useState(state.ctx.content.auth ? state.ctx.content.auth.user : null); React.useEffect(() => { const onNewState = () => { + const state = core.getState(); setUser(state.ctx.content.auth ? state.ctx.content.auth.user : null); }; core.on('NewModel', onNewState); From 98bd2e354a992961a311de70c385fc4a7db000b8 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 09:57:56 +0200 Subject: [PATCH 75/81] not need else removed --- src/routes/Intro/Intro.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 062198661..6c63579e3 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -136,15 +136,14 @@ const Intro = ({ queryParams }) => { if (!state.termsAccepted) { dispatch({ type: 'error', error: 'You must accept the Terms of Service' }); return; - } else { - core.dispatch({ - action: 'UserOp', - args: { - userOp: 'Logout' - } - }); - location = '#/'; } + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Logout' + } + }); + location = '#/'; }, [state.termsAccepted]); const signup = React.useCallback(() => { if (!state.termsAccepted) { From 7c512aa995af8eab93ddba29c1272048d58df781 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 10:20:57 +0200 Subject: [PATCH 76/81] define baseUrl --- src/routes/Intro/Intro.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 6c63579e3..729c1eb1b 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -9,6 +9,7 @@ const ConsentCheckbox = require('./ConsentCheckbox'); const styles = require('./styles'); const SIGNUP_FORM = 'signup'; +const baseUrl = 'https://www.strem.io'; const Intro = ({ queryParams }) => { const { core } = useServices(); From d8e01779376814121e526d3e618ac8411f708fab Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 10:34:47 +0200 Subject: [PATCH 77/81] check for password length --- src/routes/Intro/Intro.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 729c1eb1b..3e29206a5 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -122,6 +122,10 @@ const Intro = ({ queryParams }) => { dispatch({ type: 'error', error: 'Invalid email' }); return; } + if (state.password.length === 0) { + dispatch({ type: 'error', error: 'Invalid password' }); + return; + } core.dispatch({ action: 'UserOp', args: { From 823994f0969cb136ad62c59ed78e93764fc32205 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 10:38:50 +0200 Subject: [PATCH 78/81] console.error used --- src/routes/Intro/Intro.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 3e29206a5..7da434434 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -106,10 +106,8 @@ const Intro = ({ queryParams }) => { }); }) .catch(function(err) { - console.log(err); + console.error(err); }); - } else { - console.log('Please log into this app.'); } }, [state.email, state.password]); const loginWithFacebook = React.useCallback(() => { From 2bcf42b601ff0e531d656950a6174c30ef0864f8 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 10:55:38 +0200 Subject: [PATCH 79/81] inline statusChangeCallback func --- src/routes/Intro/Intro.js | 57 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 7da434434..39438e717 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -83,38 +83,35 @@ const Intro = ({ queryParams }) => { core.off('Event', onEvent); }; }, []); - const statusChangeCallback = React.useCallback((response) => { - if (response.status === 'connected') { - fetch(baseUrl + "/fb-login-with-token/" + encodeURIComponent(response.authResponse.accessToken), { timeout: 10 * 1000 }) - .then((resp) => { - if (resp.status < 200 || resp.status >= 300) { - throw new Error('Login failed at getting token from Stremio with status ' + resp.status); - } else { - return resp.json(); - } - }) - .then(function() { - core.dispatch({ - action: 'UserOp', - args: { - userOp: 'Login', - args: { - email: state.email, - password: response.authResponse.accessToken - } - } - }); - }) - .catch(function(err) { - console.error(err); - }); - } - }, [state.email, state.password]); const loginWithFacebook = React.useCallback(() => { - FB.login(function(response) { - statusChangeCallback(response); + FB.login((response) => { + if (response.status === 'connected') { + fetch(baseUrl + "/fb-login-with-token/" + encodeURIComponent(response.authResponse.accessToken), { timeout: 10 * 1000 }) + .then((resp) => { + if (resp.status < 200 || resp.status >= 300) { + throw new Error('Login failed at getting token from Stremio with status ' + resp.status); + } else { + return resp.json(); + } + }) + .then(() => { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'Login', + args: { + email: state.email, + password: response.authResponse.accessToken + } + } + }); + }) + .catch((err) => { + console.error(err); + }); + } }); - }, []); + }, [state.email, state.password]); const loginWithEmail = React.useCallback(() => { if (typeof state.email !== 'string' || state.email.length === 0) { dispatch({ type: 'error', error: 'Invalid email' }); From 013ad5bcde60be0bd0cf9c95b674f6798e1118c7 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 11:49:49 +0200 Subject: [PATCH 80/81] prop form added --- src/common/useUser.js | 6 +++-- src/routes/Intro/Intro.js | 47 +++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/common/useUser.js b/src/common/useUser.js index f5681c122..dd7f97482 100644 --- a/src/common/useUser.js +++ b/src/common/useUser.js @@ -3,8 +3,10 @@ const { useServices } = require('stremio/services'); const useUser = () => { const { core } = useServices(); - const state = core.getState(); - const [user, setUser] = React.useState(state.ctx.content.auth ? state.ctx.content.auth.user : null); + const [user, setUser] = React.useState(() => { + const state = core.getState(); + return state.ctx.content.auth ? state.ctx.content.auth.user : null; + }); React.useEffect(() => { const onNewState = () => { const state = core.getState(); diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 39438e717..8b7e8983d 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -21,20 +21,23 @@ const Intro = ({ queryParams }) => { const privacyPolicyRef = React.useRef(); const marketingRef = React.useRef(); const errorRef = React.useRef(); - const [selectedForm, setSelectedForm] = React.useState(queryParams.get('form')); const [state, dispatch] = React.useReducer( (state, action) => { switch (action.type) { - case 'reset-form': - return { - email: '', - password: '', - confirmPassword: '', - termsAccepted: false, - privacyPolicyAccepted: false, - marketingAccepted: false, - error: '' - }; + case 'set-form': + if (state.form !== action.form) { + return { + form: action.form, + email: '', + password: '', + confirmPassword: '', + termsAccepted: false, + privacyPolicyAccepted: false, + marketingAccepted: false, + error: '' + }; + } + return state; case 'change-credentials': return { ...state, @@ -57,6 +60,7 @@ const Intro = ({ queryParams }) => { } }, { + form: queryParams.get('form'), email: '', password: '', confirmPassword: '', @@ -194,12 +198,12 @@ const Intro = ({ queryParams }) => { }); }, []); const passwordOnSubmit = React.useCallback(() => { - if (selectedForm === SIGNUP_FORM) { + if (state.form === SIGNUP_FORM) { confirmPasswordRef.current.focus(); } else { loginWithEmail(); } - }, [selectedForm, loginWithEmail]); + }, [state.form, loginWithEmail]); const confirmPasswordOnChange = React.useCallback((event) => { dispatch({ type: 'change-credentials', @@ -220,8 +224,7 @@ const Intro = ({ queryParams }) => { dispatch({ type: 'toggle-checkbox', name: 'marketingAccepted' }); }, []); React.useEffect(() => { - dispatch({ type: 'reset-form' }); - setSelectedForm(queryParams.get('form')); + dispatch({ type: 'set-form', form: queryParams.get('form') }); }, [queryParams]); React.useEffect(() => { if (typeof state.error === 'string' && state.error.length > 0) { @@ -232,7 +235,7 @@ const Intro = ({ queryParams }) => { if (routeFocused) { emailRef.current.focus(); } - }, [selectedForm, routeFocused]); + }, [state.form, routeFocused]); return (
@@ -260,7 +263,7 @@ const Intro = ({ queryParams }) => { onSubmit={passwordOnSubmit} /> { - selectedForm === SIGNUP_FORM ? + state.form === SIGNUP_FORM ? { : null } - { - selectedForm === SIGNUP_FORM ? + state.form === SIGNUP_FORM ? : null } -
From 204f7ead5272ec1435b4764ceba28fbc60857955 Mon Sep 17 00:00:00 2001 From: svetlagasheva Date: Mon, 11 Nov 2019 12:40:21 +0200 Subject: [PATCH 81/81] inline baseUrl --- src/common/useUser.js | 1 - src/routes/Intro/Intro.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/useUser.js b/src/common/useUser.js index dd7f97482..cb92a36b6 100644 --- a/src/common/useUser.js +++ b/src/common/useUser.js @@ -13,7 +13,6 @@ const useUser = () => { setUser(state.ctx.content.auth ? state.ctx.content.auth.user : null); }; core.on('NewModel', onNewState); - onNewState(); return () => { core.off('NewModel', onNewState); }; diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 8b7e8983d..beb35e26d 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -9,7 +9,6 @@ const ConsentCheckbox = require('./ConsentCheckbox'); const styles = require('./styles'); const SIGNUP_FORM = 'signup'; -const baseUrl = 'https://www.strem.io'; const Intro = ({ queryParams }) => { const { core } = useServices(); @@ -90,7 +89,7 @@ const Intro = ({ queryParams }) => { const loginWithFacebook = React.useCallback(() => { FB.login((response) => { if (response.status === 'connected') { - fetch(baseUrl + "/fb-login-with-token/" + encodeURIComponent(response.authResponse.accessToken), { timeout: 10 * 1000 }) + fetch('https://www.strem.io/fb-login-with-token/' + encodeURIComponent(response.authResponse.accessToken), { timeout: 10 * 1000 }) .then((resp) => { if (resp.status < 200 || resp.status >= 300) { throw new Error('Login failed at getting token from Stremio with status ' + resp.status);