diff --git a/package-lock.json b/package-lock.json index 0b7fb7583..6544311ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1833,9 +1833,9 @@ "integrity": "sha512-yT3No1gIWKLV2BhQIeSgG94EzXxmEqXJLulO+pFpziqWNUbmmEKeE+nRvW5wtoIK4SLy+v0bLd0b6HBH3KFfWw==" }, "@stremio/stremio-core-web": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.36.0.tgz", - "integrity": "sha512-A63yB+Pml/c6jtK4l5r7aacZ2Jm+ofaj60MfVCZfzSE4csBPMuoROYx6TM6nI6NU8iUDTcIU+OYoWcMfaT7v7A==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.38.0.tgz", + "integrity": "sha512-VAutpvUPzoBJ7U0IigOB6aIPVWLKeTQoAnpccsOLBEDhYUEMAcVqGirRx8Vyz1kEA+lhAQgBsDbBWONPQMONRA==", "requires": { "@babel/runtime": "7.15.4" }, diff --git a/package.json b/package.json index 335762151..cc067fde6 100755 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@stremio/stremio-colors": "4.0.1", - "@stremio/stremio-core-web": "0.36.0", + "@stremio/stremio-core-web": "0.38.0", "@stremio/stremio-icons": "3.0.5", "@stremio/stremio-video": "0.0.19-rc.1", "a-color-picker": "1.2.1", diff --git a/src/common/MetaItem/MetaItem.js b/src/common/MetaItem/MetaItem.js index f4959af0f..5175b2ed6 100644 --- a/src/common/MetaItem/MetaItem.js +++ b/src/common/MetaItem/MetaItem.js @@ -2,8 +2,8 @@ const React = require('react'); const PropTypes = require('prop-types'); -const filterInvalidDOMProps = require('filter-invalid-dom-props').default; const classnames = require('classnames'); +const filterInvalidDOMProps = require('filter-invalid-dom-props').default; const Icon = require('@stremio/stremio-icons/dom'); const Button = require('stremio/common/Button'); const Image = require('stremio/common/Image'); diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 1fa8a3567..4657a7892 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -7,6 +7,19 @@ const FocusLock = require('react-focus-lock').default; const { useRouteFocused } = require('stremio-router'); const styles = require('./styles'); +const getAnchorElement = (element) => { + if (element === document.documentElement) { + return element; + } + + const style = window.getComputedStyle(element); + if (style.overflowY.indexOf('auto') !== -1 || style.overflowY.indexOf('scroll') !== -1) { + return element; + } + + return getAnchorElement(element.parentElement); +}; + const Popup = ({ open, direction, renderLabel, renderMenu, dataset, onCloseRequest, ...props }) => { const routeFocused = useRouteFocused(); const labelRef = React.useRef(null); @@ -49,14 +62,16 @@ const Popup = ({ open, direction, renderLabel, renderMenu, dataset, onCloseReque React.useLayoutEffect(() => { if (open) { const autoDirection = []; - const documentRect = document.documentElement.getBoundingClientRect(); + const anchor = getAnchorElement(labelRef.current); + const anchorRect = anchor.getBoundingClientRect(); + const labelRect = labelRef.current.getBoundingClientRect(); const menuRect = menuRef.current.getBoundingClientRect(); const labelPosition = { - left: labelRect.left - documentRect.left, - top: labelRect.top - documentRect.top, - right: (documentRect.width + documentRect.left) - (labelRect.left + labelRect.width), - bottom: (documentRect.height + documentRect.top) - (labelRect.top + labelRect.height) + left: labelRect.left - anchorRect.left, + top: labelRect.top - anchorRect.top, + right: (anchorRect.width + anchorRect.left) - (labelRect.left + labelRect.width), + bottom: (anchorRect.height + anchorRect.top) - (labelRect.top + labelRect.height) }; if (menuRect.height <= labelPosition.bottom) { @@ -87,7 +102,7 @@ const Popup = ({ open, direction, renderLabel, renderMenu, dataset, onCloseReque return renderLabel({ ...props, ref: labelRef, - className: styles['label-container'], + className: classnames(styles['label-container'], props.className, { 'active': open }), children: open ? {renderMenu()} diff --git a/src/routes/MetaDetails/VideosList/Video/Video.js b/src/routes/MetaDetails/VideosList/Video/Video.js index eda4ef8d6..125932db8 100644 --- a/src/routes/MetaDetails/VideosList/Video/Video.js +++ b/src/routes/MetaDetails/VideosList/Video/Video.js @@ -3,12 +3,37 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); +const { useServices } = require('stremio/services'); +const { useRouteFocused } = require('stremio-router'); const Icon = require('@stremio/stremio-icons/dom'); -const { Button, Image } = require('stremio/common'); +const { Button, Image, Popup, useBinaryState } = require('stremio/common'); const VideoPlaceholder = require('./VideoPlaceholder'); const styles = require('./styles'); const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, ...props }) => { + const { core } = useServices(); + const routeFocused = useRouteFocused(); + const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); + const popupLabelOnContextMenu = React.useCallback((event) => { + if (!event.nativeEvent.togglePopupPrevented && !event.nativeEvent.ctrlKey) { + toggleMenu(); + event.nativeEvent.preventDefault(); + } + }, [toggleMenu]); + const popupMenuOnContextMenu = React.useCallback((event) => { + event.nativeEvent.togglePopupPrevented = true; + }, []); + const toggleWatchedOnClick = React.useCallback((event) => { + event.preventDefault(); + closeMenu(); + core.transport.dispatch({ + action: 'MetaDetails', + args: { + action: 'MarkAsWatched', + args: [id, !watched] + } + }); + }, [id, watched]); const href = React.useMemo(() => { return deepLinks ? typeof deepLinks.player === 'string' ? @@ -21,74 +46,115 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w : null; }, [deepLinks]); - return ( - + ); + }, []); + const renderMenu = React.useMemo(() => function renderMenu() { + return ( +
+ +
- { - progress !== null && !isNaN(progress) && progress > 0 ? -
-
-
- : - null - } - + ); + }, [watched, toggleWatchedOnClick]); + React.useEffect(() => { + if (!routeFocused) { + closeMenu(); + } + }, [routeFocused]); + return ( + ); }; diff --git a/src/routes/MetaDetails/VideosList/Video/styles.less b/src/routes/MetaDetails/VideosList/Video/styles.less index 70b57d4d6..e00a42c21 100644 --- a/src/routes/MetaDetails/VideosList/Video/styles.less +++ b/src/routes/MetaDetails/VideosList/Video/styles.less @@ -3,13 +3,21 @@ @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~stremio/common/screen-sizes.less'; +:import('~stremio/common/Popup/styles.less') { + context-menu-container: menu-container; + menu-direction-top-left: menu-direction-top-left; + menu-direction-bottom-left: menu-direction-bottom-left; + menu-direction-top-right: menu-direction-top-right; + menu-direction-bottom-right: menu-direction-bottom-right; +} + .video-container { display: flex; flex-direction: row; flex-wrap: wrap; padding: 0.5rem 1rem; - &:hover, &:focus { + &:hover, &:focus, &:global(.active) { background-color: @color-background; } @@ -124,8 +132,49 @@ @media only screen and (max-width: @minimum) { .video-container { - &:hover, &:focus { + &:hover, &:focus, &:global(.active) { background-color: @color-surface-light5-20; } + + .context-menu-container { + max-width: calc(90% - 1.5rem); + + &.menu-direction-top-left, &.menu-direction-bottom-left { + right: 1.5rem; + } + + &.menu-direction-top-right, &.menu-direction-bottom-right { + left: 1.5rem; + } + + &.menu-direction-top-left, &.menu-direction-top-right { + bottom: 90%; + } + + &.menu-direction-bottom-left, &.menu-direction-bottom-right { + top: 90%; + } + + .context-menu-content { + background-color: @color-background-dark1; + + .context-menu-option-container { + display: flex; + flex-direction: row; + align-items: center; + padding: 0.5rem 1rem; + + &:hover, &:focus { + background-color: @color-background; + } + + .context-menu-option-label { + font-size: 1rem; + max-height: 2.4em; + color: @color-surface-light5-90; + } + } + } + } } } \ No newline at end of file diff --git a/src/routes/Search/useSearch.js b/src/routes/Search/useSearch.js index b3902555e..d1ff8e79c 100644 --- a/src/routes/Search/useSearch.js +++ b/src/routes/Search/useSearch.js @@ -18,7 +18,7 @@ const useSearch = (queryParams) => { const state = core.transport.getState('search'); if (state.selected !== null) { const [, query] = state.selected.extra.find(([name]) => name === 'search'); - const responses = state.catalogs.filter((catalog) => catalog.content.type === 'Ready'); + const responses = state.catalogs.filter((catalog) => catalog.content?.type === 'Ready'); core.transport.analytics({ event: 'Search', args: {