From b2bfcee2f82b5b879a7267236faebf405d66998b Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 12 Dec 2019 18:34:25 +0200 Subject: [PATCH 1/9] useInLibrary hook linked with the core --- src/common/useInLibrary.js | 64 +++++++++++++++++++++++---- src/routes/MetaDetails/MetaDetails.js | 15 ++++++- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/common/useInLibrary.js b/src/common/useInLibrary.js index 2f595ed15..f9de835c0 100644 --- a/src/common/useInLibrary.js +++ b/src/common/useInLibrary.js @@ -1,12 +1,60 @@ -const useBinaryState = require('stremio/common/useBinaryState'); +const React = require('react'); +const { useServices } = require('stremio/services'); +const useModelState = require('stremio/common/useModelState'); -const useInLibrary = (id) => { - const [inLibrary, addToLibrary, removeFromLibrary, toggleInLibrary] = useBinaryState(false); - if (typeof id === 'string') { - return [inLibrary, addToLibrary, removeFromLibrary, toggleInLibrary]; - } else { - return [false, null, null, null]; - } +const useInLibrary = (metaItem) => { + const { core } = useServices(); + const initLibraryItemsState = React.useCallback(() => { + return core.getState('library_items'); + }, []); + const libraryItems = useModelState({ + model: 'library_items', + init: initLibraryItemsState + }); + const addToLibrary = React.useCallback((metaItem) => { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'AddToLibrary', + args: { + meta_item: metaItem, + now: new Date() + } + } + }); + }, []); + const removeFromLibrary = React.useCallback((id) => { + core.dispatch({ + action: 'UserOp', + args: { + userOp: 'RemoveFromLibrary', + args: { + id, + now: new Date() + } + } + }); + }, []); + const inLibrary = React.useMemo(() => { + return typeof metaItem === 'object' && metaItem !== null ? + libraryItems.ids.includes(metaItem.id) + : + false; + }, [metaItem, libraryItems]); + const toggleInLibrary = React.useMemo(() => { + if (typeof metaItem !== 'object' || metaItem === null) { + return null; + } + + return () => { + if (inLibrary) { + removeFromLibrary(metaItem.id); + } else { + addToLibrary(metaItem); + } + }; + }, [metaItem, inLibrary]); + return [inLibrary, toggleInLibrary]; }; module.exports = useInLibrary; diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index a19227a6c..a800ea1a7 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -11,7 +11,20 @@ const MetaDetails = ({ urlParams }) => { const [metaResourceRef, metaResources, selectedMetaResource] = useSelectableResource(metaDetails.selected.meta_resource_ref, metaDetails.meta_resources); const streamsResourceRef = metaDetails.selected.streams_resource_ref; const streamsResources = metaDetails.streams_resources; - const [inLibrary, , , toggleInLibrary] = useInLibrary(metaResourceRef !== null ? metaResourceRef.id : null); + const metaItem = React.useMemo(() => { + return selectedMetaResource !== null ? + selectedMetaResource.content.content + : + metaResourceRef !== null ? + { + id: metaResourceRef.id, + type: metaResourceRef.type_name, + name: '' + } + : + null; + }, [metaResourceRef, selectedMetaResource]); + const [inLibrary, toggleInLibrary] = useInLibrary(metaItem); return (
Date: Fri, 13 Dec 2019 13:34:28 +0200 Subject: [PATCH 2/9] Addons screen ui adapted to changes in core --- src/routes/Addons/Addons.js | 254 +++++++++++------------ src/routes/Addons/styles.less | 65 +++--- src/routes/Addons/useAddons.js | 25 ++- src/routes/Addons/useSelectableInputs.js | 18 +- 4 files changed, 188 insertions(+), 174 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 0a8152a17..6e47ee363 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -1,49 +1,69 @@ const React = require('react'); const Icon = require('stremio-icons/dom'); -const { Button, Multiselect, NavBar, TextInput, SharePrompt, ModalDialog } = require('stremio/common'); +const { Button, Multiselect, NavBar, TextInput, SharePrompt, ModalDialog, useBinaryState } = require('stremio/common'); const Addon = require('./Addon'); -const AddonPrompt = require('./AddonPrompt'); const useAddons = require('./useAddons'); -const useSelectedAddon = require('./useSelectedAddon'); +const useSelectableInputs = require('./useSelectableInputs'); const styles = require('./styles'); const Addons = ({ urlParams, queryParams }) => { - const inputRef = React.useRef(null); - const [query, setQuery] = React.useState(''); - const queryOnChange = React.useCallback((event) => { - setQuery(event.currentTarget.value); - }, []); - const [[addons, dropdowns, setSelectedAddon, installedAddons, error], installSelectedAddon, uninstallSelectedAddon] = useAddons(urlParams, queryParams); - const [addAddonModalOpened, setAddAddonModalOpened] = React.useState(false); - const [selectedAddon, clearSelectedAddon] = useSelectedAddon(queryParams.get('addon')); - const [sharedAddon, setSharedAddon] = React.useState(null); - const onAddAddonButtonClicked = React.useCallback(() => { - setAddAddonModalOpened(true); - }, []); - const onAddButtonClicked = React.useCallback(() => { - if (inputRef.current.value.length > 0) { - setSelectedAddon(inputRef.current.value); - setAddAddonModalOpened(false); + const addons = useAddons(urlParams); + const selectInputs = useSelectableInputs(addons); + const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false); + const addAddonUrlInputRef = React.useRef(null); + const addAddonOnSubmit = React.useCallback(() => { + if (addAddonUrlInputRef.current !== null) { + // TODO install addon } - }, [setSelectedAddon]); - const installedAddon = React.useCallback((currentAddon) => { - return installedAddons.some((installedAddon) => installedAddon.transportUrl === currentAddon.transportUrl); - }, [installedAddons]); - const toggleAddon = React.useCallback(() => { - installedAddon(selectedAddon) ? uninstallSelectedAddon(selectedAddon) : installSelectedAddon(selectedAddon); - clearSelectedAddon(); - }, [selectedAddon]); + }, []); + const addAddonModalButtons = React.useMemo(() => { + return [ + { + className: styles['cancel-button'], + label: 'Cancel', + props: { + onClick: closeAddAddonModal + } + }, + { + label: 'Add', + props: { + onClick: addAddonOnSubmit + } + } + ]; + }, []); + const [search, setSearch] = React.useState(''); + const searchInputOnChange = React.useCallback((event) => { + setSearch(event.currentTarget.value); + }, []); + const [sharedTransportUrl, setSharedTransportUrl] = React.useState(null); + const shareModalOnClose = React.useCallback(() => { + setSharedTransportUrl(null); + }, []); + const onAddonShare = React.useCallback((event) => { + setSharedTransportUrl(event.dataset.transportUrl); + }, []); + React.useLayoutEffect(() => { + closeAddAddonModal(null); + setSearch(''); + setSharedTransportUrl(null); + }, [urlParams, queryParams]); return (
-
- - {dropdowns.map((dropdown, index) => ( - + {selectInputs.map((selectInput, index) => ( + ))}
-
- { - error !== null ? + { + addons.selectable.catalogs.length === 0 && addons.catalog_resource === null ? +
+ No addons +
+ : + addons.catalog_resource === null ?
- {error.type}{error.type === 'Other' ? ` - ${error.content}` : null} + No select
: - 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)} - /> - )) - : + addons.catalog_resource.content.type === 'Err' ?
- Loading + Addons could not be loaded
- } -
- { - addAddonModalOpened ? - setAddAddonModalOpened(false) - } - }, - { - label: 'Add', - props: { - title: 'Add', - onClick: onAddButtonClicked - } - } - ]} - onCloseRequest={() => setAddAddonModalOpened(false)} - > - - - : - null - } - { - selectedAddon !== null ? - - - - : - null - } - { - sharedAddon !== null ? - setSharedAddon(null)}> - setSharedAddon(null)} - /> - - : - null + : + addons.catalog_resource.content.type === 'Loading' ? +
+ Loading +
+ : +
+ { + addons.catalog_resource.content.content + .filter((addon) => { + return search.length === 0 || + ( + (typeof addon.manifest.name === 'string' && addon.manifest.name.toLowerCase().includes(search.toLowerCase())) || + (typeof addon.manifest.description === 'string' && addon.manifest.description.toLowerCase().includes(search.toLowerCase())) + ); + }) + .map((addon, index) => ( + + )) + } +
}
+ { + addAddonModalOpen ? + + + + : + null + } + { + typeof sharedTransportUrl === 'string' ? + + + + : + null + }
); }; diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index ff3b7f2c1..b17fbfd46 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -1,3 +1,7 @@ +:import('~stremio/common/Multiselect/styles.less') { + multiselect-menu-container: menu-container; +} + .addons-container { display: flex; flex-direction: column; @@ -16,11 +20,12 @@ display: flex; flex-direction: column; - .top-bar-container { + .selectable-inputs-container { flex: none; + align-self: stretch; display: flex; flex-direction: row; - margin: 2rem; + padding: 1.5rem; overflow: visible; .add-button-container { @@ -30,7 +35,7 @@ align-items: center; height: 3rem; max-width: 15rem; - margin-right: 1rem; + margin-right: 1.5rem; padding: 0 1rem; background-color: var(--color-signal5); @@ -40,8 +45,8 @@ .icon { flex: none; - width: 1.5rem; - height: 1.5rem; + width: 1.2rem; + height: 1.2rem; margin-right: 1rem; fill: var(--color-surfacelighter); } @@ -56,12 +61,17 @@ } } - .dropdown { + .select-input-container { flex-grow: 0; flex-shrink: 1; flex-basis: 15rem; height: 3rem; - margin-right: 1rem; + margin-right: 1.5rem; + + .multiselect-menu-container { + max-height: calc(3.2rem * 7); + overflow: auto; + } } .search-bar-container { @@ -72,7 +82,6 @@ flex-direction: row; align-items: center; height: 3rem; - margin-right: 1rem; padding: 0 1rem; background-color: var(--color-backgroundlighter); cursor: text; @@ -82,7 +91,7 @@ } .icon { - display: block; + flex: none; width: 1.2rem; height: 1.2rem; margin-right: 1rem; @@ -91,7 +100,6 @@ .search-input { flex: 1; - align-self: stretch; color: var(--color-surfacelighter); &::placeholder { @@ -103,32 +111,31 @@ } } - .addons-list-container { + .message-container { flex: 1; align-self: stretch; - padding: 0 2rem; + padding: 0 1.5rem; + font-size: 2rem; + color: var(--color-surfacelighter); + } + + .addons-container { + flex: 1; + align-self: stretch; + padding: 0 1.5rem; overflow-y: auto; .addon { - width: 100%; - margin-bottom: 2rem; - } - - .message-container { - padding: 0 2rem; - font-size: 2rem; - color: var(--color-surfacelighter); + margin-bottom: 1.5rem; } } } } -.add-addon-prompt-container { - .url-content { - flex: 1; - width: 100%; - padding: 0.5rem; - font-size: 0.9rem; +.add-addon-modal-container { + .addon-url-input { + width: 25rem; + padding: 0.5rem 1rem; color: var(--color-surfacedark); border: thin solid var(--color-surface); } @@ -138,8 +145,8 @@ } } -.addon-prompt-container { - .cancel-button { - background-color: var(--color-surfacedark); +.share-modal-container { + .share-prompt-container { + width: 25rem; } } \ No newline at end of file diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index b29b23cd4..aad7086bc 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -15,8 +15,29 @@ const initAddonsState = () => ({ const mapAddonsStateWithCtx = (addons, ctx) => { const selectable = addons.selectable; - const catalog_resource = addons.catalog_resource; - // TODO add MY catalogId replace catalog content if resource catalog id is MY + // TODO replace catalog content if resource catalog id is MY + const catalog_resource = addons.catalog_resource !== null && addons.catalog_resource.content.type === 'Ready' ? + { + ...addons.catalog_resource, + content: { + ...addons.catalog_resource.content, + content: addons.catalog_resource.content.content.map((descriptor) => ({ + transportUrl: descriptor.transportUrl, + installed: ctx.content.addons.some((addon) => addon.transportUrl === descriptor.transportUrl), + manifest: { + id: descriptor.manifest.id, + name: descriptor.manifest.name, + version: descriptor.manifest.version, + logo: descriptor.manifest.logo, + description: descriptor.manifest.description, + types: descriptor.manifest.types, + catalogs: descriptor.manifest.catalogs, + } + })) + } + } + : + addons.catalog_resource; return { selectable, catalog_resource }; }; diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js index 8376fa846..1b1c4dd15 100644 --- a/src/routes/Addons/useSelectableInputs.js +++ b/src/routes/Addons/useSelectableInputs.js @@ -15,18 +15,6 @@ const equalWithouExtra = (request1, request2) => { }; const mapSelectableInputs = (addons) => { - const selectedCatalogRequest = addons.catalog_resource !== null ? - addons.catalog_resource.request - : - { - base: null, - path: { - resource: 'addon_catalog', - id: null, - type_name: null, - extra: [] - } - }; const catalogSelect = { title: 'Select catalog', options: addons.selectable.catalogs @@ -36,7 +24,8 @@ const mapSelectableInputs = (addons) => { })), selected: addons.selectable.catalogs .filter(({ load_request: { path: { id } } }) => { - return id === selectedCatalogRequest.path.id; + return addons.catalog_resource !== null && + addons.catalog_resource.request.path.id === id; }) .map(({ load_request }) => JSON.stringify(load_request)), onSelect: (event) => { @@ -52,7 +41,8 @@ const mapSelectableInputs = (addons) => { })), selected: addons.selectable.types .filter(({ load_request }) => { - return equalWithouExtra(load_request, selectedCatalogRequest); + return addons.catalog_resource !== null && + equalWithouExtra(addons.catalog_resource.request, load_request); }) .map(({ load_request }) => JSON.stringify(load_request)), onSelect: (event) => { From 5939a98bc633ae062e9cc3f4ac8be4045531f03b Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 13 Dec 2019 18:12:00 +0200 Subject: [PATCH 3/9] redirect to addon details on submit --- src/routes/Addons/Addons.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 6e47ee363..504145fcf 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -13,9 +13,17 @@ const Addons = ({ urlParams, queryParams }) => { const addAddonUrlInputRef = React.useRef(null); const addAddonOnSubmit = React.useCallback(() => { if (addAddonUrlInputRef.current !== null) { - // TODO install addon + const queryParams = new URLSearchParams([['addon', addAddonUrlInputRef.current.value]]); + if (typeof urlParams.addonTransportUrl === 'string' && typeof urlParams.catalogId === 'string' && typeof urlParams.type === 'string') { + const addonTransportUrl = encodeURIComponent(urlParams.addonTransportUrl); + const catalogId = encodeURIComponent(urlParams.catalogId); + const type = encodeURIComponent(urlParams.type); + window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`); + } else { + window.location.replace(`#/addons?${queryParams}`); + } } - }, []); + }, [urlParams]); const addAddonModalButtons = React.useMemo(() => { return [ { @@ -32,7 +40,7 @@ const Addons = ({ urlParams, queryParams }) => { } } ]; - }, []); + }, [addAddonOnSubmit]); const [search, setSearch] = React.useState(''); const searchInputOnChange = React.useCallback((event) => { setSearch(event.currentTarget.value); From bd03788c3d96aaed9f3f78bc676893095c1b5e42 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 13 Dec 2019 18:14:30 +0200 Subject: [PATCH 4/9] navigate to addon details moved in a separate function --- src/routes/Addons/Addons.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 504145fcf..054fd5b57 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -7,23 +7,26 @@ const useSelectableInputs = require('./useSelectableInputs'); const styles = require('./styles'); const Addons = ({ urlParams, queryParams }) => { + const navigateToAddonDetails = React.useCallback((transportUrl) => { + const queryParams = new URLSearchParams([['addon', transportUrl]]); + if (typeof urlParams.addonTransportUrl === 'string' && typeof urlParams.catalogId === 'string' && typeof urlParams.type === 'string') { + const addonTransportUrl = encodeURIComponent(urlParams.addonTransportUrl); + const catalogId = encodeURIComponent(urlParams.catalogId); + const type = encodeURIComponent(urlParams.type); + window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`); + } else { + window.location.replace(`#/addons?${queryParams}`); + } + }, [urlParams]); const addons = useAddons(urlParams); const selectInputs = useSelectableInputs(addons); const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false); const addAddonUrlInputRef = React.useRef(null); const addAddonOnSubmit = React.useCallback(() => { if (addAddonUrlInputRef.current !== null) { - const queryParams = new URLSearchParams([['addon', addAddonUrlInputRef.current.value]]); - if (typeof urlParams.addonTransportUrl === 'string' && typeof urlParams.catalogId === 'string' && typeof urlParams.type === 'string') { - const addonTransportUrl = encodeURIComponent(urlParams.addonTransportUrl); - const catalogId = encodeURIComponent(urlParams.catalogId); - const type = encodeURIComponent(urlParams.type); - window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`); - } else { - window.location.replace(`#/addons?${queryParams}`); - } + navigateToAddonDetails(addAddonUrlInputRef.current.value); } - }, [urlParams]); + }, [navigateToAddonDetails]); const addAddonModalButtons = React.useMemo(() => { return [ { From 160b8b4a5b1f9c0a5e511126015d48a8ce83a010 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 13 Dec 2019 18:16:41 +0200 Subject: [PATCH 5/9] onToggle cb added to Addon --- src/routes/Addons/Addons.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 054fd5b57..4dfaff765 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -55,6 +55,9 @@ const Addons = ({ urlParams, queryParams }) => { const onAddonShare = React.useCallback((event) => { setSharedTransportUrl(event.dataset.transportUrl); }, []); + const onAddonToggle = React.useCallback((event) => { + navigateToAddonDetails(event.dataset.transportUrl); + }, []); React.useLayoutEffect(() => { closeAddAddonModal(null); setSearch(''); @@ -128,8 +131,8 @@ const Addons = ({ urlParams, queryParams }) => { description={addon.manifest.description} types={addon.manifest.types} version={addon.manifest.version} - transportUrl={addon.transportUrl} onShare={onAddonShare} + onToggle={onAddonToggle} dataset={{ transportUrl: addon.transportUrl }} /> )) From 5282ad936799bc45aff763f2a0e1a720c42e3b03 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 13 Dec 2019 18:19:17 +0200 Subject: [PATCH 6/9] addons list ui fixed --- src/routes/Addons/Addons.js | 2 +- src/routes/Addons/styles.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 4dfaff765..c99f7bc30 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -111,7 +111,7 @@ const Addons = ({ urlParams, queryParams }) => { Loading
: -
+
{ addons.catalog_resource.content.content .filter((addon) => { diff --git a/src/routes/Addons/styles.less b/src/routes/Addons/styles.less index b17fbfd46..f7ef52dcd 100644 --- a/src/routes/Addons/styles.less +++ b/src/routes/Addons/styles.less @@ -119,7 +119,7 @@ color: var(--color-surfacelighter); } - .addons-container { + .addons-list-container { flex: 1; align-self: stretch; padding: 0 1.5rem; From 91c692a0c46e0eae0afa8633ce9555f5da863ed5 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 13 Dec 2019 18:20:03 +0200 Subject: [PATCH 7/9] useSelectedAddon dropped --- src/routes/Addons/useSelectedAddon.js | 35 --------------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/routes/Addons/useSelectedAddon.js diff --git a/src/routes/Addons/useSelectedAddon.js b/src/routes/Addons/useSelectedAddon.js deleted file mode 100644 index 5bffaec37..000000000 --- a/src/routes/Addons/useSelectedAddon.js +++ /dev/null @@ -1,35 +0,0 @@ -const React = require('react'); -const UrlUtils = require('url'); -const { routesRegexp, useLocationHash, useRouteActive } = require('stremio/common'); - -const useSelectedAddon = (transportUrl) => { - const [addon, setAddon] = React.useState(null); - const locationHash = useLocationHash(); - const active = useRouteActive(routesRegexp.addons.regexp); - React.useEffect(() => { - if (typeof transportUrl !== 'string') { - setAddon(null); - return; - } - - 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 || ''); - queryParams.delete('addon'); - if ([...queryParams].length !== 0) { - window.location.replace(`#${pathname}?${queryParams.toString()}`); - } else { - window.location.replace(`#${pathname}`); - } - setAddon(null); - } - }, [active, locationHash]); - return [addon, clear, setAddon]; -}; - -module.exports = useSelectedAddon; From 3e1ab699f54b5d415932018af3afa07a1dcd9d9c Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 15 Dec 2019 01:38:16 +0200 Subject: [PATCH 8/9] Addon layout changed --- src/routes/Addons/Addon/Addon.js | 72 ++++++++++++++++++++--------- src/routes/Addons/Addon/styles.less | 25 +++------- src/routes/Addons/Addons.js | 57 ++++++++++++++--------- src/routes/Addons/useAddons.js | 3 +- 4 files changed, 93 insertions(+), 64 deletions(-) diff --git a/src/routes/Addons/Addon/Addon.js b/src/routes/Addons/Addon/Addon.js index 023c17732..d646ed2b9 100644 --- a/src/routes/Addons/Addon/Addon.js +++ b/src/routes/Addons/Addon/Addon.js @@ -2,24 +2,54 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { Button } = require('stremio/common'); +const { Button, Image } = require('stremio/common'); const styles = require('./styles'); -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); +const Addon = ({ className, id, name, version, logo, description, types, installed, onToggle, onShare, dataset }) => { + const toggleButtonOnClick = React.useCallback((event) => { + if (typeof onToggle === 'function') { + onToggle({ + type: 'toggle', + nativeEvent: event.nativeEvent, + reactEvent: event, + dataset: dataset + }); } - }, [toggle]); + }, [onToggle, dataset]); + const shareButtonOnClick = React.useCallback((event) => { + if (typeof onShare === 'function') { + onShare({ + type: 'share', + nativeEvent: event.nativeEvent, + reactEvent: event, + dataset: dataset + }); + } + }, [onShare, dataset]); + const onKeyDown = React.useCallback((event) => { + if (event.key === 'Enter' && typeof onToggle === 'function') { + onToggle({ + type: 'toggle', + nativeEvent: event.nativeEvent, + reactEvent: event, + dataset: dataset + }); + } + }, [onToggle, dataset]); + const renderLogoFallback = React.useMemo(() => () => { + return ( + + ); + }, []); return ( - - @@ -68,14 +98,14 @@ Addon.propTypes = { className: PropTypes.string, id: PropTypes.string, name: PropTypes.string, + version: PropTypes.string, logo: PropTypes.string, description: PropTypes.string, types: PropTypes.arrayOf(PropTypes.string), - version: PropTypes.string, - transportUrl: PropTypes.string, installed: PropTypes.bool, - toggle: PropTypes.func, - onShareButtonClicked: PropTypes.func + onToggle: PropTypes.func, + onShare: PropTypes.func, + dataset: PropTypes.objectOf(PropTypes.string) }; module.exports = Addon; diff --git a/src/routes/Addons/Addon/styles.less b/src/routes/Addons/Addon/styles.less index 2ae172544..e57eea428 100644 --- a/src/routes/Addons/Addon/styles.less +++ b/src/routes/Addons/Addon/styles.less @@ -1,7 +1,6 @@ .addon-container { display: flex; flex-direction: row; - flex-wrap: wrap; align-items: flex-start; padding: 1rem; background-color: var(--color-backgroundlighter); @@ -17,6 +16,7 @@ display: block; width: 100%; height: 100%; + padding: 0.5rem; object-fit: contain; object-position: center; } @@ -31,14 +31,13 @@ } .info-container { - flex-grow: 1000; + flex-grow: 1; flex-shrink: 1; flex-basis: 0; display: flex; flex-direction: row; flex-wrap: wrap; align-items: baseline; - min-width: 40rem; padding: 0 0.5rem; .name-container { @@ -47,7 +46,7 @@ flex-basis: auto; padding: 0 0.5rem; max-height: 3.6em; - font-size: 1.5rem; + font-size: 1.6rem; color: var(--color-surfacelighter); } @@ -55,6 +54,7 @@ flex-grow: 1; flex-shrink: 1; flex-basis: auto; + margin-top: 0.5rem; padding: 0 0.5rem; max-height: 2.4em; color: var(--color-surfacelight); @@ -83,22 +83,14 @@ } .buttons-container { - flex-grow: 1; - flex-shrink: 0; - flex-basis: 0; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-end; - min-width: 17rem; + flex: none; + width: 17rem; .install-button-container, .uninstall-button-container, .share-button-container { - flex: none; display: flex; flex-direction: row; align-items: center; justify-content: center; - width: 17rem; height: 3.5rem; padding: 0 1rem; @@ -106,13 +98,8 @@ margin-top: 1rem; } - &:not(:last-child) { - margin-right: 1rem; - } - .icon { flex: none; - display: block; width: 1.5rem; height: 1.5rem; margin-right: 1rem; diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index c99f7bc30..083c16747 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -6,27 +6,32 @@ const useAddons = require('./useAddons'); const useSelectableInputs = require('./useSelectableInputs'); const styles = require('./styles'); +const navigateToAddonDetails = (addonsCatalogRequest, transportUrl) => { + const queryParams = new URLSearchParams([['addon', transportUrl]]); + if (addonsCatalogRequest !== null) { + const addonTransportUrl = encodeURIComponent(addonsCatalogRequest.base); + const catalogId = encodeURIComponent(addonsCatalogRequest.path.id); + const type = encodeURIComponent(addonsCatalogRequest.path.type_name); + window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`); + } else { + window.location.replace(`#/addons?${queryParams}`); + } +}; + const Addons = ({ urlParams, queryParams }) => { - const navigateToAddonDetails = React.useCallback((transportUrl) => { - const queryParams = new URLSearchParams([['addon', transportUrl]]); - if (typeof urlParams.addonTransportUrl === 'string' && typeof urlParams.catalogId === 'string' && typeof urlParams.type === 'string') { - const addonTransportUrl = encodeURIComponent(urlParams.addonTransportUrl); - const catalogId = encodeURIComponent(urlParams.catalogId); - const type = encodeURIComponent(urlParams.type); - window.location.replace(`#/addons/${addonTransportUrl}/${catalogId}/${type}?${queryParams}`); - } else { - window.location.replace(`#/addons?${queryParams}`); - } - }, [urlParams]); const addons = useAddons(urlParams); const selectInputs = useSelectableInputs(addons); const [addAddonModalOpen, openAddAddonModal, closeAddAddonModal] = useBinaryState(false); const addAddonUrlInputRef = React.useRef(null); const addAddonOnSubmit = React.useCallback(() => { if (addAddonUrlInputRef.current !== null) { - navigateToAddonDetails(addAddonUrlInputRef.current.value); + const addonsCatalogRequest = addons.catalog_resource !== null ? + addons.catalog_resource.request + : + null; + navigateToAddonDetails(addonsCatalogRequest, addAddonUrlInputRef.current.value); } - }, [navigateToAddonDetails]); + }, [addons]); const addAddonModalButtons = React.useMemo(() => { return [ { @@ -49,19 +54,23 @@ const Addons = ({ urlParams, queryParams }) => { setSearch(event.currentTarget.value); }, []); const [sharedTransportUrl, setSharedTransportUrl] = React.useState(null); - const shareModalOnClose = React.useCallback(() => { + const clearSharedTransportUrl = React.useCallback(() => { setSharedTransportUrl(null); }, []); const onAddonShare = React.useCallback((event) => { setSharedTransportUrl(event.dataset.transportUrl); }, []); const onAddonToggle = React.useCallback((event) => { - navigateToAddonDetails(event.dataset.transportUrl); - }, []); + const addonsCatalogRequest = addons.catalog_resource !== null ? + addons.catalog_resource.request + : + null; + navigateToAddonDetails(addonsCatalogRequest, event.dataset.transportUrl); + }, [addons]); React.useLayoutEffect(() => { - closeAddAddonModal(null); + closeAddAddonModal(); setSearch(''); - setSharedTransportUrl(null); + clearSharedTransportUrl(); }, [urlParams, queryParams]); return (
@@ -127,12 +136,13 @@ const Addons = ({ urlParams, queryParams }) => { className={styles['addon']} id={addon.manifest.id} name={addon.manifest.name} + version={addon.manifest.version} logo={addon.manifest.logo} description={addon.manifest.description} types={addon.manifest.types} - version={addon.manifest.version} - onShare={onAddonShare} + installed={addon.installed} onToggle={onAddonToggle} + onShare={onAddonShare} dataset={{ transportUrl: addon.transportUrl }} /> )) @@ -163,8 +173,11 @@ const Addons = ({ urlParams, queryParams }) => { - + onCloseRequest={clearSharedTransportUrl}> + : null diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index aad7086bc..57be65b45 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -30,8 +30,7 @@ const mapAddonsStateWithCtx = (addons, ctx) => { version: descriptor.manifest.version, logo: descriptor.manifest.logo, description: descriptor.manifest.description, - types: descriptor.manifest.types, - catalogs: descriptor.manifest.catalogs, + types: descriptor.manifest.types } })) } From d78f2f66964780cfdb83afdc3baaf1d5c8b3ccba Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 16 Dec 2019 11:22:55 +0200 Subject: [PATCH 9/9] useAddonDetails hook implemented --- src/routes/Addons/useAddonDetails.js | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/routes/Addons/useAddonDetails.js diff --git a/src/routes/Addons/useAddonDetails.js b/src/routes/Addons/useAddonDetails.js new file mode 100644 index 000000000..afa45eceb --- /dev/null +++ b/src/routes/Addons/useAddonDetails.js @@ -0,0 +1,48 @@ +const React = require('react'); +const { useModelState } = require('stremio/common'); + +const initAddonDetailsState = () => ({ + descriptor: null +}); + +const mapAddonDetailsStateWithCtx = (addonDetails, ctx) => { + const descriptor = addonDetails.descriptor !== null && addonDetails.descriptor.content.type === 'Ready' ? + { + ...addonDetails.descriptor, + content: { + ...addonDetails.descriptor.content, + installed: ctx.content.addons.some((addon) => addon.transportUrl === addonDetails.descriptor.transport_url), + } + } + : + addonDetails.descriptor; + return { descriptor }; +}; + +const useAddonDetails = (queryParams) => { + const loadAddonDetailsAction = React.useMemo(() => { + if (queryParams.has('addon')) { + return { + action: 'Load', + args: { + load: 'AddonDetails', + args: { + transport_url: queryParams.get('addon') + } + } + }; + } else { + return { + action: 'Unload' + }; + } + }, [queryParams]); + return useModelState({ + model: 'addon_details', + action: loadAddonDetailsAction, + mapWithCtx: mapAddonDetailsStateWithCtx, + init: initAddonDetailsState, + }); +}; + +module.exports = useAddonDetails;