diff --git a/package-lock.json b/package-lock.json index c40f9af6a..3b2684f62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@stremio/stremio-colors": "5.0.1", - "@stremio/stremio-core-web": "0.46.3", + "@stremio/stremio-core-web": "0.47.0", "@stremio/stremio-icons": "5.2.0", "@stremio/stremio-video": "0.0.38", "a-color-picker": "1.2.1", @@ -2969,9 +2969,9 @@ "license": "MIT" }, "node_modules/@stremio/stremio-core-web": { - "version": "0.46.3", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.46.3.tgz", - "integrity": "sha512-5us8KQQ0EJ2xjLukcLu3yia7fa8BIjxjOwbMebpSLGEomq6MzVQMmRGp/7rId2ggfIej2Zwf/GBVepZ2/nLE4A==", + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.47.0.tgz", + "integrity": "sha512-//wXNpVFnKjVegPjAKPLz+SanGvk/p+4cuDAvZst5igi+wCw/wbsxOssq8HZbq1QvaeFQdpttW0HXbkzLcyf8Q==", "dependencies": { "@babel/runtime": "7.16.0" } diff --git a/package.json b/package.json index e36a69c38..dca1280da 100755 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@stremio/stremio-colors": "5.0.1", - "@stremio/stremio-core-web": "0.46.3", + "@stremio/stremio-core-web": "0.47.0", "@stremio/stremio-icons": "5.2.0", "@stremio/stremio-video": "0.0.38", "a-color-picker": "1.2.1", diff --git a/src/App/styles.less b/src/App/styles.less index 4493c3c9d..a2581a88f 100644 --- a/src/App/styles.less +++ b/src/App/styles.less @@ -184,6 +184,10 @@ html { .toasts-container { padding: 0 1rem; } + + .tooltip-container { + display: none; + } } } } diff --git a/src/common/AddonDetailsModal/AddonDetailsModal.js b/src/common/AddonDetailsModal/AddonDetailsModal.js index 3f2fe8c52..085925aac 100644 --- a/src/common/AddonDetailsModal/AddonDetailsModal.js +++ b/src/common/AddonDetailsModal/AddonDetailsModal.js @@ -28,6 +28,7 @@ function withRemoteAndLocalAddon(AddonDetails) { id={addon.manifest.id} name={addon.manifest.name} version={addon.manifest.version} + background={addon.manifest.background} logo={addon.manifest.logo} description={addon.manifest.description} types={addon.manifest.types} @@ -132,8 +133,11 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { null; return toggleButton !== null ? configureButton ? [cancelButton, configureButton, toggleButton] : [cancelButton, toggleButton] : [cancelButton]; }, [addonDetails, onCloseRequest]); + const modalBackground = React.useMemo(() => { + return addonDetails.remoteAddon?.content.type === 'Ready' ? addonDetails.remoteAddon.content.content.manifest.background : null; + }, [addonDetails.remoteAddon]); return ( - + { addonDetails.selected === null ?
diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js index 50bc770c9..aeaa51462 100644 --- a/src/common/CONSTANTS.js +++ b/src/common/CONSTANTS.js @@ -81,6 +81,11 @@ const EXTERNAL_PLAYERS = [ value: 'outplayer', platforms: ['ios'], }, + { + label: 'M3U Playlist', + value: 'm3u', + platforms: ['ios', 'android', 'windows', 'linux', 'macos'], + }, ]; module.exports = { diff --git a/src/common/MetaItem/MetaItem.js b/src/common/MetaItem/MetaItem.js index 00e2c6c99..aef6daa25 100644 --- a/src/common/MetaItem/MetaItem.js +++ b/src/common/MetaItem/MetaItem.js @@ -102,7 +102,7 @@ const MetaItem = React.memo(({ className, type, name, poster, posterShape, poste { progress > 0 ?
-
+
: diff --git a/src/common/MetaItem/styles.less b/src/common/MetaItem/styles.less index f31724970..8e429dd1e 100644 --- a/src/common/MetaItem/styles.less +++ b/src/common/MetaItem/styles.less @@ -317,11 +317,14 @@ .title-label { flex: 1; - max-height: 2.4em; padding-left: 1.5rem; font-weight: 600; text-align: center; color: var(--primary-foreground-color); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; &:only-child { padding: 0 0.5rem; diff --git a/src/common/ModalDialog/ModalDialog.js b/src/common/ModalDialog/ModalDialog.js index e7e2e8056..52d0a2513 100644 --- a/src/common/ModalDialog/ModalDialog.js +++ b/src/common/ModalDialog/ModalDialog.js @@ -9,7 +9,7 @@ const { default: Icon } = require('@stremio/stremio-icons/react'); const { Modal } = require('stremio-router'); const styles = require('./styles'); -const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, ...props }) => { +const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, background, ...props }) => { const routeFocused = useRouteFocused(); const modalsContainer = useModalsContainer(); const modalContainerRef = React.useRef(null); @@ -59,41 +59,44 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ return (
+
- { - typeof title === 'string' && title.length > 0 ? -
{title}
- : - null - }
- {children} + { + typeof title === 'string' && title.length > 0 ? +
{title}
+ : + null + } +
+ {children} +
+ { + Array.isArray(buttons) && buttons.length > 0 ? +
+ {buttons.map(({ className, label, icon, props }, index) => ( + + ))} +
+ : + null + }
- { - Array.isArray(buttons) && buttons.length > 0 ? -
- {buttons.map(({ className, label, icon, props }, index) => ( - - ))} -
- : - null - }
); @@ -102,6 +105,7 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ ModalDialog.propTypes = { className: PropTypes.string, title: PropTypes.string, + background: PropTypes.string, buttons: PropTypes.arrayOf(PropTypes.shape({ className: PropTypes.string, label: PropTypes.string, diff --git a/src/common/ModalDialog/styles.less b/src/common/ModalDialog/styles.less index 796685b38..c3d1ae0d4 100644 --- a/src/common/ModalDialog/styles.less +++ b/src/common/ModalDialog/styles.less @@ -21,6 +21,18 @@ background-color: var(--modal-background-color); box-shadow: var(--outer-glow); + .modal-dialog-background { + z-index: 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-size: cover; + background-position: center; + opacity: 0.1; + } + .close-button-container { position: absolute; top: 0.5rem; @@ -38,7 +50,8 @@ opacity: 0.4; } - &:hover, &:focus { + &:hover, + &:focus { .icon { opacity: 1; color: var(--primary-foreground-color); @@ -50,36 +63,43 @@ } } - .title-container { - flex: 1 0 auto; - display: flex; - align-items: center; - height: 4.5rem; - font-size: 1.2rem; - font-weight: 500; - color: var(--primary-foreground-color); - } - .modal-dialog-content { - flex: 1; - align-self: stretch; - overflow-y: auto; - padding: 2rem 0; + z-index: 1; + position: relative; + height: 100%; + width: 100%; - &:last-child { - margin-bottom: 2rem; + .title-container { + flex: 1 0 auto; + display: flex; + align-items: center; + height: 4.5rem; + font-size: 1.2rem; + font-weight: 500; + color: var(--primary-foreground-color); } - } - .buttons-container { - flex: none; - align-self: stretch; - display: flex; - flex-direction: row; - flex-wrap: wrap; + .modal-dialog-content { + flex: 1; + align-self: stretch; + overflow-y: auto; + padding: 2rem 0; - &:last-child { - margin: 2rem 0; + &:last-child { + margin-bottom: 2rem; + } + } + + .buttons-container { + flex: none; + align-self: stretch; + display: flex; + flex-direction: row; + flex-wrap: wrap; + + &:last-child { + margin: 2rem 0; + } } } } diff --git a/src/routes/Board/useBoard.js b/src/routes/Board/useBoard.js index 06f043d52..30aa1ff3b 100644 --- a/src/routes/Board/useBoard.js +++ b/src/routes/Board/useBoard.js @@ -22,7 +22,7 @@ const useBoard = () => { } }, 'board'); }, []); - const board = useModelState({ model: 'board', timeout: 1500, action }); + const board = useModelState({ model: 'board', action }); return [board, loadRange]; }; diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js index e45dd585a..e7af3a07d 100644 --- a/src/routes/Library/Library.js +++ b/src/routes/Library/Library.js @@ -5,11 +5,13 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); const NotFound = require('stremio/routes/NotFound'); -const { Button, DelayedRenderer, Multiselect, MainNavBars, LibItem, Image, ModalDialog, PaginationInput, useProfile, useNotifications, routesRegexp, useBinaryState, withCoreSuspender } = require('stremio/common'); +const { Button, DelayedRenderer, Multiselect, MainNavBars, LibItem, Image, ModalDialog, useProfile, useNotifications, routesRegexp, useOnScrollToBottom, useBinaryState, withCoreSuspender } = require('stremio/common'); const useLibrary = require('./useLibrary'); const useSelectableInputs = require('./useSelectableInputs'); const styles = require('./styles'); +const SCROLL_TO_BOTTOM_TRESHOLD = 400; + function withModel(Library) { const withModel = ({ urlParams, queryParams }) => { const model = React.useMemo(() => { @@ -46,9 +48,21 @@ function withModel(Library) { const Library = ({ model, urlParams, queryParams }) => { const profile = useProfile(); const notifications = useNotifications(); - const library = useLibrary(model, urlParams, queryParams); - const [typeSelect, sortSelect, paginationInput] = useSelectableInputs(library); + const [library, loadNextPage] = useLibrary(model, urlParams, queryParams); + const [typeSelect, sortSelect, hasNextPage] = useSelectableInputs(library); const [inputsModalOpen, openInputsModal, closeInputsModal] = useBinaryState(false); + const scrollContainerRef = React.useRef(null); + const onScrollToBottom = React.useCallback(() => { + if (hasNextPage) { + loadNextPage(); + } + }, [hasNextPage, loadNextPage]); + const onScroll = useOnScrollToBottom(onScrollToBottom, SCROLL_TO_BOTTOM_TRESHOLD); + React.useLayoutEffect(() => { + if (profile.auth !== null && library.selected && library.selected.request.page === 1) { + scrollContainerRef.current.scrollTop = 0; + } + }, [profile.auth, library.selected]); return (
@@ -58,12 +72,6 @@ const Library = ({ model, urlParams, queryParams }) => {
- { - paginationInput !== null ? - - : - null - } @@ -107,7 +115,7 @@ const Library = ({ model, urlParams, queryParams }) => {
Empty {model === 'library' ? 'Library' : 'Continue Watching'}
: -
+
{library.catalog.map((libItem, index) => ( ))} diff --git a/src/routes/Library/styles.less b/src/routes/Library/styles.less index 87062fb9b..ee89347ae 100644 --- a/src/routes/Library/styles.less +++ b/src/routes/Library/styles.less @@ -7,13 +7,6 @@ multiselect-menu-container: menu-container; } -:import('~stremio/common/PaginationInput/styles.less') { - pagination-prev-button-container: prev-button-container; - pagination-next-button-container: next-button-container; - pagination-button-icon: icon; - pagination-label: label; -} - :import('~stremio/common/ModalDialog/styles.less') { selectable-inputs-modal-container: modal-dialog-container; selectable-inputs-modal-content: modal-dialog-content; @@ -75,26 +68,6 @@ .spacing { flex: 1; } - - .pagination-input { - flex: none; - height: 3rem; - margin-left: 1.5rem; - - .pagination-prev-button-container, .pagination-next-button-container { - width: 3rem; - height: 3rem; - - .pagination-button-icon { - width: 1rem; - height: 1rem; - } - } - - .pagination-label { - width: 3rem; - } - } } .message-container { @@ -277,10 +250,6 @@ display: none; } - .pagination-input { - margin-left: 0; - } - .filter-container { display: flex; } diff --git a/src/routes/Library/useLibrary.js b/src/routes/Library/useLibrary.js index 8b6dee216..a7e1aeb68 100644 --- a/src/routes/Library/useLibrary.js +++ b/src/routes/Library/useLibrary.js @@ -1,9 +1,19 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useServices } = require('stremio/services'); const { useModelState } = require('stremio/common'); const useLibrary = (model, urlParams, queryParams) => { + const { core } = useServices(); + const loadNextPage = React.useCallback(() => { + core.transport.dispatch({ + action: 'LibraryWithFilters', + args: { + action: 'LoadNextPage', + } + }, 'library'); + }, []); const action = React.useMemo(() => ({ action: 'Load', args: { @@ -12,12 +22,12 @@ const useLibrary = (model, urlParams, queryParams) => { request: { type: typeof urlParams.type === 'string' ? urlParams.type : null, sort: queryParams.has('sort') ? queryParams.get('sort') : undefined, - page: queryParams.has('page') ? parseInt(queryParams.get('page'), 10) : undefined } } } }), [urlParams, queryParams]); - return useModelState({ model, action }); + const library = useModelState({ model, action }); + return [library, loadNextPage]; }; module.exports = useLibrary; diff --git a/src/routes/Library/useSelectableInputs.js b/src/routes/Library/useSelectableInputs.js index f98307479..2121d1392 100644 --- a/src/routes/Library/useSelectableInputs.js +++ b/src/routes/Library/useSelectableInputs.js @@ -32,21 +32,7 @@ const mapSelectableInputs = (library, t) => { window.location = event.value; } }; - const paginationInput = library.selectable.prevPage || library.selectable.nextPage ? - { - label: library.selected.request.page.toString(), - onSelect: (event) => { - if (event.value === 'prev' && library.selectable.prevPage) { - window.location = library.selectable.prevPage.deepLinks.library; - } - if (event.value === 'next' && library.selectable.nextPage) { - window.location = library.selectable.nextPage.deepLinks.library; - } - } - } - : - null; - return [typeSelect, sortSelect, paginationInput]; + return [typeSelect, sortSelect, library.selectable.nextPage]; }; const useSelectableInputs = (library) => { diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 713796b5a..3c79c3331 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -269,7 +269,8 @@ const Player = ({ urlParams, queryParams }) => { setError(null); if (player.selected === null) { video.unload(); - } else if (player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading')) { + } else if (streamingServer.settings !== null && streamingServer.settings.type !== 'Loading' && + (player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading'))) { video.load({ stream: { ...player.selected.stream,