From 41fe4f01334adb7ecb55f43c3836bb9b5b15404d Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Wed, 25 Sep 2019 14:56:10 +0300 Subject: [PATCH 01/96] video submodule moved to /src/video --- src/routes/Player/Video/Video.js | 4 +--- .../Player/Video/stremio-video => video}/HTMLSubtitles.js | 0 src/{routes/Player/Video/stremio-video => video}/HTMLVideo.js | 0 src/{routes/Player/Video/stremio-video => video}/MPVVideo.js | 0 src/{routes/Player/Video/stremio-video => video}/README.MD | 0 .../Player/Video/stremio-video => video}/YouTubeVideo.js | 0 .../Video/stremio-video => video}/binarySearchUpperBound.js | 0 .../Player/Video/stremio-video => video}/colorConverter.js | 0 .../Player/Video/stremio-video => video}/subtitlesParser.js | 0 .../Player/Video/stremio-video => video}/subtitlesRenderer.js | 0 webpack.config.js | 3 ++- 11 files changed, 3 insertions(+), 4 deletions(-) rename src/{routes/Player/Video/stremio-video => video}/HTMLSubtitles.js (100%) rename src/{routes/Player/Video/stremio-video => video}/HTMLVideo.js (100%) rename src/{routes/Player/Video/stremio-video => video}/MPVVideo.js (100%) rename src/{routes/Player/Video/stremio-video => video}/README.MD (100%) rename src/{routes/Player/Video/stremio-video => video}/YouTubeVideo.js (100%) rename src/{routes/Player/Video/stremio-video => video}/binarySearchUpperBound.js (100%) rename src/{routes/Player/Video/stremio-video => video}/colorConverter.js (100%) rename src/{routes/Player/Video/stremio-video => video}/subtitlesParser.js (100%) rename src/{routes/Player/Video/stremio-video => video}/subtitlesRenderer.js (100%) diff --git a/src/routes/Player/Video/Video.js b/src/routes/Player/Video/Video.js index f99932a31..8a71c7278 100644 --- a/src/routes/Player/Video/Video.js +++ b/src/routes/Player/Video/Video.js @@ -1,9 +1,7 @@ const React = require('react'); const PropTypes = require('prop-types'); const hat = require('hat'); -const HTMLVideo = require('./stremio-video/HTMLVideo'); -const YouTubeVideo = require('./stremio-video/YouTubeVideo'); -const MPVVideo = require('./stremio-video/MPVVideo'); +const { HTMLVideo, YouTubeVideo, MPVVideo } = require('stremio-video'); class Video extends React.Component { constructor(props) { diff --git a/src/routes/Player/Video/stremio-video/HTMLSubtitles.js b/src/video/HTMLSubtitles.js similarity index 100% rename from src/routes/Player/Video/stremio-video/HTMLSubtitles.js rename to src/video/HTMLSubtitles.js diff --git a/src/routes/Player/Video/stremio-video/HTMLVideo.js b/src/video/HTMLVideo.js similarity index 100% rename from src/routes/Player/Video/stremio-video/HTMLVideo.js rename to src/video/HTMLVideo.js diff --git a/src/routes/Player/Video/stremio-video/MPVVideo.js b/src/video/MPVVideo.js similarity index 100% rename from src/routes/Player/Video/stremio-video/MPVVideo.js rename to src/video/MPVVideo.js diff --git a/src/routes/Player/Video/stremio-video/README.MD b/src/video/README.MD similarity index 100% rename from src/routes/Player/Video/stremio-video/README.MD rename to src/video/README.MD diff --git a/src/routes/Player/Video/stremio-video/YouTubeVideo.js b/src/video/YouTubeVideo.js similarity index 100% rename from src/routes/Player/Video/stremio-video/YouTubeVideo.js rename to src/video/YouTubeVideo.js diff --git a/src/routes/Player/Video/stremio-video/binarySearchUpperBound.js b/src/video/binarySearchUpperBound.js similarity index 100% rename from src/routes/Player/Video/stremio-video/binarySearchUpperBound.js rename to src/video/binarySearchUpperBound.js diff --git a/src/routes/Player/Video/stremio-video/colorConverter.js b/src/video/colorConverter.js similarity index 100% rename from src/routes/Player/Video/stremio-video/colorConverter.js rename to src/video/colorConverter.js diff --git a/src/routes/Player/Video/stremio-video/subtitlesParser.js b/src/video/subtitlesParser.js similarity index 100% rename from src/routes/Player/Video/stremio-video/subtitlesParser.js rename to src/video/subtitlesParser.js diff --git a/src/routes/Player/Video/stremio-video/subtitlesRenderer.js b/src/video/subtitlesRenderer.js similarity index 100% rename from src/routes/Player/Video/stremio-video/subtitlesRenderer.js rename to src/video/subtitlesRenderer.js diff --git a/webpack.config.js b/webpack.config.js index cf1a8de8f..6f6ebf8bd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -97,7 +97,8 @@ module.exports = { extensions: ['.js', '.json', '.less', '.wasm'], alias: { 'stremio': path.resolve(__dirname, 'src'), - 'stremio-router': path.resolve(__dirname, 'src/router') + 'stremio-router': path.resolve(__dirname, 'src/router'), + 'stremio-video': path.resolve(__dirname, 'src/video') } }, devServer: { From 0696ac48ac472fb918e42effd978a1b97021b1d7 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Wed, 25 Sep 2019 15:13:20 +0300 Subject: [PATCH 02/96] ActionButton adapted to the latest changes in common --- src/common/MetaPreview/ActionButton/ActionButton.js | 4 +++- src/common/MetaPreview/ActionButton/styles.less | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/MetaPreview/ActionButton/ActionButton.js b/src/common/MetaPreview/ActionButton/ActionButton.js index dc19a33ae..38bca56d6 100644 --- a/src/common/MetaPreview/ActionButton/ActionButton.js +++ b/src/common/MetaPreview/ActionButton/ActionButton.js @@ -10,7 +10,9 @@ const ActionButton = ({ className, icon, label, ...props }) => { + ); +}; PlayPauseButton.propTypes = { className: PropTypes.string, paused: PropTypes.bool, - dispatch: PropTypes.func.isRequired + dispatch: PropTypes.func }; module.exports = PlayPauseButton; diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index e6d25c68c..242a97e57 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -20,47 +20,33 @@ } .control-bar-buttons-container { - height: var(--control-bar-button-size); display: flex; flex-direction: row; align-items: center; .control-bar-button { - width: var(--control-bar-button-size); - height: var(--control-bar-button-size); + flex: none; + width: 4rem; + height: 4rem; display: flex; justify-content: center; align-items: center; - cursor: pointer; - - :global(.icon) { - width: 60%; - height: 60%; - fill: var(--color-surfacelight); - overflow: visible; - } &:global(.active) { background-color: var(--color-backgrounddarker); - - :global(.icon) { - fill: var(--color-surfacelighter); - } } &:global(.disabled) { - cursor: default; - pointer-events: none; - :global(.icon) { fill: var(--color-surfacedark); } } - &:hover:not(:global(.disabled)) { - :global(.icon) { - fill: var(--color-surfacelighter); - } + :global(.icon) { + flex: none; + width: 2.5rem; + height: 2.5rem; + fill: var(--color-surfacelighter); } } From f156416f82180977781740d2139a3dd9fc359734 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 14:15:39 +0300 Subject: [PATCH 31/96] disable focusability of playpause button --- src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js b/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js index 1129126e8..b337c6e2c 100644 --- a/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js +++ b/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js @@ -11,7 +11,7 @@ const PlayPauseButton = ({ className, paused, dispatch }) => { } }, [paused, dispatch]); return ( - + ); } MuteButton.propTypes = { className: PropTypes.string, muted: PropTypes.bool, volume: PropTypes.number, - dispatch: PropTypes.func.isRequired + dispatch: PropTypes.func }; module.exports = MuteButton; From d8906af971a275bced54606bebcca147bde57f2b Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 14:58:03 +0300 Subject: [PATCH 34/96] disabled slider styles implemented --- src/common/Slider/styles.less | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/Slider/styles.less b/src/common/Slider/styles.less index ca3b46190..fb6c3d134 100644 --- a/src/common/Slider/styles.less +++ b/src/common/Slider/styles.less @@ -12,6 +12,18 @@ html.active-slider-within { overflow: visible; cursor: pointer; + &:global(.disabled) { + pointer-events: none; + + .track, .track-before { + background-color: var(--color-surfacedark); + } + + .thumb { + fill: var(--color-surfacedark); + } + } + .layer { position: absolute; top: 0; From d05d430523394a6ff8e537178fb94a96969b7350 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 14:59:51 +0300 Subject: [PATCH 35/96] seekbar disabled state styled --- src/routes/Player/ControlBar/SeekBar/SeekBar.js | 2 +- src/routes/Player/ControlBar/SeekBar/styles.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Player/ControlBar/SeekBar/SeekBar.js b/src/routes/Player/ControlBar/SeekBar/SeekBar.js index ef1a0c32a..d7481ec19 100644 --- a/src/routes/Player/ControlBar/SeekBar/SeekBar.js +++ b/src/routes/Player/ControlBar/SeekBar/SeekBar.js @@ -31,7 +31,7 @@ const SeekBar = ({ className, time, duration, dispatch }) => {
{formatTime(seekTime !== null ? seekTime : time)}
Date: Thu, 3 Oct 2019 15:03:06 +0300 Subject: [PATCH 36/96] Volume slider implemented with hooks --- .../ControlBar/VolumeSlider/VolumeSlider.js | 89 +++++++------------ .../ControlBar/VolumeSlider/styles.less | 25 ++---- src/routes/Player/ControlBar/styles.less | 9 +- 3 files changed, 43 insertions(+), 80 deletions(-) diff --git a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js index 96710f55c..595ebb96f 100644 --- a/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js +++ b/src/routes/Player/ControlBar/VolumeSlider/VolumeSlider.js @@ -5,68 +5,43 @@ const debounce = require('lodash.debounce'); const { Slider } = require('stremio/common'); const styles = require('./styles'); -class VolumeSlider extends React.Component { - constructor(props) { - super(props); - - this.state = { - volume: null +const VolumeSlider = ({ className, volume, dispatch }) => { + const [slidingVolume, setSlidingVolume] = React.useState(null); + const resetVolumeDebounced = React.useCallback(debounce(() => { + setSlidingVolume(null); + }, 100), []); + const onSlide = React.useCallback((volume) => { + resetVolumeDebounced.cancel(); + setSlidingVolume(volume); + }, []); + const onComplete = React.useCallback((volume) => { + resetVolumeDebounced(); + setSlidingVolume(volume); + if (typeof dispatch === 'function') { + dispatch({ propName: 'volume', propValue: volume }); + } + }, []); + React.useEffect(() => { + return () => { + resetVolumeDebounced.cancel(); }; - } - - shouldComponentUpdate(nextProps, nextState) { - return nextState.volume !== this.state.volume || - nextProps.className !== this.props.className || - nextProps.volume !== this.props.volume; - } - - componentWillUnmount() { - this.resetVolumeDebounced.cancel(); - } - - resetVolumeDebounced = debounce(() => { - this.setState({ volume: null }); - }, 100) - - onSlide = (volume) => { - this.resetVolumeDebounced.cancel(); - this.setState({ volume }); - } - - onComplete = (volume) => { - this.resetVolumeDebounced(); - this.setState({ volume }); - this.props.dispatch({ propName: 'volume', propValue: volume }); - } - - onCancel = () => { - this.resetVolumeDebounced.cancel(); - this.setState({ volume: null }); - } - - render() { - const volume = this.state.volume !== null ? this.state.volume : this.props.volume; - return ( -
- -
- ); - } -} + }, []); + return ( + + ); +}; VolumeSlider.propTypes = { className: PropTypes.string, volume: PropTypes.number, - dispatch: PropTypes.func.isRequired + dispatch: PropTypes.func }; module.exports = VolumeSlider; diff --git a/src/routes/Player/ControlBar/VolumeSlider/styles.less b/src/routes/Player/ControlBar/VolumeSlider/styles.less index 1e2d3e563..156e86305 100644 --- a/src/routes/Player/ControlBar/VolumeSlider/styles.less +++ b/src/routes/Player/ControlBar/VolumeSlider/styles.less @@ -1,24 +1,11 @@ -.volume-slider-container { - padding: 0 calc(var(--volume-slider-thumb-size) / 2); - - .volume-slider { - --thumb-size: var(--volume-slider-thumb-size); - --track-size: var(--volume-slider-track-size); - --track-before-color: var(--color-primary); - --track-color: var(--color-backgroundlighter); - --thumb-color: transparent; - width: 100%; - height: 100%; - - &:global(.disabled) { - --track-color: var(--color-surfacedark); - } - } +:import('~stremio/common/Slider/styles.less') { + slider-track-before: track-before; +} +.volume-slider { &:hover, &:global(.active) { - .volume-slider { - --track-before-color: var(--color-primarylight); - --thumb-color: var(--color-surfacelighter); + .slider-track-before { + background-color: var(--color-primarylight); } } } \ No newline at end of file diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index 242a97e57..ff8277266 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -51,10 +51,11 @@ } .volume-slider { - --volume-slider-thumb-size: calc(var(--control-bar-button-size) * 0.36); - --volume-slider-track-size: calc(var(--control-bar-button-size) * 0.10); - width: calc(var(--control-bar-button-size) * 4); - height: var(--control-bar-button-size); + --thumb-size: 1.25rem; + flex: none; + width: 16rem; + height: 4rem; + margin: 0 1rem; } .spacing { From d05abaad4b3180b3c5ae8c4ff586b074166c3078 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 17:19:24 +0300 Subject: [PATCH 37/96] modalContainerClassName added to Popup proptypes --- src/common/Popup/Popup.js | 6 ++++-- src/common/Popup/styles.less | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index e970c5711..9a81dcda1 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -1,10 +1,11 @@ const React = require('react'); const PropTypes = require('prop-types'); +const classnames = require('classnames'); const { Modal } = require('stremio-router'); const styles = require('./styles'); // TODO rename to Popover -const Popup = ({ open, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { +const Popup = ({ open, modalContainerClassName, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { const labelRef = React.useRef(null); const menuRef = React.useRef(null); const [menuStyles, setMenuStyles] = React.useState({}); @@ -110,7 +111,7 @@ const Popup = ({ open, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequ {renderLabel(labelRef)} { open ? - +
{renderMenu()}
@@ -124,6 +125,7 @@ const Popup = ({ open, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequ Popup.propTypes = { open: PropTypes.bool, + modalContainerClassName: PropTypes.string, menuMatchLabelWidth: PropTypes.bool, renderLabel: PropTypes.func.isRequired, renderMenu: PropTypes.func.isRequired, diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less index 37cdda390..478c81e5c 100644 --- a/src/common/Popup/styles.less +++ b/src/common/Popup/styles.less @@ -1,4 +1,4 @@ -.popup-modal-container { +.menu-modal-container { pointer-events: none; .menu-container { From 41f60660095b6873e3956eb76f18af12ea3539a5 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 17:24:18 +0300 Subject: [PATCH 38/96] menuRelativePosition prop added to Popup --- src/common/Popup/Popup.js | 103 ++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 9a81dcda1..d87dc79e7 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -5,7 +5,7 @@ const { Modal } = require('stremio-router'); const styles = require('./styles'); // TODO rename to Popover -const Popup = ({ open, modalContainerClassName, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { +const Popup = ({ open, modalContainerClassName, menuRelativePosition, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { const labelRef = React.useRef(null); const menuRef = React.useRef(null); const [menuStyles, setMenuStyles] = React.useState({}); @@ -47,58 +47,60 @@ const Popup = ({ open, modalContainerClassName, menuMatchLabelWidth, renderLabel React.useEffect(() => { let menuStyles = {}; if (open) { - const documentRect = document.documentElement.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) - }; - const matchLabelWidthMenuStyles = { - width: `${labelRect.width}px`, - maxWidth: `${labelRect.width}px` - }; - const bottomMenuStyles = { - top: `${labelPosition.top + labelRect.height}px`, - maxHeight: `${labelPosition.bottom}px` - }; - const topMenuStyles = { - bottom: `${labelPosition.bottom + labelRect.height}px`, - maxHeight: `${labelPosition.top}px` - }; - const rightMenuStyles = { - left: `${labelPosition.left}px`, - maxWidth: `${labelPosition.right + labelRect.width}px` - }; - const leftMenuStyles = { - right: `${labelPosition.right}px`, - maxWidth: `${labelPosition.left + labelRect.width}px` - }; + if (menuRelativePosition !== false) { + const documentRect = document.documentElement.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) + }; + const matchLabelWidthMenuStyles = { + width: `${labelRect.width}px`, + maxWidth: `${labelRect.width}px` + }; + const bottomMenuStyles = { + top: `${labelPosition.top + labelRect.height}px`, + maxHeight: `${labelPosition.bottom}px` + }; + const topMenuStyles = { + bottom: `${labelPosition.bottom + labelRect.height}px`, + maxHeight: `${labelPosition.top}px` + }; + const rightMenuStyles = { + left: `${labelPosition.left}px`, + maxWidth: `${labelPosition.right + labelRect.width}px` + }; + const leftMenuStyles = { + right: `${labelPosition.right}px`, + maxWidth: `${labelPosition.left + labelRect.width}px` + }; - if (menuRect.height <= labelPosition.bottom) { - menuStyles = { ...menuStyles, ...bottomMenuStyles }; - } else if (menuRect.height <= labelPosition.top) { - menuStyles = { ...menuStyles, ...topMenuStyles }; - } else if (labelPosition.bottom >= labelPosition.top) { - menuStyles = { ...menuStyles, ...bottomMenuStyles }; - } else { - menuStyles = { ...menuStyles, ...topMenuStyles }; - } + if (menuRect.height <= labelPosition.bottom) { + menuStyles = { ...menuStyles, ...bottomMenuStyles }; + } else if (menuRect.height <= labelPosition.top) { + menuStyles = { ...menuStyles, ...topMenuStyles }; + } else if (labelPosition.bottom >= labelPosition.top) { + menuStyles = { ...menuStyles, ...bottomMenuStyles }; + } else { + menuStyles = { ...menuStyles, ...topMenuStyles }; + } - if (menuRect.width <= (labelPosition.right + labelRect.width)) { - menuStyles = { ...menuStyles, ...rightMenuStyles }; - } else if (menuRect.width <= (labelPosition.left + labelRect.width)) { - menuStyles = { ...menuStyles, ...leftMenuStyles }; - } else if (labelPosition.right > labelPosition.left) { - menuStyles = { ...menuStyles, ...rightMenuStyles }; - } else { - menuStyles = { ...menuStyles, ...leftMenuStyles }; - } + if (menuRect.width <= (labelPosition.right + labelRect.width)) { + menuStyles = { ...menuStyles, ...rightMenuStyles }; + } else if (menuRect.width <= (labelPosition.left + labelRect.width)) { + menuStyles = { ...menuStyles, ...leftMenuStyles }; + } else if (labelPosition.right > labelPosition.left) { + menuStyles = { ...menuStyles, ...rightMenuStyles }; + } else { + menuStyles = { ...menuStyles, ...leftMenuStyles }; + } - if (menuMatchLabelWidth) { - menuStyles = { ...menuStyles, ...matchLabelWidthMenuStyles }; + if (menuMatchLabelWidth) { + menuStyles = { ...menuStyles, ...matchLabelWidthMenuStyles }; + } } menuStyles = { ...menuStyles, visibility: 'visible' }; @@ -126,6 +128,7 @@ const Popup = ({ open, modalContainerClassName, menuMatchLabelWidth, renderLabel Popup.propTypes = { open: PropTypes.bool, modalContainerClassName: PropTypes.string, + menuRelativePosition: PropTypes.bool, menuMatchLabelWidth: PropTypes.bool, renderLabel: PropTypes.func.isRequired, renderMenu: PropTypes.func.isRequired, From 276740ad90a2caec25ee743207b2f0bb915f8099 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 18:17:47 +0300 Subject: [PATCH 39/96] menuModalClassName classname renamed --- src/common/Popup/Popup.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index d87dc79e7..5f06edd6a 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -5,7 +5,7 @@ const { Modal } = require('stremio-router'); const styles = require('./styles'); // TODO rename to Popover -const Popup = ({ open, modalContainerClassName, menuRelativePosition, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { +const Popup = ({ open, menuModalClassName, menuRelativePosition, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { const labelRef = React.useRef(null); const menuRef = React.useRef(null); const [menuStyles, setMenuStyles] = React.useState({}); @@ -113,7 +113,7 @@ const Popup = ({ open, modalContainerClassName, menuRelativePosition, menuMatchL {renderLabel(labelRef)} { open ? - +
{renderMenu()}
@@ -127,7 +127,7 @@ const Popup = ({ open, modalContainerClassName, menuRelativePosition, menuMatchL Popup.propTypes = { open: PropTypes.bool, - modalContainerClassName: PropTypes.string, + menuModalClassName: PropTypes.string, menuRelativePosition: PropTypes.bool, menuMatchLabelWidth: PropTypes.bool, renderLabel: PropTypes.func.isRequired, From 55a01456877744647bb848af501f13c2c7a19fdd Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 18:18:05 +0300 Subject: [PATCH 40/96] show slider thumb with delay --- src/routes/Player/ControlBar/SeekBar/styles.less | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/routes/Player/ControlBar/SeekBar/styles.less b/src/routes/Player/ControlBar/SeekBar/styles.less index 5199d6d6a..0c72177b0 100644 --- a/src/routes/Player/ControlBar/SeekBar/styles.less +++ b/src/routes/Player/ControlBar/SeekBar/styles.less @@ -21,6 +21,14 @@ } } + &:hover { + .slider:not(:global(.disabled)) { + .slider-thumb { + transition-delay: 100ms; + } + } + } + .label { flex: none; max-width: 5rem; @@ -37,6 +45,7 @@ margin: 0 var(--thumb-size); .slider-thumb { + transition: 0s fill; fill: transparent; } } From f92c7de0e7cf9e1d960ce3850a3ca24a107ca205 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 3 Oct 2019 18:20:16 +0300 Subject: [PATCH 41/96] sample share modal adapted to common components --- src/routes/Player/ControlBar/ControlBar.js | 5 +- .../ControlBar/ShareButton/ShareButton.js | 59 +++++++------------ .../Player/ControlBar/ShareButton/styles.less | 11 ++-- src/routes/Player/ControlBar/styles.less | 7 +++ 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js index dfe6ad82b..aa70ffd70 100644 --- a/src/routes/Player/ControlBar/ControlBar.js +++ b/src/routes/Player/ControlBar/ControlBar.js @@ -36,7 +36,7 @@ const ControlBar = (props) => { dispatch={props.dispatch} />
- { subtitlesOutlineColor={props.subtitlesOutlineColor} dispatch={props.dispatch} /> + */}
diff --git a/src/routes/Player/ControlBar/ShareButton/ShareButton.js b/src/routes/Player/ControlBar/ShareButton/ShareButton.js index 8b9c00352..f0dbd994b 100644 --- a/src/routes/Player/ControlBar/ShareButton/ShareButton.js +++ b/src/routes/Player/ControlBar/ShareButton/ShareButton.js @@ -2,47 +2,28 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { Popup } = require('stremio/common'); +const { Button, Popup, useBinaryState } = require('stremio/common'); const styles = require('./styles'); -class ShareButton extends React.Component { - constructor(props) { - super(props); - - this.state = { - popupOpen: false - }; - } - - shouldComponentUpdate(nextProps, nextState) { - return nextState.popupOpen !== this.state.popupOpen || - nextProps.className !== this.props.className || - nextProps.modalContainerClassName !== this.props.modalContainerClassName; - } - - onPopupOpen = () => { - this.setState({ popupOpen: true }); - } - - onPopupClose = () => { - this.setState({ popupOpen: false }); - } - - render() { - return ( - - -
- -
-
- -
- - - ); - } -} +const ShareButton = ({ className, modalContainerClassName }) => { + const [modalOpen, openModal, closeModal, toggleModal] = useBinaryState(false); + return ( + ( + + )} + renderMenu={() => ( +
+ )} + onCloseRequest={closeModal} + /> + ); +}; ShareButton.propTypes = { className: PropTypes.string, diff --git a/src/routes/Player/ControlBar/ShareButton/styles.less b/src/routes/Player/ControlBar/ShareButton/styles.less index 2de50b10a..024666077 100644 --- a/src/routes/Player/ControlBar/ShareButton/styles.less +++ b/src/routes/Player/ControlBar/ShareButton/styles.less @@ -1,5 +1,8 @@ -.share-dialog-container { - width: calc(var(--control-bar-button-size) * 5); - height: calc(var(--control-bar-button-size) * 3); - background-color: var(--color-backgrounddark); +.share-modal-container { + .share-dialog-container { + flex: none; + width: 10rem; + height: 5rem; + background-color: var(--color-backgroundlighter); + } } \ No newline at end of file diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index ff8277266..50842aca7 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -62,4 +62,11 @@ flex: 1; } } +} + +.modal-container { + display: flex; + align-items: flex-end; + justify-content: flex-end; + padding: 8rem 1rem; } \ No newline at end of file From 2774700b88c2d3fa0d2dd878560b120c3d2a58a2 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 4 Oct 2019 21:16:29 +0300 Subject: [PATCH 42/96] scroll event bubbled to window --- src/App/App.js | 25 +++++++++++-------- src/App/styles.less | 17 ++++++++----- .../ScrollEventEmitter/ScrollEventEmitter.js | 18 +++++++++++++ src/common/ScrollEventEmitter/index.js | 3 +++ src/common/index.js | 2 ++ 5 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 src/common/ScrollEventEmitter/ScrollEventEmitter.js create mode 100644 src/common/ScrollEventEmitter/index.js diff --git a/src/App/App.js b/src/App/App.js index ffb33d4af..1f33410e1 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -2,6 +2,7 @@ require('spatial-navigation-polyfill'); const React = require('react'); const { Router } = require('stremio-router'); const { Core, KeyboardNavigation, ServicesProvider, Shell } = require('stremio/services'); +const { ScrollEventEmitter } = require('stremio/common'); const routerViewsConfig = require('./routerViewsConfig'); const styles = require('./styles'); @@ -39,17 +40,19 @@ const App = () => { return ( - { - shellInitialized && coreInitialized ? - - : -
- } + + { + shellInitialized && coreInitialized ? + + : +
+ } + ); diff --git a/src/App/styles.less b/src/App/styles.less index 2b0f0b307..4062d977d 100644 --- a/src/App/styles.less +++ b/src/App/styles.less @@ -70,15 +70,20 @@ html { width: 100%; height: 100%; - .router { + .app-content { width: 100%; height: 100%; - } - .app-loader { - width: 100%; - height: 100%; - background-color: var(--color-background); + .router { + width: 100%; + height: 100%; + } + + .app-loader { + width: 100%; + height: 100%; + background-color: var(--color-background); + } } } } diff --git a/src/common/ScrollEventEmitter/ScrollEventEmitter.js b/src/common/ScrollEventEmitter/ScrollEventEmitter.js new file mode 100644 index 000000000..7b2aec479 --- /dev/null +++ b/src/common/ScrollEventEmitter/ScrollEventEmitter.js @@ -0,0 +1,18 @@ +const React = require('react'); + +const ScrollEventEmitter = (props) => { + const onScroll = React.useCallback((event) => { + if (typeof props.onScroll === 'function') { + props.onScroll(event); + } + + const reactScrollEvent = new Event('react-scroll'); + reactScrollEvent.nativeEvent = event.nativeEvent; + window.dispatchEvent(reactScrollEvent); + }, []); + return ( +
+ ); +}; + +module.exports = ScrollEventEmitter; diff --git a/src/common/ScrollEventEmitter/index.js b/src/common/ScrollEventEmitter/index.js new file mode 100644 index 000000000..393e00c39 --- /dev/null +++ b/src/common/ScrollEventEmitter/index.js @@ -0,0 +1,3 @@ +const ScrollEventEmitter = require('./ScrollEventEmitter'); + +module.exports = ScrollEventEmitter; diff --git a/src/common/index.js b/src/common/index.js index a055080e5..4f9e1707e 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -13,6 +13,7 @@ const MetaRowPlaceholder = require('./MetaRowPlaceholder'); const NavBar = require('./NavBar'); const PlayIconCircleCentered = require('./PlayIconCircleCentered'); const Popup = require('./Popup'); +const ScrollEventEmitter = require('./ScrollEventEmitter'); const ShareModal = require('./ShareModal'); const Slider = require('./Slider'); const TextInput = require('./TextInput'); @@ -42,6 +43,7 @@ module.exports = { NavBar, PlayIconCircleCentered, Popup, + ScrollEventEmitter, ShareModal, Slider, TextInput, From 083ace9e0d68483322d428b48e78b188e7f0b10f Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 4 Oct 2019 21:23:02 +0300 Subject: [PATCH 43/96] storybook adapted to the changes in App --- storybook/RouterDecorator/RouterDecorator.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/storybook/RouterDecorator/RouterDecorator.js b/storybook/RouterDecorator/RouterDecorator.js index 7958e53f2..88813e8e5 100644 --- a/storybook/RouterDecorator/RouterDecorator.js +++ b/storybook/RouterDecorator/RouterDecorator.js @@ -1,18 +1,21 @@ const React = require('react'); const { RoutesContainerProvider } = require('stremio/router/RoutesContainerContext'); const Route = require('stremio/router/Route'); +const { ScrollEventEmitter } = require('stremio/common'); const appStyles = require('stremio/App/styles'); const styles = require('./styles'); const RouterDecorator = ({ children }) => (
- - -
- {children} -
-
-
+ + + +
+ {children} +
+
+
+
); From 8d53b77cf58d143e2361b6ef7600b922ac650bd2 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 4 Oct 2019 21:23:51 +0300 Subject: [PATCH 44/96] nested popups handled --- src/common/Popup/Popup.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 5f06edd6a..0b041e541 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -21,29 +21,38 @@ const Popup = ({ open, menuModalClassName, menuRelativePosition, menuMatchLabelW } break; case 'mousedown': - case 'scroll': if (event.target !== document && event.target !== document.documentElement && - !labelRef.current.contains(event.target) && - !menuRef.current.contains(event.target)) { + !event.closePopupPrevented) { onCloseRequest(event); } break; + case 'react-scroll': + if (!event.nativeEvent.closePopupPrevented) { + onCloseRequest(event.nativeEvent); + } + break; } }; if (open) { - window.addEventListener('scroll', checkCloseEvent, true); + window.addEventListener('react-scroll', checkCloseEvent); window.addEventListener('mousedown', checkCloseEvent); window.addEventListener('keydown', checkCloseEvent); window.addEventListener('resize', checkCloseEvent); } return () => { - window.removeEventListener('scroll', checkCloseEvent, true); + window.removeEventListener('react-scroll', checkCloseEvent); window.removeEventListener('mousedown', checkCloseEvent); window.removeEventListener('keydown', checkCloseEvent); window.removeEventListener('resize', checkCloseEvent); }; }, [open, onCloseRequest]); + const menuOnMouseDown = React.useCallback((event) => { + event.nativeEvent.closePopupPrevented = true; + }, []); + const menuOnScroll = React.useCallback((event) => { + event.nativeEvent.closePopupPrevented = true; + }, []); React.useEffect(() => { let menuStyles = {}; if (open) { @@ -114,7 +123,7 @@ const Popup = ({ open, menuModalClassName, menuRelativePosition, menuMatchLabelW { open ? -
+
{renderMenu()}
From c0a6a43f18a4391237b799263073edf54320f6b3 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 4 Oct 2019 21:49:55 +0300 Subject: [PATCH 45/96] Subtitles button layout in player --- src/routes/Player/ControlBar/ControlBar.js | 5 +- .../ControlBar/ShareButton/ShareButton.js | 8 +- .../Player/ControlBar/ShareButton/styles.less | 2 - .../SubtitlesButton/SubtitlesButton.js | 84 ++++++------------- .../ControlBar/SubtitlesButton/styles.less | 8 +- src/routes/Player/ControlBar/styles.less | 14 ++++ 6 files changed, 49 insertions(+), 72 deletions(-) diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js index aa70ffd70..02888a0a8 100644 --- a/src/routes/Player/ControlBar/ControlBar.js +++ b/src/routes/Player/ControlBar/ControlBar.js @@ -36,9 +36,9 @@ const ControlBar = (props) => { dispatch={props.dispatch} />
- {/* { subtitlesOutlineColor={props.subtitlesOutlineColor} dispatch={props.dispatch} /> - */} { - const [modalOpen, openModal, closeModal, toggleModal] = useBinaryState(false); + const [popupOpen, openPopup, closePopup, togglePopup] = useBinaryState(false); return ( ( - )} renderMenu={() => (
)} - onCloseRequest={closeModal} + onCloseRequest={closePopup} /> ); }; diff --git a/src/routes/Player/ControlBar/ShareButton/styles.less b/src/routes/Player/ControlBar/ShareButton/styles.less index 024666077..0c644a9ce 100644 --- a/src/routes/Player/ControlBar/ShareButton/styles.less +++ b/src/routes/Player/ControlBar/ShareButton/styles.less @@ -1,8 +1,6 @@ .share-modal-container { .share-dialog-container { - flex: none; width: 10rem; height: 5rem; - background-color: var(--color-backgroundlighter); } } \ No newline at end of file diff --git a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesButton.js b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesButton.js index a737f9c83..f0ad89a4b 100644 --- a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesButton.js +++ b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesButton.js @@ -2,65 +2,32 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); -const { Popup } = require('stremio/common'); +const { Button, Popup, useBinaryState } = require('stremio/common'); const SubtitlesPicker = require('./SubtitlesPicker'); const styles = require('./styles'); -class SubtitlesButton extends React.Component { - constructor(props) { - super(props); - - this.state = { - popupOpen: false - }; - } - - shouldComponentUpdate(nextProps, nextState) { - return nextState.popupOpen !== this.state.popupOpen || - nextProps.className !== this.props.className || - nextProps.modalContainerClassName !== this.props.modalContainerClassName || - nextProps.subtitlesTracks !== this.props.subtitlesTracks || - nextProps.selectedSubtitlesTrackId !== this.props.selectedSubtitlesTrackId || - nextProps.subtitlesSize !== this.props.subtitlesSize || - nextProps.subtitlesDelay !== this.props.subtitlesDelay || - nextProps.subtitlesTextColor !== this.props.subtitlesTextColor || - nextProps.subtitlesBackgroundColor !== this.props.subtitlesBackgroundColor || - nextProps.subtitlesOutlineColor !== this.props.subtitlesOutlineColor; - } - - onPopupOpen = () => { - this.setState({ popupOpen: true }); - } - - onPopupClose = () => { - this.setState({ popupOpen: false }); - } - - render() { - return ( - - -
- -
-
- - - -
- ); - } -} +const SubtitlesButton = (props) => { + const [popupOpen, openPopup, closePopup, togglePopup] = useBinaryState(false); + return ( + ( + + )} + renderMenu={() => ( + + )} + onCloseRequest={closePopup} + /> + ); +}; SubtitlesButton.propTypes = { className: PropTypes.string, @@ -69,7 +36,7 @@ SubtitlesButton.propTypes = { id: PropTypes.string.isRequired, label: PropTypes.string.isRequired, origin: PropTypes.string.isRequired - })).isRequired, + })), selectedSubtitlesTrackId: PropTypes.string, subtitlesSize: PropTypes.number, subtitlesDelay: PropTypes.number, @@ -78,8 +45,5 @@ SubtitlesButton.propTypes = { subtitlesOutlineColor: PropTypes.string, dispatch: PropTypes.func.isRequired }; -SubtitlesButton.defaultProps = { - subtitlesTracks: Object.freeze([]) -}; module.exports = SubtitlesButton; diff --git a/src/routes/Player/ControlBar/SubtitlesButton/styles.less b/src/routes/Player/ControlBar/SubtitlesButton/styles.less index 89a0efb18..317825e56 100644 --- a/src/routes/Player/ControlBar/SubtitlesButton/styles.less +++ b/src/routes/Player/ControlBar/SubtitlesButton/styles.less @@ -1,4 +1,6 @@ -.subtitles-picker-container { - --subtitles-picker-button-size: calc(var(--control-bar-button-size) * 0.6); - background-color: var(--color-backgrounddark); +.subtitles-modal-container { + .subtitles-picker-container { + width: 35rem; + height: 25rem; + } } \ No newline at end of file diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index 50842aca7..33b9fd7d6 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -1,3 +1,11 @@ +:import('./ShareButton/styles.less') { + share-dialog-container: share-dialog-container; +} + +:import('./SubtitlesButton/styles.less') { + subtitles-picker-container: subtitles-picker-container; +} + .control-bar-container { position: relative; z-index: 0; @@ -69,4 +77,10 @@ align-items: flex-end; justify-content: flex-end; padding: 8rem 1rem; + + .share-dialog-container, .subtitles-picker-container { + flex: none; + background-color: var(--color-backgroundlighter); + border: thin solid var(--color-surfacelighter20); + } } \ No newline at end of file From 69e043fd57b8c28f8f17ef10868dcccac349d969 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 11:59:40 +0300 Subject: [PATCH 46/96] ColorInput refactored to use Popup --- src/common/ColorInput/ColorInput.js | 77 +++++++++++++-------------- src/common/ColorInput/styles.less | 69 ++++++++++++++---------- src/common/ColorPicker/ColorPicker.js | 6 ++- src/common/ColorPicker/styles.less | 19 +++++++ 4 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 src/common/ColorPicker/styles.less diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index ee5a37a10..883f9e4ff 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -1,60 +1,59 @@ const React = require('react'); const PropTypes = require('prop-types'); -const { Modal } = require('stremio-router'); +const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); +const Popup = require('stremio/common/Popup'); const ColorPicker = require('stremio/common/ColorPicker'); const useBinaryState = require('stremio/common/useBinaryState'); -const Icon = require('stremio-icons/dom'); const styles = require('./styles'); -const ColorInput = ({ className, id, value, onChange }) => { - const [colorInputVisible, showColorInput, closeColorInput] = useBinaryState(false); +const ColorInput = ({ className, id, value, onChange, ...props }) => { + const [popupOpen, openPopup, closePopup, togglePopup] = useBinaryState(false); const [selectedColor, setSelectedColor] = React.useState(value); - - const confirmColorInput = React.useCallback((event) => { - if(typeof onChange === "function") { + React.useEffect(() => { + setSelectedColor(value); + }, [value]); + const onSubmit = React.useCallback((event) => { + if (typeof onChange === 'function') { event.nativeEvent.value = selectedColor; onChange(event); } - closeColorInput(); + + closePopup(); }, [selectedColor, onChange]); - - React.useEffect(() => { - setSelectedColor(value); - }, [value, colorInputVisible]); - - const modalBackgroundOnClick = React.useCallback((event) => { - if(event.target === event.currentTarget) { - closeColorInput(); - } - }, []); - return ( - - - { - colorInputVisible - ? - -
- -

Choose a color:

- - -
-
- : - null - } -
+ ( + +
Choose a color:
+ + +
+ )} + onCloseRequest={closePopup} + /> ); }; ColorInput.propTypes = { className: PropTypes.string, - id: PropTypes.string.isRequired, + id: PropTypes.string, value: PropTypes.string, onChange: PropTypes.func }; diff --git a/src/common/ColorInput/styles.less b/src/common/ColorInput/styles.less index c52eaaba4..deab9038d 100644 --- a/src/common/ColorInput/styles.less +++ b/src/common/ColorInput/styles.less @@ -1,49 +1,64 @@ -.color-input-modal { - background-color: var(--color-backgrounddarker40); +.color-input-modal-container { display: flex; flex-direction: column; - + align-items: center; + justify-content: center; + pointer-events: auto; + background-color: var(--color-backgrounddarker40); .color-input-container { + flex: none; position: relative; + z-index: 0; padding: 1rem; background-color: var(--color-surfacelighter); - margin: auto; - - * { - overflow: visible; - } - - .x-icon { + .close-button-container { position: absolute; top: 1rem; right: 1rem; - width: 1rem; - height: 1rem; - fill: var(--color-surfacedark); + width: 1.5rem; + height: 1.5rem; + padding: 0.25rem; + z-index: 1; + + &:hover, &:focus { + background-color: var(--color-surfacedark20); + } + + &:focus { + outline-color: var(--color-surfacedarker); + } + + .icon { + display: block; + width: 100%; + height: 100%; + fill: var(--color-surfacedarker); + } } - h1 { + .title { font-size: 1.2rem; } - .color-input { - margin: 1rem auto 0 auto; - :global(.a-color-picker-stack):not(:global(.a-color-picker-row-top)) canvas, :global(.a-color-picker-circle) { - border: solid 1px var(--color-surfacedark); - } - :global(.a-color-picker-circle) { - box-shadow: 0 0 .2rem var(--color-surfacedark); - } + .color-picker { + margin: 1rem; } - .button { - text-align: center; - color: var(--color-surfacelighter); - background-color: var(--color-signal5); + .submit-button-container { padding: 1rem; - margin-top: 1rem; + text-align: center; + background-color: var(--color-signal5); + color: var(--color-surfacelighter); + + &:hover, &:focus { + filter: brightness(1.2); + } + + &:focus { + outline-color: var(--color-surfacedarker); + } } } } \ No newline at end of file diff --git a/src/common/ColorPicker/ColorPicker.js b/src/common/ColorPicker/ColorPicker.js index 62b65a36b..aba4e053f 100644 --- a/src/common/ColorPicker/ColorPicker.js +++ b/src/common/ColorPicker/ColorPicker.js @@ -1,8 +1,10 @@ const React = require('react'); const PropTypes = require('prop-types'); +const classnames = require('classnames'); const AColorPicker = require('a-color-picker'); +const styles = require('./styles'); -const COLOR_FORMAT = 'rgbacss'; +const COLOR_FORMAT = 'hexcss4'; // TODO implement custom picker which is keyboard accessible const ColorPicker = ({ className, value, onChange }) => { @@ -32,7 +34,7 @@ const ColorPicker = ({ className, value, onChange }) => { } }, [value]); return ( -
+
); }; diff --git a/src/common/ColorPicker/styles.less b/src/common/ColorPicker/styles.less new file mode 100644 index 000000000..6cd52668a --- /dev/null +++ b/src/common/ColorPicker/styles.less @@ -0,0 +1,19 @@ +.color-picker-container { + overflow: visible; + + * { + overflow: visible; + } + + :global(.a-color-picker-stack):not(:global(.a-color-picker-row-top)) canvas, :global(.a-color-picker-circle) { + border: solid 1px var(--color-surfacedark); + } + + :global(.a-color-picker-circle) { + box-shadow: 0 0 .2rem var(--color-surfacedark); + } + + :global(.a-color-picker-clipbaord) { + pointer-events: none; + } +} \ No newline at end of file From ae4bfda5545ce6adb95640c198839e7f0f693467 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 12:00:06 +0300 Subject: [PATCH 47/96] unsubscrive from onchange listener on unmount --- src/common/ColorPicker/ColorPicker.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/ColorPicker/ColorPicker.js b/src/common/ColorPicker/ColorPicker.js index aba4e053f..223cefbb1 100644 --- a/src/common/ColorPicker/ColorPicker.js +++ b/src/common/ColorPicker/ColorPicker.js @@ -21,12 +21,14 @@ const ColorPicker = ({ className, value, onChange }) => { }); }, []); React.useEffect(() => { - pickerRef.current.off('change'); pickerRef.current.on('change', (picker, color) => { if (typeof onChange === 'function') { onChange(AColorPicker.parseColor(color, COLOR_FORMAT)); } }); + return () => { + pickerRef.current.off('change'); + }; }, [onChange]); React.useEffect(() => { if (AColorPicker.parseColor(pickerRef.current.color, COLOR_FORMAT) !== value) { From 671873aeb31961256f96c684a66408da90d4f45c Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 12:27:28 +0300 Subject: [PATCH 48/96] subtitles picker adapted to latest changes in common --- .../SubtitlesPicker/SubtitlesPicker.js | 294 +++++++----------- .../SubtitlesPicker/styles.less | 3 +- 2 files changed, 109 insertions(+), 188 deletions(-) diff --git a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/SubtitlesPicker.js b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/SubtitlesPicker.js index 785a54cfd..7e24eb44c 100644 --- a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/SubtitlesPicker.js +++ b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/SubtitlesPicker.js @@ -3,13 +3,16 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const { Modal } = require('stremio-router'); -const { ColorPicker } = require('stremio/common'); +const { ColorInput } = require('stremio/common'); const styles = require('./styles'); const ORIGIN_PRIORITIES = Object.freeze({ 'LOCAL': 1, 'EMBEDDED': 2 }); +const LANGUAGE_PRIORITIES = Object.freeze({ + 'English': 1 +}); const SUBTITLES_SIZE_LABELS = Object.freeze({ 1: '75%', 2: '100%', @@ -27,13 +30,9 @@ const comparatorWithPriorities = (priorities) => { if (!isNaN(valueB)) return 1; return a - b; }; -} +}; const NumberInput = ({ value, label, delta, onChange }) => { - if (value === null) { - return null; - } - return (
@@ -47,100 +46,55 @@ const NumberInput = ({ value, label, delta, onChange }) => { ); }; -const SubtitlesColorPicker = ({ label, value, onChange }) => { - const [open, setOpen] = React.useState(false); - const onOpen = () => setOpen(true); - const onClose = () => setOpen(false); - if (value === null) { - return null; - } - - return ( - -
-
-
{label}
-
- { - open ? - -
- -
-
- : - null - } - - ); -}; - -class SubtitlesPicker extends React.Component { - shouldComponentUpdate(nextProps, nextState) { - return nextProps.className !== this.props.className || - nextProps.subtitlesTracks !== this.props.subtitlesTracks || - nextProps.selectedSubtitlesTrackId !== this.props.selectedSubtitlesTrackId || - nextProps.subtitlesSize !== this.props.subtitlesSize || - nextProps.subtitlesDelay !== this.props.subtitlesDelay || - nextProps.subtitlesTextColor !== this.props.subtitlesTextColor || - nextProps.subtitlesBackgroundColor !== this.props.subtitlesBackgroundColor || - nextProps.subtitlesOutlineColor !== this.props.subtitlesOutlineColor; - } - - toggleSubtitleEnabled = () => { - const selectedSubtitlesTrackId = this.props.selectedSubtitlesTrackId === null && this.props.subtitlesTracks.length > 0 ? - this.props.subtitlesTracks[0].id +const SubtitlesPicker = (props) => { + const toggleSubtitleEnabled = React.useCallback(() => { + const selectedSubtitlesTrackId = props.selectedSubtitlesTrackId === null && props.subtitlesTracks.length > 0 ? + props.subtitlesTracks[0].id : null; - this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: selectedSubtitlesTrackId }); - } - - labelOnClick = (event) => { - const subtitleTrack = this.props.subtitlesTracks.find(({ label, origin }) => { + props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: selectedSubtitlesTrackId }); + }, [props.selectedSubtitlesTrackId, props.subtitlesTracks, props.dispatch]); + const labelOnClick = React.useCallback((event) => { + const subtitleTrack = props.subtitlesTracks.find(({ label, origin }) => { return label === event.currentTarget.dataset.label && origin === event.currentTarget.dataset.origin; }); if (subtitleTrack) { - this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: subtitleTrack.id }); + props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: subtitleTrack.id }); } - } - - variantOnClick = (event) => { - this.props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: event.currentTarget.dataset.trackId }); - } - - setsubtitlesSize = (event) => { - this.props.dispatch({ propName: 'subtitlesSize', propValue: event.currentTarget.dataset.value }); - } - - setSubtitlesDelay = (event) => { - this.props.dispatch({ propName: 'subtitlesDelay', propValue: event.currentTarget.dataset.value }); - } - - setSubtitlesTextColor = (color) => { - this.props.dispatch({ propName: 'subtitlesTextColor', propValue: color }); - } - - setSubtitlesBackgroundColor = (color) => { - this.props.dispatch({ propName: 'subtitlesBackgroundColor', propValue: color }); - } - - setSubtitlesOutlineColor = (color) => { - this.props.dispatch({ propName: 'subtitlesOutlineColor', propValue: color }); - } - - renderToggleButton({ selectedTrack }) { - return ( -
+ }, [props.subtitlesTracks, props.dispatch]); + const variantOnClick = React.useCallback((event) => { + props.dispatch({ propName: 'selectedSubtitlesTrackId', propValue: event.currentTarget.dataset.trackId }); + }, [props.dispatch]); + const setsubtitlesSize = React.useCallback((event) => { + props.dispatch({ propName: 'subtitlesSize', propValue: event.currentTarget.dataset.value }); + }, [props.dispatch]); + const setSubtitlesDelay = React.useCallback((event) => { + props.dispatch({ propName: 'subtitlesDelay', propValue: event.currentTarget.dataset.value }); + }, [props.dispatch]); + const setSubtitlesTextColor = React.useCallback((event) => { + props.dispatch({ propName: 'subtitlesTextColor', propValue: event.nativeEvent.value }); + }, [props.dispatch]); + const setSubtitlesBackgroundColor = React.useCallback((color) => { + props.dispatch({ propName: 'subtitlesBackgroundColor', propValue: color }); + }, [props.dispatch]); + const setSubtitlesOutlineColor = React.useCallback((color) => { + props.dispatch({ propName: 'subtitlesOutlineColor', propValue: color }); + }, [props.dispatch]); + const selectedTrack = props.subtitlesTracks.find(({ id }) => id === props.selectedSubtitlesTrackId); + const groupedTracks = props.subtitlesTracks.reduce((result, track) => { + result[track.origin] = result[track.origin] || {}; + result[track.origin][track.label] = result[track.origin][track.label] || []; + result[track.origin][track.label].push(track); + return result; + }, {}); + return ( +
+
ON
OFF
- ); - } - - renderLabelsList({ groupedTracks, selectedTrack }) { - return (
{ Object.keys(groupedTracks) @@ -150,13 +104,13 @@ class SubtitlesPicker extends React.Component {
{origin}
{ Object.keys(groupedTracks[origin]) - .sort(comparatorWithPriorities(this.props.languagePriorities)) + .sort(comparatorWithPriorities(LANGUAGE_PRIORITIES)) .map((label) => { const selected = selectedTrack && selectedTrack.label === label && selectedTrack.origin === origin; return (
- ); - } - - renderVariantsList({ groupedTracks, selectedTrack }) { - if (groupedTracks[selectedTrack.origin][selectedTrack.label].length <= 1) { - return null; - } - - return ( -
- { - groupedTracks[selectedTrack.origin][selectedTrack.label].map((track, index) => ( -
+
Subtitles are disabled
+
+ : +
+
Preferences
+ { + groupedTracks[selectedTrack.origin][selectedTrack.label].length > 1 ? +
+ { + groupedTracks[selectedTrack.origin][selectedTrack.label].map((track, index) => ( +
+ )) + } +
+ : + null + } +
+ +
Text color
+
+ {/* - )) - } -
- ); - } - - renderPreferences({ groupedTracks, selectedTrack }) { - if (!selectedTrack) { - return ( -
-
Subtitles are disabled
-
- ); - } - - return ( -
-
Preferences
- {this.renderVariantsList({ groupedTracks, selectedTrack })} - - - - - -
- ); - } - - render() { - const selectedTrack = this.props.subtitlesTracks.find(({ id }) => id === this.props.selectedSubtitlesTrackId); - const groupedTracks = this.props.subtitlesTracks.reduce((result, track) => { - result[track.origin] = result[track.origin] || {}; - result[track.origin][track.label] = result[track.origin][track.label] || []; - result[track.origin][track.label].push(track); - return result; - }, {}); - - return ( -
- {this.renderToggleButton({ selectedTrack })} - {this.renderLabelsList({ groupedTracks, selectedTrack })} - {this.renderPreferences({ groupedTracks, selectedTrack })} -
- ); - } -} + */} + + +
+ } +
+ ); +}; SubtitlesPicker.propTypes = { className: PropTypes.string, - languagePriorities: PropTypes.objectOf(PropTypes.number).isRequired, + languagePriorities: PropTypes.objectOf(PropTypes.number), subtitlesTracks: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, label: PropTypes.string.isRequired, origin: PropTypes.string.isRequired - })).isRequired, + })), selectedSubtitlesTrackId: PropTypes.string, subtitlesSize: PropTypes.number, subtitlesDelay: PropTypes.number, subtitlesTextColor: PropTypes.string, subtitlesBackgroundColor: PropTypes.string, subtitlesOutlineColor: PropTypes.string, - dispatch: PropTypes.func.isRequired -}; -SubtitlesPicker.defaultProps = { - subtitlesTracks: Object.freeze([]), - languagePriorities: Object.freeze({ - English: 1 - }) + dispatch: PropTypes.func }; module.exports = SubtitlesPicker; diff --git a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/styles.less b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/styles.less index ac9bd7441..9b4537498 100644 --- a/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/styles.less +++ b/src/routes/Player/ControlBar/SubtitlesButton/SubtitlesPicker/styles.less @@ -1,6 +1,5 @@ .subtitles-picker-container { - width: calc(var(--subtitles-picker-button-size) * 14); - height: calc(var(--subtitles-picker-button-size) * 9); + --subtitles-picker-button-size: 2.5rem; font-size: calc(var(--subtitles-picker-button-size) * 0.45); padding: calc(var(--subtitles-picker-button-size) * 0.3); gap: calc(var(--subtitles-picker-button-size) * 0.3); From 082470ed165900a56b246ebd0c93cc3cbd8d539d Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 17:39:22 +0300 Subject: [PATCH 49/96] router focus implemented based on focus lock --- package.json | 5 +- .../FocusableContext/FocusableContext.js | 7 --- .../FocusableContext/FocusableProvider.js | 57 ------------------- src/router/FocusableContext/index.js | 7 --- src/router/FocusableContext/useFocusable.js | 8 --- .../FocusedRouteContext.js | 7 +++ src/router/FocusedRouteContext/index.js | 7 +++ .../FocusedRouteContext/useFocusedRoute.js | 8 +++ src/router/Modal/Modal.js | 16 ++---- src/router/Route/Route.js | 14 ++--- src/router/Router/Router.js | 17 +++--- .../RoutesContainerContext.js | 7 --- .../RoutesContainerProvider.js | 25 -------- src/router/RoutesContainerContext/index.js | 7 --- .../useRoutesContainer.js | 8 --- src/router/index.js | 4 +- yarn.lock | 25 +++++++- 17 files changed, 68 insertions(+), 161 deletions(-) delete mode 100644 src/router/FocusableContext/FocusableContext.js delete mode 100644 src/router/FocusableContext/FocusableProvider.js delete mode 100644 src/router/FocusableContext/index.js delete mode 100644 src/router/FocusableContext/useFocusable.js create mode 100644 src/router/FocusedRouteContext/FocusedRouteContext.js create mode 100644 src/router/FocusedRouteContext/index.js create mode 100644 src/router/FocusedRouteContext/useFocusedRoute.js delete mode 100644 src/router/RoutesContainerContext/RoutesContainerContext.js delete mode 100644 src/router/RoutesContainerContext/RoutesContainerProvider.js delete mode 100644 src/router/RoutesContainerContext/index.js delete mode 100644 src/router/RoutesContainerContext/useRoutesContainer.js diff --git a/package.json b/package.json index d2f4b81e8..03b12da14 100755 --- a/package.json +++ b/package.json @@ -20,10 +20,11 @@ "prop-types": "15.7.2", "react": "16.9.0", "react-dom": "16.9.0", + "react-focus-lock": "2.1.1", "spatial-navigation-polyfill": "git+ssh://git@github.com/NikolaBorislavovHristov/spatial-navigation.git#964d09bf2b0853e27af6c25924b595d6621a019d", "stremio-colors": "git+ssh://git@github.com/Stremio/stremio-colors.git#v2.0.4", - "stremio-icons": "git+ssh://git@github.com/Stremio/stremio-icons.git#v1.0.11", "stremio-core-web": "git+ssh://git@github.com/stremio/stremio-core-web.git#v0.6.0", + "stremio-icons": "git+ssh://git@github.com/Stremio/stremio-icons.git#v1.0.11", "vtt.js": "0.13.0" }, "devDependencies": { @@ -54,4 +55,4 @@ "webpack-cli": "3.3.9", "webpack-dev-server": "3.8.1" } -} \ No newline at end of file +} diff --git a/src/router/FocusableContext/FocusableContext.js b/src/router/FocusableContext/FocusableContext.js deleted file mode 100644 index 5fbec1a1f..000000000 --- a/src/router/FocusableContext/FocusableContext.js +++ /dev/null @@ -1,7 +0,0 @@ -const React = require('react'); - -const FocusableContext = React.createContext(false); - -FocusableContext.displayName = 'FocusableContext'; - -module.exports = FocusableContext; diff --git a/src/router/FocusableContext/FocusableProvider.js b/src/router/FocusableContext/FocusableProvider.js deleted file mode 100644 index 3bf6bafab..000000000 --- a/src/router/FocusableContext/FocusableProvider.js +++ /dev/null @@ -1,57 +0,0 @@ -const React = require('react'); -const PropTypes = require('prop-types'); -const { useModalsContainer } = require('../ModalsContainerContext'); -const { useRoutesContainer } = require('../RoutesContainerContext'); -const FocusableContext = require('./FocusableContext'); - -const FocusableProvider = ({ children, onRoutesContainerChildrenChange, onModalsContainerChildrenChange }) => { - const routesContainer = useRoutesContainer(); - const modalsContainer = useModalsContainer(); - const contentContainerRef = React.useRef(null); - const [focusable, setFocusable] = React.useState(false); - React.useEffect(() => { - const onContainerChildrenChange = () => { - setFocusable( - onRoutesContainerChildrenChange({ - routesContainer: routesContainer, - contentContainer: contentContainerRef.current - }) - && - onModalsContainerChildrenChange({ - modalsContainer: modalsContainer, - contentContainer: contentContainerRef.current - }) - ); - }; - const routesContainerChildrenObserver = new MutationObserver(onContainerChildrenChange); - const modalsContainerChildrenObserver = new MutationObserver(onContainerChildrenChange); - routesContainerChildrenObserver.observe(routesContainer, { childList: true }); - modalsContainerChildrenObserver.observe(modalsContainer, { childList: true }); - onContainerChildrenChange(); - return () => { - routesContainerChildrenObserver.disconnect(); - modalsContainerChildrenObserver.disconnect(); - }; - }, [routesContainer, modalsContainer, onRoutesContainerChildrenChange, onModalsContainerChildrenChange]); - React.useEffect(() => { - if (focusable && !contentContainerRef.current.contains(document.activeElement)) { - contentContainerRef.current.focus(); - } - }, [focusable]); - return ( - - {React.cloneElement(React.Children.only(children), { - ref: contentContainerRef, - tabIndex: -1 - })} - - ); -}; - -FocusableProvider.propTypes = { - children: PropTypes.node.isRequired, - onRoutesContainerChildrenChange: PropTypes.func.isRequired, - onModalsContainerChildrenChange: PropTypes.func.isRequired -}; - -module.exports = FocusableProvider; diff --git a/src/router/FocusableContext/index.js b/src/router/FocusableContext/index.js deleted file mode 100644 index 2e8686ca1..000000000 --- a/src/router/FocusableContext/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const FocusableProvider = require('./FocusableProvider'); -const useFocusable = require('./useFocusable'); - -module.exports = { - FocusableProvider, - useFocusable -}; diff --git a/src/router/FocusableContext/useFocusable.js b/src/router/FocusableContext/useFocusable.js deleted file mode 100644 index 751eb1d25..000000000 --- a/src/router/FocusableContext/useFocusable.js +++ /dev/null @@ -1,8 +0,0 @@ -const React = require('react'); -const FocusableContext = require('./FocusableContext'); - -const useFocusable = () => { - return React.useContext(FocusableContext); -}; - -module.exports = useFocusable; diff --git a/src/router/FocusedRouteContext/FocusedRouteContext.js b/src/router/FocusedRouteContext/FocusedRouteContext.js new file mode 100644 index 000000000..d1323e8e9 --- /dev/null +++ b/src/router/FocusedRouteContext/FocusedRouteContext.js @@ -0,0 +1,7 @@ +const React = require('react'); + +const FocusedRouteContext = React.createContext(false); + +FocusedRouteContext.displayName = 'FocusedRouteContext'; + +module.exports = FocusedRouteContext; diff --git a/src/router/FocusedRouteContext/index.js b/src/router/FocusedRouteContext/index.js new file mode 100644 index 000000000..5163ca12c --- /dev/null +++ b/src/router/FocusedRouteContext/index.js @@ -0,0 +1,7 @@ +const FocusedRouteContext = require('./FocusedRouteContext'); +const useFocusedRoute = require('./useFocusedRoute'); + +module.exports = { + FocusedRouteProvider: FocusedRouteContext.Provider, + useFocusedRoute +}; diff --git a/src/router/FocusedRouteContext/useFocusedRoute.js b/src/router/FocusedRouteContext/useFocusedRoute.js new file mode 100644 index 000000000..3c893f027 --- /dev/null +++ b/src/router/FocusedRouteContext/useFocusedRoute.js @@ -0,0 +1,8 @@ +const React = require('react'); +const FocusedRouteContext = require('./FocusedRouteContext'); + +const useFocusedRoute = () => { + return React.useContext(FocusedRouteContext); +}; + +module.exports = useFocusedRoute; diff --git a/src/router/Modal/Modal.js b/src/router/Modal/Modal.js index 89bb8d1e5..115b7aaab 100644 --- a/src/router/Modal/Modal.js +++ b/src/router/Modal/Modal.js @@ -1,21 +1,15 @@ const React = require('react'); const ReactDOM = require('react-dom'); const classnames = require('classnames'); -const { FocusableProvider } = require('../FocusableContext'); +const FocusLock = require('react-focus-lock').default; const { useModalsContainer } = require('../ModalsContainerContext'); -const Modal = (props) => { +const Modal = ({ className, children, ...props }) => { const modalsContainer = useModalsContainer(); - const onRoutesContainerChildrenChange = React.useCallback(({ routesContainer, contentContainer }) => { - return routesContainer.lastElementChild.contains(contentContainer); - }, []); - const onModalsContainerChildrenChange = React.useCallback(({ modalsContainer, contentContainer }) => { - return modalsContainer.lastElementChild === contentContainer; - }, []); return ReactDOM.createPortal( - -
- , + + {children} + , modalsContainer ); }; diff --git a/src/router/Route/Route.js b/src/router/Route/Route.js index 81d978396..440b20bb0 100644 --- a/src/router/Route/Route.js +++ b/src/router/Route/Route.js @@ -1,21 +1,15 @@ const React = require('react'); const PropTypes = require('prop-types'); -const { FocusableProvider } = require('../FocusableContext'); +const FocusLock = require('react-focus-lock').default; const { ModalsContainerProvider } = require('../ModalsContainerContext'); const Route = ({ children }) => { - const onRoutesContainerChildrenChange = React.useCallback(({ routesContainer, contentContainer }) => { - return routesContainer.lastElementChild.contains(contentContainer); - }, []); - const onModalsContainerChildrenChange = React.useCallback(({ modalsContainer }) => { - return modalsContainer.childElementCount === 0; - }, []); return (
- -
{children}
-
+ + {children} +
); diff --git a/src/router/Router/Router.js b/src/router/Router/Router.js index 50bbab579..e8617bfca 100644 --- a/src/router/Router/Router.js +++ b/src/router/Router/Router.js @@ -1,9 +1,10 @@ const React = require('react'); const ReactIs = require('react-is'); const PropTypes = require('prop-types'); +const classnames = require('classnames'); const UrlUtils = require('url'); +const { FocusedRouteProvider } = require('../FocusedRouteContext'); const Route = require('../Route'); -const { RoutesContainerProvider } = require('../RoutesContainerContext'); const Router = ({ className, onPathNotMatch, ...props }) => { const [{ homePath, viewsConfig }] = React.useState(() => ({ @@ -96,17 +97,19 @@ const Router = ({ className, onPathNotMatch, ...props }) => { }; }, [onPathNotMatch]); return ( - +
{ views .filter(view => view !== null) - .map(({ key, component, urlParams, queryParams }) => ( - - {React.createElement(component, { urlParams, queryParams })} - + .map(({ key, component, urlParams, queryParams }, index, views) => ( + + + {React.createElement(component, { urlParams, queryParams })} + + )) } - +
); }; diff --git a/src/router/RoutesContainerContext/RoutesContainerContext.js b/src/router/RoutesContainerContext/RoutesContainerContext.js deleted file mode 100644 index 4c1adcbb5..000000000 --- a/src/router/RoutesContainerContext/RoutesContainerContext.js +++ /dev/null @@ -1,7 +0,0 @@ -const React = require('react'); - -const RoutesContainerContext = React.createContext(null); - -RoutesContainerContext.displayName = 'RoutesContainerContext'; - -module.exports = RoutesContainerContext; diff --git a/src/router/RoutesContainerContext/RoutesContainerProvider.js b/src/router/RoutesContainerContext/RoutesContainerProvider.js deleted file mode 100644 index 7f04b062c..000000000 --- a/src/router/RoutesContainerContext/RoutesContainerProvider.js +++ /dev/null @@ -1,25 +0,0 @@ -const React = require('react'); -const PropTypes = require('prop-types'); -const classnames = require('classnames'); -const RoutesContainerContext = require('./RoutesContainerContext'); - -const RoutesContainerProvider = ({ className, children }) => { - const [container, setContainer] = React.useState(null); - return ( - -
- {container instanceof HTMLElement ? children : null} -
-
- ); -}; - -RoutesContainerProvider.propTypes = { - className: PropTypes.string, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node - ]) -}; - -module.exports = RoutesContainerProvider; diff --git a/src/router/RoutesContainerContext/index.js b/src/router/RoutesContainerContext/index.js deleted file mode 100644 index b96f7dc8b..000000000 --- a/src/router/RoutesContainerContext/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const RoutesContainerProvider = require('./RoutesContainerProvider'); -const useRoutesContainer = require('./useRoutesContainer'); - -module.exports = { - RoutesContainerProvider, - useRoutesContainer -}; diff --git a/src/router/RoutesContainerContext/useRoutesContainer.js b/src/router/RoutesContainerContext/useRoutesContainer.js deleted file mode 100644 index 046b294fc..000000000 --- a/src/router/RoutesContainerContext/useRoutesContainer.js +++ /dev/null @@ -1,8 +0,0 @@ -const React = require('react'); -const RoutesContainerContext = require('./RoutesContainerContext'); - -const useRoutesContainer = () => { - return React.useContext(RoutesContainerContext); -}; - -module.exports = useRoutesContainer; diff --git a/src/router/index.js b/src/router/index.js index 9407ff0da..71623af39 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,9 +1,9 @@ -const { useFocusable } = require('./FocusableContext'); +const { useFocusedRoute } = require('./FocusedRouteContext'); const Modal = require('./Modal'); const Router = require('./Router'); module.exports = { - useFocusable, + useFocusedRoute, Modal, Router }; diff --git a/yarn.lock b/yarn.lock index dc8ca098e..7fcc217df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4404,7 +4404,7 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -focus-lock@^0.6.3: +focus-lock@^0.6.3, focus-lock@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.5.tgz#f6eb37832a9b1b205406175f5277396a28c0fce1" integrity sha512-i/mVBOoa9o+tl+u9owOJUF8k8L85odZNIsctB+JAK2HFT8jckiBwmk+3uydlm6FN8czgnkIwQtBv6yyAbrzXjw== @@ -7662,7 +7662,7 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-clientside-effect@^1.2.0: +react-clientside-effect@^1.2.0, react-clientside-effect@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837" integrity sha512-nRmoyxeok5PBO6ytPvSjKp9xwXg9xagoTK1mMjwnQxqM9Hd7MNPl+LS1bOSOe+CV2+4fnEquc7H/S8QD3q697A== @@ -7761,6 +7761,17 @@ react-fast-compare@2.0.4: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== +react-focus-lock@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.1.1.tgz#49762377119ecd52eb56519ddd10a87c0c1ddd98" + integrity sha512-IKfloS8Ifx5v+Gwm64hoTqT0NmzXksTKYROOAq+HlBIxUqUS2yA5NNzQJtuOsx3nyPs7wrgycbVsffRfcA5OTw== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^0.6.5" + prop-types "^15.6.2" + react-clientside-effect "^1.2.2" + use-sidecar "^1.0.1" + react-focus-lock@^1.18.3: version "1.19.1" resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-1.19.1.tgz#2f3429793edaefe2d077121f973ce5a3c7a0651a" @@ -9132,7 +9143,7 @@ ts-pnp@^1.1.2: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90" integrity sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw== -tslib@^1.9.0: +tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== @@ -9330,6 +9341,14 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-sidecar@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.1.tgz#75c7a5fdacc14bd3ab64992c638e45a396ad2fad" + integrity sha512-CLTDS2AZmUcXXFnxP/h/OadtvBOoHHnLYMMpKGntb5vKOQT94icrXMXX0mEdGiMhQU8vxHlndB72sRwRBHXTzw== + dependencies: + detect-node "^2.0.4" + tslib "^1.9.3" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From e426afb07ac793e12083d699c9986b290b9ca7d5 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:05:41 +0300 Subject: [PATCH 50/96] disable focus lock in modal implemented --- src/router/Modal/Modal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/router/Modal/Modal.js b/src/router/Modal/Modal.js index 115b7aaab..1332df16b 100644 --- a/src/router/Modal/Modal.js +++ b/src/router/Modal/Modal.js @@ -4,10 +4,10 @@ const classnames = require('classnames'); const FocusLock = require('react-focus-lock').default; const { useModalsContainer } = require('../ModalsContainerContext'); -const Modal = ({ className, children, ...props }) => { +const Modal = ({ className, disabled, children, ...props }) => { const modalsContainer = useModalsContainer(); return ReactDOM.createPortal( - + {children} , modalsContainer From 1e583c9a94787c74f9a2094f6f5bc7b25592d203 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:08:18 +0300 Subject: [PATCH 51/96] propTypes added to Modal --- src/router/Modal/Modal.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/router/Modal/Modal.js b/src/router/Modal/Modal.js index 1332df16b..1a3cba52b 100644 --- a/src/router/Modal/Modal.js +++ b/src/router/Modal/Modal.js @@ -4,14 +4,24 @@ const classnames = require('classnames'); const FocusLock = require('react-focus-lock').default; const { useModalsContainer } = require('../ModalsContainerContext'); -const Modal = ({ className, disabled, children, ...props }) => { +const Modal = ({ className, autoFocus, disabled, children, ...props }) => { const modalsContainer = useModalsContainer(); return ReactDOM.createPortal( - + {children} , modalsContainer ); }; +Modal.propTypes = { + className: PropTypes.string, + autoFocus: PropTypes.bool, + disabled: PropTypes.bool, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node + ]) +}; + module.exports = Modal; From a7e58b1717711126c464cf5ced7652796a681fd3 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:08:33 +0300 Subject: [PATCH 52/96] useTabIndex dropped --- src/common/index.js | 4 +--- src/common/useTabIndex.js | 11 ----------- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 src/common/useTabIndex.js diff --git a/src/common/index.js b/src/common/index.js index 4f9e1707e..347d6a07b 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -25,7 +25,6 @@ const useLiveRef = require('./useLiveRef'); const useLocationHash = require('./useLocationHash'); const useRouteActive = require('./useRouteActive'); const useSpreadState = require('./useSpreadState'); -const useTabIndex = require('./useTabIndex'); module.exports = { Button, @@ -54,6 +53,5 @@ module.exports = { useLiveRef, useLocationHash, useRouteActive, - useSpreadState, - useTabIndex + useSpreadState }; diff --git a/src/common/useTabIndex.js b/src/common/useTabIndex.js deleted file mode 100644 index fc74a088d..000000000 --- a/src/common/useTabIndex.js +++ /dev/null @@ -1,11 +0,0 @@ -const { useFocusable } = require('stremio-router'); - -const useTabIndex = (tabIndex, disabled) => { - const focusable = useFocusable(); - return (tabIndex === null || isNaN(tabIndex)) ? - (focusable && !disabled ? 0 : -1) - : - tabIndex; -}; - -module.exports = useTabIndex; From 7cf24b818047d5d05cad2fa0a300b8f50a953831 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:09:34 +0300 Subject: [PATCH 53/96] Button adapted to useTabIndex removal --- src/common/Button/Button.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/Button/Button.js b/src/common/Button/Button.js index d163a3413..53e1bc01a 100644 --- a/src/common/Button/Button.js +++ b/src/common/Button/Button.js @@ -1,10 +1,8 @@ const React = require('react'); const classnames = require('classnames'); -const useTabIndex = require('stremio/common/useTabIndex'); const styles = require('./styles'); const Button = React.forwardRef(({ children, ...props }, ref) => { - const tabIndex = useTabIndex(props.tabIndex, props.disabled); const onKeyUp = React.useCallback((event) => { if (typeof props.onKeyUp === 'function') { props.onKeyUp(event); @@ -29,10 +27,10 @@ const Button = React.forwardRef(({ children, ...props }, ref) => { return React.createElement( typeof props.href === 'string' && props.href.length > 0 ? 'a' : 'div', { + tabIndex: 0, ...props, ref, className: classnames(props.className, styles['button-container'], { 'disabled': props.disabled }), - tabIndex, onKeyUp, onMouseDown }, From 2ef5999bfdccfb68c6b4220458227ac7cc0bc6fd Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:10:33 +0300 Subject: [PATCH 54/96] checkbox default size added --- src/common/Checkbox/styles.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/Checkbox/styles.less b/src/common/Checkbox/styles.less index 7efb48c73..38c9a4209 100644 --- a/src/common/Checkbox/styles.less +++ b/src/common/Checkbox/styles.less @@ -7,6 +7,8 @@ .icon { display: block; + width: 1rem; + height: 1rem; fill: var(--color-surfacelighter); } } \ No newline at end of file From b5f09cabe985df40f1a031b1ddbfa2cfbd1fa8c1 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 22:12:53 +0300 Subject: [PATCH 55/96] ColorPicker moved inside ColorInput --- src/common/ColorInput/ColorInput.js | 2 +- src/common/{ => ColorInput}/ColorPicker/ColorPicker.js | 0 src/common/{ => ColorInput}/ColorPicker/index.js | 0 src/common/{ => ColorInput}/ColorPicker/styles.less | 0 src/common/index.js | 2 -- storybook/stories/ColorPicker/ColorPicker.js | 2 +- 6 files changed, 2 insertions(+), 4 deletions(-) rename src/common/{ => ColorInput}/ColorPicker/ColorPicker.js (100%) rename src/common/{ => ColorInput}/ColorPicker/index.js (100%) rename src/common/{ => ColorInput}/ColorPicker/styles.less (100%) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index 883f9e4ff..cafb010d3 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -3,8 +3,8 @@ const PropTypes = require('prop-types'); const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); const Popup = require('stremio/common/Popup'); -const ColorPicker = require('stremio/common/ColorPicker'); const useBinaryState = require('stremio/common/useBinaryState'); +const ColorPicker = require('./ColorPicker'); const styles = require('./styles'); const ColorInput = ({ className, id, value, onChange, ...props }) => { diff --git a/src/common/ColorPicker/ColorPicker.js b/src/common/ColorInput/ColorPicker/ColorPicker.js similarity index 100% rename from src/common/ColorPicker/ColorPicker.js rename to src/common/ColorInput/ColorPicker/ColorPicker.js diff --git a/src/common/ColorPicker/index.js b/src/common/ColorInput/ColorPicker/index.js similarity index 100% rename from src/common/ColorPicker/index.js rename to src/common/ColorInput/ColorPicker/index.js diff --git a/src/common/ColorPicker/styles.less b/src/common/ColorInput/ColorPicker/styles.less similarity index 100% rename from src/common/ColorPicker/styles.less rename to src/common/ColorInput/ColorPicker/styles.less diff --git a/src/common/index.js b/src/common/index.js index 347d6a07b..6fd87f1e9 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,6 +1,5 @@ const Button = require('./Button'); const Checkbox = require('./Checkbox'); -const ColorPicker = require('./ColorPicker'); const ColorInput = require('./ColorInput'); const Dropdown = require('./Dropdown'); const Image = require('./Image'); @@ -29,7 +28,6 @@ const useSpreadState = require('./useSpreadState'); module.exports = { Button, Checkbox, - ColorPicker, ColorInput, Dropdown, Image, diff --git a/storybook/stories/ColorPicker/ColorPicker.js b/storybook/stories/ColorPicker/ColorPicker.js index 8ceefe947..385d40142 100644 --- a/storybook/stories/ColorPicker/ColorPicker.js +++ b/storybook/stories/ColorPicker/ColorPicker.js @@ -1,6 +1,6 @@ const React = require('react'); const { storiesOf } = require('@storybook/react'); -const { ColorPicker } = require('stremio/common'); +const ColorPicker = require('stremio/common/ColorInput/ColorPicker'); const styles = require('./styles'); storiesOf('ColorPicker', module).add('ColorPicker', () => { From 41841fefafb2c929eb337e5da82a32b60f758ca5 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 23:25:29 +0300 Subject: [PATCH 56/96] Modal autofocus default value fixed --- src/router/Modal/Modal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/router/Modal/Modal.js b/src/router/Modal/Modal.js index 1a3cba52b..a27802939 100644 --- a/src/router/Modal/Modal.js +++ b/src/router/Modal/Modal.js @@ -1,5 +1,6 @@ const React = require('react'); const ReactDOM = require('react-dom'); +const PropTypes = require('prop-types'); const classnames = require('classnames'); const FocusLock = require('react-focus-lock').default; const { useModalsContainer } = require('../ModalsContainerContext'); @@ -7,7 +8,7 @@ const { useModalsContainer } = require('../ModalsContainerContext'); const Modal = ({ className, autoFocus, disabled, children, ...props }) => { const modalsContainer = useModalsContainer(); return ReactDOM.createPortal( - + {children} , modalsContainer From 645ae7931dc3dc111aa66ee8b06a80f4401f23f3 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sat, 5 Oct 2019 23:42:18 +0300 Subject: [PATCH 57/96] ColorInput implemented with Modal --- src/common/ColorInput/ColorInput.js | 90 +++++++++++++++++------------ src/common/ColorInput/styles.less | 71 ++++++++++++++--------- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index cafb010d3..b16f409d1 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -1,59 +1,75 @@ const React = require('react'); const PropTypes = require('prop-types'); const Icon = require('stremio-icons/dom'); +const { Modal } = require('stremio-router'); const Button = require('stremio/common/Button'); -const Popup = require('stremio/common/Popup'); const useBinaryState = require('stremio/common/useBinaryState'); const ColorPicker = require('./ColorPicker'); const styles = require('./styles'); -const ColorInput = ({ className, id, value, onChange, ...props }) => { - const [popupOpen, openPopup, closePopup, togglePopup] = useBinaryState(false); - const [selectedColor, setSelectedColor] = React.useState(value); +const ColorInput = ({ className, value, onChange, ...props }) => { + const labelRef = React.useRef(null); + const [modalOpen, openModal, closeModal] = useBinaryState(false); + const [tempValue, setTempValue] = React.useState(value); React.useEffect(() => { - setSelectedColor(value); - }, [value]); - const onSubmit = React.useCallback((event) => { + setTempValue(value); + }, [value, modalOpen]); + const modalContainerOnMouseDown = React.useCallback((event) => { + if (!event.nativeEvent.closeModalPrevented) { + closeModal(); + } + }, []); + const modalContentOnMouseDown = React.useCallback((event) => { + event.nativeEvent.closeModalPrevented = true; + }, []); + const submitButtonOnClick = React.useCallback((event) => { + event.type = 'change'; + event.currentTarget = labelRef.current; + event.currentTarget.value = tempValue; if (typeof onChange === 'function') { - event.nativeEvent.value = selectedColor; onChange(event); } - closePopup(); - }, [selectedColor, onChange]); + event.currentTarget.value = undefined; + if (!event.nativeEvent.closeModalPrevented) { + closeModal(); + } + }, [tempValue, onChange]); return ( - ( - -
Choose a color:
- - -
- )} - onCloseRequest={closePopup} - /> + + +
+ + +
+ + : + null + } + ); }; ColorInput.propTypes = { className: PropTypes.string, - id: PropTypes.string, value: PropTypes.string, onChange: PropTypes.func }; diff --git a/src/common/ColorInput/styles.less b/src/common/ColorInput/styles.less index deab9038d..8242c6e67 100644 --- a/src/common/ColorInput/styles.less +++ b/src/common/ColorInput/styles.less @@ -1,6 +1,6 @@ .color-input-modal-container { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; justify-content: center; pointer-events: auto; @@ -8,49 +8,60 @@ .color-input-container { flex: none; - position: relative; - z-index: 0; + display: flex; + flex-direction: column; + align-items: center; + max-width: 25rem; padding: 1rem; background-color: var(--color-surfacelighter); - .close-button-container { - position: absolute; - top: 1rem; - right: 1rem; - width: 1.5rem; - height: 1.5rem; - padding: 0.25rem; - z-index: 1; + .header-container { + flex: none; + align-self: stretch; + display: flex; + flex-direction: row; + align-items: flex-start; - &:hover, &:focus { - background-color: var(--color-surfacedark20); + .title { + flex: 1; + margin-right: 1rem; + font-size: 1.2rem; + max-height: 2.4em; } - &:focus { - outline-color: var(--color-surfacedarker); - } + .close-button-container { + flex: none; + width: 1.5rem; + height: 1.5rem; + padding: 0.25rem; - .icon { - display: block; - width: 100%; - height: 100%; - fill: var(--color-surfacedarker); - } - } + &:hover, &:focus { + background-color: var(--color-surfacedark20); + } - .title { - font-size: 1.2rem; + &: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; - text-align: center; background-color: var(--color-signal5); - color: var(--color-surfacelighter); &:hover, &:focus { filter: brightness(1.2); @@ -59,6 +70,12 @@ &: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 acd75db430e8fa4f3f8f3398425d28d10c2b9919 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 6 Oct 2019 00:18:09 +0300 Subject: [PATCH 58/96] ColorInput uses data attribute to pass value --- src/common/ColorInput/ColorInput.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index b16f409d1..026926276 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -7,8 +7,7 @@ const useBinaryState = require('stremio/common/useBinaryState'); const ColorPicker = require('./ColorPicker'); const styles = require('./styles'); -const ColorInput = ({ className, value, onChange, ...props }) => { - const labelRef = React.useRef(null); +const ColorInput = ({ value, onChange, ...props }) => { const [modalOpen, openModal, closeModal] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); React.useEffect(() => { @@ -23,25 +22,19 @@ const ColorInput = ({ className, value, onChange, ...props }) => { event.nativeEvent.closeModalPrevented = true; }, []); const submitButtonOnClick = React.useCallback((event) => { - event.type = 'change'; - event.currentTarget = labelRef.current; - event.currentTarget.value = tempValue; if (typeof onChange === 'function') { onChange(event); } - event.currentTarget.value = undefined; if (!event.nativeEvent.closeModalPrevented) { closeModal(); } - }, [tempValue, onChange]); + }, [onChange]); return (
-
@@ -69,7 +62,6 @@ const ColorInput = ({ className, value, onChange, ...props }) => { }; ColorInput.propTypes = { - className: PropTypes.string, value: PropTypes.string, onChange: PropTypes.func }; From f20d04d5ee7fef7f8a686840c697e4f925b48464 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 6 Oct 2019 11:48:04 +0300 Subject: [PATCH 59/96] Popup/Multiselect api changed --- src/common/Dropdown/index.js | 3 - src/common/Dropdown/styles.less | 74 ------- .../Multiselect.js} | 75 ++++---- src/common/Multiselect/index.js | 3 + src/common/Multiselect/styles.less | 78 ++++++++ src/common/Popup/Popup.js | 181 ++++++------------ src/common/Popup/styles.less | 20 +- src/common/index.js | 4 +- 8 files changed, 197 insertions(+), 241 deletions(-) delete mode 100644 src/common/Dropdown/index.js delete mode 100644 src/common/Dropdown/styles.less rename src/common/{Dropdown/Dropdown.js => Multiselect/Multiselect.js} (51%) create mode 100644 src/common/Multiselect/index.js create mode 100644 src/common/Multiselect/styles.less diff --git a/src/common/Dropdown/index.js b/src/common/Dropdown/index.js deleted file mode 100644 index 3ac11be07..000000000 --- a/src/common/Dropdown/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const Dropdown = require('./Dropdown'); - -module.exports = Dropdown; diff --git a/src/common/Dropdown/styles.less b/src/common/Dropdown/styles.less deleted file mode 100644 index af7f98ae0..000000000 --- a/src/common/Dropdown/styles.less +++ /dev/null @@ -1,74 +0,0 @@ -.dropdown-label-container { - display: flex; - flex-direction: row; - align-items: center; - padding: 0 1rem; - background-color: var(--color-backgroundlighter); - - &:hover, &:focus { - filter: brightness(1.2); - } - - &:global(.active) { - background-color: var(--color-surfacelight); - - .label { - color: var(--color-backgrounddarker); - } - - .icon { - fill: var(--color-backgrounddarker); - } - } - - .label { - flex: 1; - max-height: 2.4em; - color: var(--color-surfacelighter); - } - - .icon { - flex: none; - width: 1rem; - height: 1rem; - margin-left: 1rem; - fill: var(--color-surfacelighter); - } -} - -.dropdown-menu-container { - .dropdown-option-container { - display: flex; - flex-direction: row; - align-items: center; - padding: 1rem; - background-color: var(--color-backgroundlighter); - - &:global(.selected) { - background-color: var(--color-surfacedarker); - - .icon { - display: block; - } - } - - &:hover, &:focus { - background-color: var(--color-surfacedark); - } - - .label { - flex: 1; - max-height: 4.8em; - color: var(--color-surfacelighter); - } - - .icon { - flex: none; - display: none; - width: 1rem; - height: 1rem; - margin-left: 1rem; - fill: var(--color-surfacelighter); - } - } -} \ No newline at end of file diff --git a/src/common/Dropdown/Dropdown.js b/src/common/Multiselect/Multiselect.js similarity index 51% rename from src/common/Dropdown/Dropdown.js rename to src/common/Multiselect/Multiselect.js index d9698785d..a02e5476b 100644 --- a/src/common/Dropdown/Dropdown.js +++ b/src/common/Multiselect/Multiselect.js @@ -7,9 +7,24 @@ const Popup = require('stremio/common/Popup'); const useBinaryState = require('stremio/common/useBinaryState'); const styles = require('./styles'); -// TODO rename to multiselect -const Dropdown = ({ className, menuClassName, menuMatchLabelWidth, renderLabel, name, selected, options, tabIndex, onOpen, onClose, onSelect }) => { +const Multiselect = ({ className, direction, title, renderLabelContent, options, selected, onOpen, onClose, onSelect, ...props }) => { + options = Array.isArray(options) ? + options.filter(option => option && typeof option.value === 'string') + : + []; + selected = Array.isArray(selected) ? + selected.filter(value => typeof value === 'string') + : + []; const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false); + const popupLabelOnClick = React.useCallback((event) => { + if (!event.nativeEvent.togglePopupPrevented) { + toggleMenu(); + } + }, [toggleMenu]); + const popupMenuOnClick = React.useCallback((event) => { + event.nativeEvent.togglePopupPrevented = true; + }, []); const optionOnClick = React.useCallback((event) => { if (typeof onSelect === 'function') { onSelect(event); @@ -29,72 +44,66 @@ const Dropdown = ({ className, menuClassName, menuMatchLabelWidth, renderLabel, onClose(); } } - }, [menuOpen, onOpen, onClose]); + }, [menuOpen]); return ( ( - )} renderMenu={() => ( -
- { - Array.isArray(options) && options.length > 0 ? - options.map(({ label, value }) => ( - - )) - : - null - } +
+ {options.map(({ label, value }) => ( + + ))}
)} /> ); }; -Dropdown.propTypes = { +Multiselect.propTypes = { className: PropTypes.string, - menuClassName: PropTypes.string, - menuMatchLabelWidth: PropTypes.bool, - renderLabel: PropTypes.func, - name: PropTypes.string, - selected: PropTypes.arrayOf(PropTypes.string), + direction: PropTypes.any, + title: PropTypes.string, + renderLabelContent: PropTypes.func, options: PropTypes.arrayOf(PropTypes.shape({ - label: PropTypes.string.isRequired, - value: PropTypes.string.isRequired + value: PropTypes.string.isRequired, + label: PropTypes.string })), - tabIndex: PropTypes.number, + selected: PropTypes.arrayOf(PropTypes.string), onOpen: PropTypes.func, onClose: PropTypes.func, onSelect: PropTypes.func }; -module.exports = Dropdown; +module.exports = Multiselect; diff --git a/src/common/Multiselect/index.js b/src/common/Multiselect/index.js new file mode 100644 index 000000000..0df1069f6 --- /dev/null +++ b/src/common/Multiselect/index.js @@ -0,0 +1,3 @@ +const Multiselect = require('./Multiselect'); + +module.exports = Multiselect; diff --git a/src/common/Multiselect/styles.less b/src/common/Multiselect/styles.less new file mode 100644 index 000000000..cc946e659 --- /dev/null +++ b/src/common/Multiselect/styles.less @@ -0,0 +1,78 @@ +:import('~stremio/common/Popup/styles.less') { + popup-menu-container: menu-container; +} + +.label-container { + display: flex; + flex-direction: row; + align-items: center; + padding: 0 1rem; + background-color: var(--color-backgroundlighter); + + &:global(.active) { + background-color: var(--color-surfacelight); + + .label { + color: var(--color-backgrounddarker); + } + + .icon { + fill: var(--color-backgrounddarker); + } + } + + .label { + flex: 1; + max-height: 2.4em; + margin-right: 1rem; + color: var(--color-surfacelighter); + } + + .icon { + flex: none; + width: 1rem; + height: 1rem; + fill: var(--color-surfacelighter); + } + + .popup-menu-container { + width: 100%; + + .menu-container { + .option-container { + display: flex; + flex-direction: row; + align-items: center; + padding: 1rem; + background-color: var(--color-backgroundlighter); + + &:global(.selected) { + background-color: var(--color-surfacedarker); + + .icon { + display: block; + } + } + + &:hover, &:focus { + background-color: var(--color-surfacedark); + } + + .label { + flex: 1; + max-height: 4.8em; + margin-right: 1rem; + color: var(--color-surfacelighter); + } + + .icon { + flex: none; + display: none; + width: 1rem; + height: 1rem; + fill: var(--color-surfacelighter); + } + } + } + } +} \ No newline at end of file diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 0b041e541..32de2e057 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -1,147 +1,78 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const { Modal } = require('stremio-router'); +const FocusLock = require('react-focus-lock').default; const styles = require('./styles'); -// TODO rename to Popover -const Popup = ({ open, menuModalClassName, menuRelativePosition, menuMatchLabelWidth, renderLabel, renderMenu, onCloseRequest }) => { +const Popup = ({ open, direction, renderLabel, renderMenu, onCloseRequest }) => { const labelRef = React.useRef(null); - const menuRef = React.useRef(null); - const [menuStyles, setMenuStyles] = React.useState({}); - React.useEffect(() => { - const checkCloseEvent = (event) => { - switch (event.type) { - case 'resize': - onCloseRequest(event); - break; - case 'keydown': - if (event.key === 'Escape') { - onCloseRequest(event); - } - break; - case 'mousedown': - if (event.target !== document && - event.target !== document.documentElement && - !event.closePopupPrevented) { - onCloseRequest(event); - } - break; - case 'react-scroll': - if (!event.nativeEvent.closePopupPrevented) { - onCloseRequest(event.nativeEvent); - } - break; - } - }; - if (open) { - window.addEventListener('react-scroll', checkCloseEvent); - window.addEventListener('mousedown', checkCloseEvent); - window.addEventListener('keydown', checkCloseEvent); - window.addEventListener('resize', checkCloseEvent); - } - return () => { - window.removeEventListener('react-scroll', checkCloseEvent); - window.removeEventListener('mousedown', checkCloseEvent); - window.removeEventListener('keydown', checkCloseEvent); - window.removeEventListener('resize', checkCloseEvent); - }; - }, [open, onCloseRequest]); + const [autoDirection, setAutoDirection] = React.useState(null); const menuOnMouseDown = React.useCallback((event) => { event.nativeEvent.closePopupPrevented = true; }, []); - const menuOnScroll = React.useCallback((event) => { - event.nativeEvent.closePopupPrevented = true; - }, []); React.useEffect(() => { - let menuStyles = {}; + const checkCloseEvent = (event) => { + if (typeof onCloseRequest === 'function') { + switch (event.type) { + case 'resize': + onCloseRequest(event); + break; + case 'keydown': + if (event.key === 'Escape') { + onCloseRequest(event); + } + break; + case 'mousedown': + if (event.target !== document.documentElement && + !labelRef.current.contains(event.target) && + !event.closePopupPrevented) { + onCloseRequest(event); + } + break; + } + } + }; if (open) { - if (menuRelativePosition !== false) { - const documentRect = document.documentElement.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) - }; - const matchLabelWidthMenuStyles = { - width: `${labelRect.width}px`, - maxWidth: `${labelRect.width}px` - }; - const bottomMenuStyles = { - top: `${labelPosition.top + labelRect.height}px`, - maxHeight: `${labelPosition.bottom}px` - }; - const topMenuStyles = { - bottom: `${labelPosition.bottom + labelRect.height}px`, - maxHeight: `${labelPosition.top}px` - }; - const rightMenuStyles = { - left: `${labelPosition.left}px`, - maxWidth: `${labelPosition.right + labelRect.width}px` - }; - const leftMenuStyles = { - right: `${labelPosition.right}px`, - maxWidth: `${labelPosition.left + labelRect.width}px` - }; - - if (menuRect.height <= labelPosition.bottom) { - menuStyles = { ...menuStyles, ...bottomMenuStyles }; - } else if (menuRect.height <= labelPosition.top) { - menuStyles = { ...menuStyles, ...topMenuStyles }; - } else if (labelPosition.bottom >= labelPosition.top) { - menuStyles = { ...menuStyles, ...bottomMenuStyles }; - } else { - menuStyles = { ...menuStyles, ...topMenuStyles }; - } - - if (menuRect.width <= (labelPosition.right + labelRect.width)) { - menuStyles = { ...menuStyles, ...rightMenuStyles }; - } else if (menuRect.width <= (labelPosition.left + labelRect.width)) { - menuStyles = { ...menuStyles, ...leftMenuStyles }; - } else if (labelPosition.right > labelPosition.left) { - menuStyles = { ...menuStyles, ...rightMenuStyles }; - } else { - menuStyles = { ...menuStyles, ...leftMenuStyles }; - } - - if (menuMatchLabelWidth) { - menuStyles = { ...menuStyles, ...matchLabelWidthMenuStyles }; - } - } - - menuStyles = { ...menuStyles, visibility: 'visible' }; + window.addEventListener('resize', checkCloseEvent); + window.addEventListener('keydown', checkCloseEvent); + window.addEventListener('mousedown', checkCloseEvent); + } + return () => { + window.removeEventListener('resize', checkCloseEvent); + window.removeEventListener('keydown', checkCloseEvent); + window.removeEventListener('mousedown', checkCloseEvent); + }; + }, [open, onCloseRequest]); + React.useLayoutEffect(() => { + if (open) { + const documentRect = document.documentElement.getBoundingClientRect(); + const labelRect = labelRef.current.getBoundingClientRect(); + const labelOffsetTop = labelRect.top - documentRect.top; + const labelOffsetBottom = (documentRect.height + documentRect.top) - (labelRect.top + labelRect.height); + const autoDirection = labelOffsetBottom >= labelOffsetTop ? 'bottom' : 'top'; + setAutoDirection(autoDirection); + } else { + setAutoDirection(null); } - - setMenuStyles(menuStyles); }, [open]); - return ( - - {renderLabel(labelRef)} - { - open ? - -
- {renderMenu()} -
-
- : - null - } -
- ); + return renderLabel({ + ref: labelRef, + className: styles['label-container'], + children: open ? + + {renderMenu()} + + : + null + }); } Popup.propTypes = { open: PropTypes.bool, - menuModalClassName: PropTypes.string, - menuRelativePosition: PropTypes.bool, - menuMatchLabelWidth: PropTypes.bool, + direction: PropTypes.oneOf(['top', 'bottom']), renderLabel: PropTypes.func.isRequired, renderMenu: PropTypes.func.isRequired, - onCloseRequest: PropTypes.func.isRequired + onCloseRequest: PropTypes.func }; module.exports = Popup; diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less index 478c81e5c..dfb675caa 100644 --- a/src/common/Popup/styles.less +++ b/src/common/Popup/styles.less @@ -1,12 +1,24 @@ -.menu-modal-container { - pointer-events: none; +.label-container { + position: relative; + overflow: visible; .menu-container { position: absolute; - pointer-events: auto; + right: 0; + z-index: 1; + overflow: visible; visibility: hidden; - overflow: auto; box-shadow: 0 1.35rem 2.7rem var(--color-backgrounddarker40), 0 1.1rem 0.85rem var(--color-backgrounddarker20); + + &.menu-direction-bottom { + top: 100%; + visibility: visible; + } + + &.menu-direction-top { + bottom: 100%; + visibility: visible; + } } } \ No newline at end of file diff --git a/src/common/index.js b/src/common/index.js index 6fd87f1e9..66b43c638 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,7 +1,7 @@ const Button = require('./Button'); const Checkbox = require('./Checkbox'); const ColorInput = require('./ColorInput'); -const Dropdown = require('./Dropdown'); +const Multiselect = require('./Multiselect'); const Image = require('./Image'); const MainNavBar = require('./MainNavBar'); const MetaItem = require('./MetaItem'); @@ -29,7 +29,7 @@ module.exports = { Button, Checkbox, ColorInput, - Dropdown, + Multiselect, Image, MainNavBar, MetaItem, From 091d9273e5ba107935d38a2312d83848f365224a Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 6 Oct 2019 11:51:01 +0300 Subject: [PATCH 60/96] update broken flag synchronously on src change --- src/common/Image/Image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Image/Image.js b/src/common/Image/Image.js index 1d868fb23..da0120d13 100644 --- a/src/common/Image/Image.js +++ b/src/common/Image/Image.js @@ -9,7 +9,7 @@ const Image = ({ className, src, alt, fallbackSrc, renderFallback }) => { setBroken(true); } }, [src]); - React.useEffect(() => { + React.useLayoutEffect(() => { setBroken(false); }, [src]); return ( From d00b20568db9b1397938bad434ed13139684d299 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 6 Oct 2019 13:50:46 +0300 Subject: [PATCH 61/96] PlayIconCircleCentered moved in MetaItem --- src/common/MetaItem/MetaItem.js | 2 +- .../PlayIconCircleCentered/PlayIconCircleCentered.js | 0 src/common/{ => MetaItem}/PlayIconCircleCentered/index.js | 0 src/common/{ => MetaItem}/PlayIconCircleCentered/styles.less | 0 src/common/index.js | 2 -- 5 files changed, 1 insertion(+), 3 deletions(-) rename src/common/{ => MetaItem}/PlayIconCircleCentered/PlayIconCircleCentered.js (100%) rename src/common/{ => MetaItem}/PlayIconCircleCentered/index.js (100%) rename src/common/{ => MetaItem}/PlayIconCircleCentered/styles.less (100%) diff --git a/src/common/MetaItem/MetaItem.js b/src/common/MetaItem/MetaItem.js index ffb5545e6..349f204fe 100644 --- a/src/common/MetaItem/MetaItem.js +++ b/src/common/MetaItem/MetaItem.js @@ -4,8 +4,8 @@ const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); const Image = require('stremio/common/Image'); -const PlayIconCircleCentered = require('stremio/common/PlayIconCircleCentered'); const Dropdown = require('stremio/common/Dropdown'); +const PlayIconCircleCentered = require('./PlayIconCircleCentered'); const styles = require('./styles'); const ICON_FOR_TYPE = Object.assign(Object.create(null), { diff --git a/src/common/PlayIconCircleCentered/PlayIconCircleCentered.js b/src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js similarity index 100% rename from src/common/PlayIconCircleCentered/PlayIconCircleCentered.js rename to src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js diff --git a/src/common/PlayIconCircleCentered/index.js b/src/common/MetaItem/PlayIconCircleCentered/index.js similarity index 100% rename from src/common/PlayIconCircleCentered/index.js rename to src/common/MetaItem/PlayIconCircleCentered/index.js diff --git a/src/common/PlayIconCircleCentered/styles.less b/src/common/MetaItem/PlayIconCircleCentered/styles.less similarity index 100% rename from src/common/PlayIconCircleCentered/styles.less rename to src/common/MetaItem/PlayIconCircleCentered/styles.less diff --git a/src/common/index.js b/src/common/index.js index 66b43c638..70c5db61e 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -10,7 +10,6 @@ const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder'); const MetaRow = require('./MetaRow'); const MetaRowPlaceholder = require('./MetaRowPlaceholder'); const NavBar = require('./NavBar'); -const PlayIconCircleCentered = require('./PlayIconCircleCentered'); const Popup = require('./Popup'); const ScrollEventEmitter = require('./ScrollEventEmitter'); const ShareModal = require('./ShareModal'); @@ -38,7 +37,6 @@ module.exports = { MetaRow, MetaRowPlaceholder, NavBar, - PlayIconCircleCentered, Popup, ScrollEventEmitter, ShareModal, From fbddd392d990d498d241f68e2aa876d1f75e0e68 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Sun, 6 Oct 2019 13:53:06 +0300 Subject: [PATCH 62/96] MetaItem moved inside MetaRow --- src/common/{ => MetaRow}/MetaItem/MetaItem.js | 0 .../MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js | 0 .../{ => MetaRow}/MetaItem/PlayIconCircleCentered/index.js | 0 .../{ => MetaRow}/MetaItem/PlayIconCircleCentered/styles.less | 0 src/common/{ => MetaRow}/MetaItem/index.js | 0 src/common/{ => MetaRow}/MetaItem/styles.less | 0 src/common/MetaRow/MetaRow.js | 2 +- src/common/index.js | 2 -- 8 files changed, 1 insertion(+), 3 deletions(-) rename src/common/{ => MetaRow}/MetaItem/MetaItem.js (100%) rename src/common/{ => MetaRow}/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js (100%) rename src/common/{ => MetaRow}/MetaItem/PlayIconCircleCentered/index.js (100%) rename src/common/{ => MetaRow}/MetaItem/PlayIconCircleCentered/styles.less (100%) rename src/common/{ => MetaRow}/MetaItem/index.js (100%) rename src/common/{ => MetaRow}/MetaItem/styles.less (100%) diff --git a/src/common/MetaItem/MetaItem.js b/src/common/MetaRow/MetaItem/MetaItem.js similarity index 100% rename from src/common/MetaItem/MetaItem.js rename to src/common/MetaRow/MetaItem/MetaItem.js diff --git a/src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js b/src/common/MetaRow/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js similarity index 100% rename from src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js rename to src/common/MetaRow/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js diff --git a/src/common/MetaItem/PlayIconCircleCentered/index.js b/src/common/MetaRow/MetaItem/PlayIconCircleCentered/index.js similarity index 100% rename from src/common/MetaItem/PlayIconCircleCentered/index.js rename to src/common/MetaRow/MetaItem/PlayIconCircleCentered/index.js diff --git a/src/common/MetaItem/PlayIconCircleCentered/styles.less b/src/common/MetaRow/MetaItem/PlayIconCircleCentered/styles.less similarity index 100% rename from src/common/MetaItem/PlayIconCircleCentered/styles.less rename to src/common/MetaRow/MetaItem/PlayIconCircleCentered/styles.less diff --git a/src/common/MetaItem/index.js b/src/common/MetaRow/MetaItem/index.js similarity index 100% rename from src/common/MetaItem/index.js rename to src/common/MetaRow/MetaItem/index.js diff --git a/src/common/MetaItem/styles.less b/src/common/MetaRow/MetaItem/styles.less similarity index 100% rename from src/common/MetaItem/styles.less rename to src/common/MetaRow/MetaItem/styles.less diff --git a/src/common/MetaRow/MetaRow.js b/src/common/MetaRow/MetaRow.js index 8ae3c4991..3e046e4bf 100644 --- a/src/common/MetaRow/MetaRow.js +++ b/src/common/MetaRow/MetaRow.js @@ -3,7 +3,7 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); -const MetaItem = require('stremio/common/MetaItem'); +const MetaItem = require('./MetaItem'); const styles = require('./styles'); const MetaRow = ({ className, title, message, items, itemMenuOptions }) => { diff --git a/src/common/index.js b/src/common/index.js index 70c5db61e..3049744fb 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -4,7 +4,6 @@ const ColorInput = require('./ColorInput'); const Multiselect = require('./Multiselect'); const Image = require('./Image'); const MainNavBar = require('./MainNavBar'); -const MetaItem = require('./MetaItem'); const MetaPreview = require('./MetaPreview'); const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder'); const MetaRow = require('./MetaRow'); @@ -31,7 +30,6 @@ module.exports = { Multiselect, Image, MainNavBar, - MetaItem, MetaPreview, MetaPreviewPlaceholder, MetaRow, From 5e9c1b8ff18c8f8ce4ba009aeb649eb1eaaf9fa0 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 10 Oct 2019 18:15:35 +0300 Subject: [PATCH 63/96] ColorInput works with custom syntetic events --- src/common/ColorInput/ColorInput.js | 27 +++++++++++-------- .../ColorInput/ColorPicker/ColorPicker.js | 17 ++++++++---- src/common/ColorInput/ColorPicker/styles.less | 2 +- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index 026926276..a2b294bb1 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -13,6 +13,20 @@ const ColorInput = ({ value, onChange, ...props }) => { React.useEffect(() => { setTempValue(value); }, [value, modalOpen]); + const colorPickerOnInput = React.useCallback((event) => { + setTempValue(event.value); + }, []); + const submitButtonOnClick = React.useCallback((event) => { + if (typeof onChange === 'function') { + onChange({ + type: 'change', + value: tempValue, + nativeEvent: event.nativeEvent + }); + } + + closeModal(); + }, [onChange, tempValue]); const modalContainerOnMouseDown = React.useCallback((event) => { if (!event.nativeEvent.closeModalPrevented) { closeModal(); @@ -21,15 +35,6 @@ const ColorInput = ({ value, onChange, ...props }) => { const modalContentOnMouseDown = React.useCallback((event) => { event.nativeEvent.closeModalPrevented = true; }, []); - const submitButtonOnClick = React.useCallback((event) => { - if (typeof onChange === 'function') { - onChange(event); - } - - if (!event.nativeEvent.closeModalPrevented) { - closeModal(); - } - }, [onChange]); return (
- -
diff --git a/src/common/ColorInput/ColorPicker/ColorPicker.js b/src/common/ColorInput/ColorPicker/ColorPicker.js index 223cefbb1..d27a1142f 100644 --- a/src/common/ColorInput/ColorPicker/ColorPicker.js +++ b/src/common/ColorInput/ColorPicker/ColorPicker.js @@ -7,7 +7,7 @@ const styles = require('./styles'); const COLOR_FORMAT = 'hexcss4'; // TODO implement custom picker which is keyboard accessible -const ColorPicker = ({ className, value, onChange }) => { +const ColorPicker = ({ className, value, onInput }) => { value = AColorPicker.parseColor(value, COLOR_FORMAT); const pickerRef = React.useRef(null); const pickerElementRef = React.useRef(null); @@ -19,17 +19,24 @@ const ColorPicker = ({ className, value, onChange }) => { showRGB: false, showAlpha: true }); + const clipboardPicker = pickerElementRef.current.querySelector('.a-color-picker-clipbaord'); + if (clipboardPicker instanceof HTMLElement) { + clipboardPicker.tabIndex = -1; + } }, []); React.useEffect(() => { pickerRef.current.on('change', (picker, color) => { - if (typeof onChange === 'function') { - onChange(AColorPicker.parseColor(color, COLOR_FORMAT)); + if (typeof onInput === 'function') { + onInput({ + type: 'input', + value: AColorPicker.parseColor(color, COLOR_FORMAT) + }); } }); return () => { pickerRef.current.off('change'); }; - }, [onChange]); + }, [onInput]); React.useEffect(() => { if (AColorPicker.parseColor(pickerRef.current.color, COLOR_FORMAT) !== value) { pickerRef.current.color = value; @@ -43,7 +50,7 @@ const ColorPicker = ({ className, value, onChange }) => { ColorPicker.propTypes = { className: PropTypes.string, value: PropTypes.string, - onChange: PropTypes.func + onInput: PropTypes.func }; module.exports = ColorPicker; diff --git a/src/common/ColorInput/ColorPicker/styles.less b/src/common/ColorInput/ColorPicker/styles.less index 6cd52668a..174895a99 100644 --- a/src/common/ColorInput/ColorPicker/styles.less +++ b/src/common/ColorInput/ColorPicker/styles.less @@ -6,7 +6,7 @@ } :global(.a-color-picker-stack):not(:global(.a-color-picker-row-top)) canvas, :global(.a-color-picker-circle) { - border: solid 1px var(--color-surfacedark); + border: solid thin var(--color-surfacedark); } :global(.a-color-picker-circle) { From 73ffe015499a024b67407700eda9611a6b532e06 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 10 Oct 2019 18:16:53 +0300 Subject: [PATCH 64/96] Image container removed --- src/common/Image/Image.js | 30 +++++++++++------------------- src/common/Image/styles.less | 7 ------- 2 files changed, 11 insertions(+), 26 deletions(-) delete mode 100644 src/common/Image/styles.less diff --git a/src/common/Image/Image.js b/src/common/Image/Image.js index da0120d13..98f4955e8 100644 --- a/src/common/Image/Image.js +++ b/src/common/Image/Image.js @@ -1,36 +1,28 @@ const React = require('react'); const PropTypes = require('prop-types'); -const styles = require('./styles'); const Image = ({ className, src, alt, fallbackSrc, renderFallback }) => { const [broken, setBroken] = React.useState(false); - const onError = React.useCallback((event) => { - if (typeof src !== 'string' || event.currentTarget.src === src) { - setBroken(true); - } - }, [src]); + const onError = React.useCallback(() => { + setBroken(true); + }, []); React.useLayoutEffect(() => { setBroken(false); }, [src]); - return ( -
- { - (broken || typeof src !== 'string' || src.length === 0) && (typeof renderFallback === 'function' || typeof fallbackSrc === 'string') ? - typeof renderFallback === 'function' ? - renderFallback() - : - {alt} - : - {alt} - } -
- ); + return (broken || typeof src !== 'string' || src.length === 0) && (typeof renderFallback === 'function' || typeof fallbackSrc === 'string') ? + typeof renderFallback === 'function' ? + renderFallback() + : + {alt} + : + {alt}; }; Image.propTypes = { className: PropTypes.string, src: PropTypes.string, alt: PropTypes.string, + fallbackSrc: PropTypes.string, renderFallback: PropTypes.func }; diff --git a/src/common/Image/styles.less b/src/common/Image/styles.less deleted file mode 100644 index 463f98e1b..000000000 --- a/src/common/Image/styles.less +++ /dev/null @@ -1,7 +0,0 @@ -.image, .fallback-image { - display: block; - width: 100%; - height: 100%; - object-fit: contain; - object-position: center; -} \ No newline at end of file From ed534b22b8d4c87eecae4877bc5ea93d55f33bf1 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Thu, 10 Oct 2019 18:47:24 +0300 Subject: [PATCH 65/96] MetaRowPlaceholder moved to MetaRow --- .../MetaRowPlaceholder/MetaRowPlaceholder.js | 30 +++++++++++ .../{ => MetaRow}/MetaRowPlaceholder/index.js | 0 .../MetaRowPlaceholder/styles.less | 21 ++++++-- .../MetaRowPlaceholder/MetaRowPlaceholder.js | 52 ------------------- src/common/index.js | 6 +-- 5 files changed, 48 insertions(+), 61 deletions(-) create mode 100644 src/common/MetaRow/MetaRowPlaceholder/MetaRowPlaceholder.js rename src/common/{ => MetaRow}/MetaRowPlaceholder/index.js (100%) rename src/common/{ => MetaRow}/MetaRowPlaceholder/styles.less (57%) delete mode 100644 src/common/MetaRowPlaceholder/MetaRowPlaceholder.js diff --git a/src/common/MetaRow/MetaRowPlaceholder/MetaRowPlaceholder.js b/src/common/MetaRow/MetaRowPlaceholder/MetaRowPlaceholder.js new file mode 100644 index 000000000..a6d3bda30 --- /dev/null +++ b/src/common/MetaRow/MetaRowPlaceholder/MetaRowPlaceholder.js @@ -0,0 +1,30 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const styles = require('./styles'); + +const MetaRowPlaceholder = ({ className, title, maximumItemsCount }) => { + maximumItemsCount = maximumItemsCount !== null && isFinite(maximumItemsCount) ? maximumItemsCount : 20; + return ( +
+
{title}
+
+ {Array(maximumItemsCount).fill(null).map((_, index) => ( +
+
+
+
+ ))} +
+
+
+ ); +}; + +MetaRowPlaceholder.propTypes = { + className: PropTypes.string, + title: PropTypes.string, + maximumItemsCount: PropTypes.number +}; + +module.exports = MetaRowPlaceholder; diff --git a/src/common/MetaRowPlaceholder/index.js b/src/common/MetaRow/MetaRowPlaceholder/index.js similarity index 100% rename from src/common/MetaRowPlaceholder/index.js rename to src/common/MetaRow/MetaRowPlaceholder/index.js diff --git a/src/common/MetaRowPlaceholder/styles.less b/src/common/MetaRow/MetaRowPlaceholder/styles.less similarity index 57% rename from src/common/MetaRowPlaceholder/styles.less rename to src/common/MetaRow/MetaRowPlaceholder/styles.less index f060d73f6..8e563492c 100644 --- a/src/common/MetaRowPlaceholder/styles.less +++ b/src/common/MetaRow/MetaRowPlaceholder/styles.less @@ -7,13 +7,14 @@ .title-container { grid-area: title-area; - height: 2.4rem; + max-height: 2.4em; margin-bottom: 2rem; + font-size: 1.5rem; + color: var(--color-surface); - .title-label-container { - width: 60%; - height: 100%; - background-color: var(--color-placeholder); + &:empty { + height: 1.2em; + background: linear-gradient(to right, var(--color-placeholder) 0 40%, transparent 40% 100%); } } @@ -30,6 +31,16 @@ .poster-container { padding-bottom: calc(100% * var(--poster-shape-ratio)); } + + .title-bar-container { + height: 2.8rem; + background-color: var(--color-placeholder); + } } } + + .see-all-container { + grid-area: see-all-area; + background-color: var(--color-placeholder); + } } \ No newline at end of file diff --git a/src/common/MetaRowPlaceholder/MetaRowPlaceholder.js b/src/common/MetaRowPlaceholder/MetaRowPlaceholder.js deleted file mode 100644 index ca36a5298..000000000 --- a/src/common/MetaRowPlaceholder/MetaRowPlaceholder.js +++ /dev/null @@ -1,52 +0,0 @@ -const React = require('react'); -const PropTypes = require('prop-types'); -const classnames = require('classnames'); -const styles = require('./styles'); - -const MetaRowPlaceholder = ({ className }) => { - return ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -}; - -MetaRowPlaceholder.propTypes = { - className: PropTypes.string -}; - -module.exports = MetaRowPlaceholder; diff --git a/src/common/index.js b/src/common/index.js index 3049744fb..e594b6b64 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,13 +1,12 @@ const Button = require('./Button'); const Checkbox = require('./Checkbox'); const ColorInput = require('./ColorInput'); -const Multiselect = require('./Multiselect'); const Image = require('./Image'); const MainNavBar = require('./MainNavBar'); const MetaPreview = require('./MetaPreview'); const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder'); const MetaRow = require('./MetaRow'); -const MetaRowPlaceholder = require('./MetaRowPlaceholder'); +const Multiselect = require('./Multiselect'); const NavBar = require('./NavBar'); const Popup = require('./Popup'); const ScrollEventEmitter = require('./ScrollEventEmitter'); @@ -27,13 +26,12 @@ module.exports = { Button, Checkbox, ColorInput, - Multiselect, Image, MainNavBar, MetaPreview, MetaPreviewPlaceholder, MetaRow, - MetaRowPlaceholder, + Multiselect, NavBar, Popup, ScrollEventEmitter, From c5b2ee48cf1e1709a4bc4805963a1ead4374621a Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 11 Oct 2019 14:44:22 +0300 Subject: [PATCH 66/96] useDataset custom hook implemented --- src/common/index.js | 2 ++ src/common/useDataset.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/common/useDataset.js diff --git a/src/common/index.js b/src/common/index.js index e594b6b64..883d517dd 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -16,6 +16,7 @@ const TextInput = require('./TextInput'); const routesRegexp = require('./routesRegexp'); const useAnimationFrame = require('./useAnimationFrame'); const useBinaryState = require('./useBinaryState'); +const useDataset = require('./useDataset'); const useFullscreen = require('./useFullscreen'); const useLiveRef = require('./useLiveRef'); const useLocationHash = require('./useLocationHash'); @@ -41,6 +42,7 @@ module.exports = { routesRegexp, useAnimationFrame, useBinaryState, + useDataset, useFullscreen, useLiveRef, useLocationHash, diff --git a/src/common/useDataset.js b/src/common/useDataset.js new file mode 100644 index 000000000..29ef0f03a --- /dev/null +++ b/src/common/useDataset.js @@ -0,0 +1,15 @@ +const React = require('react'); + +const useDataset = (props) => { + props = typeof props === 'object' && props !== null ? props : {}; + const dataPropNames = Object.keys(props).filter(propsName => propsName.startsWith('data-')); + const dataset = React.useMemo(() => { + return dataPropNames.reduce((dataset, dataPropName) => { + dataset[dataPropName.slice(5)] = String(props[dataPropName]); + return dataset; + }, {}); + }, [dataPropNames.join('')]); + return dataset; +}; + +module.exports = useDataset; From 6f43075bdd34325da3f1c3dce80913e527e3ee65 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 11 Oct 2019 14:44:56 +0300 Subject: [PATCH 67/96] dataset integrated in ColorInput --- src/common/ColorInput/ColorInput.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index a2b294bb1..9b96442ca 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -4,10 +4,12 @@ const Icon = require('stremio-icons/dom'); const { Modal } = require('stremio-router'); const Button = require('stremio/common/Button'); const useBinaryState = require('stremio/common/useBinaryState'); +const useDataset = require('stremio/common/useDataset'); const ColorPicker = require('./ColorPicker'); const styles = require('./styles'); const ColorInput = ({ value, onChange, ...props }) => { + const dataset = useDataset(props); const [modalOpen, openModal, closeModal] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); React.useEffect(() => { @@ -21,12 +23,13 @@ const ColorInput = ({ value, onChange, ...props }) => { onChange({ type: 'change', value: tempValue, + dataset: dataset, nativeEvent: event.nativeEvent }); } closeModal(); - }, [onChange, tempValue]); + }, [onChange, tempValue, dataset]); const modalContainerOnMouseDown = React.useCallback((event) => { if (!event.nativeEvent.closeModalPrevented) { closeModal(); From 2aabefb8ff87323faeb2779766b7fde0b5c146d6 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 11 Oct 2019 14:49:04 +0300 Subject: [PATCH 68/96] color imput value trasformed to hexcss4 format --- src/common/ColorInput/ColorInput.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/ColorInput/ColorInput.js b/src/common/ColorInput/ColorInput.js index 9b96442ca..ff27c72dc 100644 --- a/src/common/ColorInput/ColorInput.js +++ b/src/common/ColorInput/ColorInput.js @@ -1,5 +1,6 @@ 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'); @@ -8,7 +9,10 @@ const useDataset = require('stremio/common/useDataset'); const ColorPicker = require('./ColorPicker'); const styles = require('./styles'); +const COLOR_FORMAT = 'hexcss4'; + const ColorInput = ({ value, onChange, ...props }) => { + value = AColorPicker.parseColor(value, COLOR_FORMAT); const dataset = useDataset(props); const [modalOpen, openModal, closeModal] = useBinaryState(false); const [tempValue, setTempValue] = React.useState(value); From c996d521de36aa5997a208ffb9975eefcd714531 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 11 Oct 2019 15:39:45 +0300 Subject: [PATCH 69/96] syntetic events dispatched from Popup --- src/common/Popup/Popup.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 32de2e057..5cf7c65d9 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -2,47 +2,53 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const FocusLock = require('react-focus-lock').default; +const useDataset = require('stremio/common/useDataset'); const styles = require('./styles'); -const Popup = ({ open, direction, renderLabel, renderMenu, onCloseRequest }) => { +const Popup = ({ open, direction, renderLabel, renderMenu, onCloseRequest, ...props }) => { + direction = ['top', 'bottom'].includes(direction) ? direction : null; + const dataset = useDataset(props); const labelRef = React.useRef(null); const [autoDirection, setAutoDirection] = React.useState(null); const menuOnMouseDown = React.useCallback((event) => { event.nativeEvent.closePopupPrevented = true; }, []); React.useEffect(() => { - const checkCloseEvent = (event) => { - if (typeof onCloseRequest === 'function') { + const onCloseEvent = (event) => { + if (!event.closePopupPrevented && typeof onCloseRequest === 'function') { + const closeEvent = { + type: 'close', + nativeEvent: event, + dataset: dataset + }; switch (event.type) { case 'resize': - onCloseRequest(event); + onCloseRequest(closeEvent); break; case 'keydown': if (event.key === 'Escape') { - onCloseRequest(event); + onCloseRequest(closeEvent); } break; case 'mousedown': - if (event.target !== document.documentElement && - !labelRef.current.contains(event.target) && - !event.closePopupPrevented) { - onCloseRequest(event); + if (event.target !== document.documentElement && !labelRef.current.contains(event.target)) { + onCloseRequest(closeEvent); } break; } } }; if (open) { - window.addEventListener('resize', checkCloseEvent); - window.addEventListener('keydown', checkCloseEvent); - window.addEventListener('mousedown', checkCloseEvent); + window.addEventListener('resize', onCloseEvent); + window.addEventListener('keydown', onCloseEvent); + window.addEventListener('mousedown', onCloseEvent); } return () => { - window.removeEventListener('resize', checkCloseEvent); - window.removeEventListener('keydown', checkCloseEvent); - window.removeEventListener('mousedown', checkCloseEvent); + window.removeEventListener('resize', onCloseEvent); + window.removeEventListener('keydown', onCloseEvent); + window.removeEventListener('mousedown', onCloseEvent); }; - }, [open, onCloseRequest]); + }, [open, onCloseRequest, dataset]); React.useLayoutEffect(() => { if (open) { const documentRect = document.documentElement.getBoundingClientRect(); From 6d76f309a2a1bfc79ebe1cfdabf4638575201df4 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Fri, 11 Oct 2019 15:59:44 +0300 Subject: [PATCH 70/96] dataset integrated in Multiselect component --- src/common/Multiselect/Multiselect.js | 24 ++++++++++++++++++------ src/common/Multiselect/styles.less | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/common/Multiselect/Multiselect.js b/src/common/Multiselect/Multiselect.js index a02e5476b..75333f072 100644 --- a/src/common/Multiselect/Multiselect.js +++ b/src/common/Multiselect/Multiselect.js @@ -5,6 +5,7 @@ const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); const Popup = require('stremio/common/Popup'); const useBinaryState = require('stremio/common/useBinaryState'); +const useDataset = require('stremio/common/useDataset'); const styles = require('./styles'); const Multiselect = ({ className, direction, title, renderLabelContent, options, selected, onOpen, onClose, onSelect, ...props }) => { @@ -16,6 +17,7 @@ const Multiselect = ({ className, direction, title, renderLabelContent, options, selected.filter(value => typeof value === 'string') : []; + const dataset = useDataset(props); const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false); const popupLabelOnClick = React.useCallback((event) => { if (!event.nativeEvent.togglePopupPrevented) { @@ -27,21 +29,31 @@ const Multiselect = ({ className, direction, title, renderLabelContent, options, }, []); const optionOnClick = React.useCallback((event) => { if (typeof onSelect === 'function') { - onSelect(event); + onSelect({ + type: 'select', + nativeEvent: event.nativeEvent, + dataset: dataset + }); } if (!event.nativeEvent.closeMenuPrevented) { closeMenu(); } - }, [onSelect]); - React.useEffect(() => { + }, [onSelect, dataset]); + React.useLayoutEffect(() => { if (menuOpen) { if (typeof onOpen === 'function') { - onOpen(); + onOpen({ + type: 'open', + dataset: dataset + }); } } else { if (typeof onClose === 'function') { - onClose(); + onClose({ + type: 'close', + dataset: dataset + }); } } }, [menuOpen]); @@ -51,7 +63,7 @@ const Multiselect = ({ className, direction, title, renderLabelContent, options, direction={direction} onCloseRequest={closeMenu} renderLabel={({ ref, className: popupLabelClassName, children }) => ( - + ); +}); + +MetaItem.displayName = 'MetaItem'; + +MetaItem.propTypes = { + className: PropTypes.string, + type: PropTypes.string, + name: PropTypes.string, + poster: PropTypes.string, + posterShape: PropTypes.oneOf(['poster', 'landscape', 'square']), + playIcon: PropTypes.bool, + progress: PropTypes.number, + menuOptions: PropTypes.array, + onSelect: PropTypes.func, + menuOptionOnSelect: PropTypes.func +}; + +module.exports = MetaItem; diff --git a/src/common/MetaRow/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js b/src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js similarity index 100% rename from src/common/MetaRow/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js rename to src/common/MetaItem/PlayIconCircleCentered/PlayIconCircleCentered.js diff --git a/src/common/MetaRow/MetaItem/PlayIconCircleCentered/index.js b/src/common/MetaItem/PlayIconCircleCentered/index.js similarity index 100% rename from src/common/MetaRow/MetaItem/PlayIconCircleCentered/index.js rename to src/common/MetaItem/PlayIconCircleCentered/index.js diff --git a/src/common/MetaRow/MetaItem/PlayIconCircleCentered/styles.less b/src/common/MetaItem/PlayIconCircleCentered/styles.less similarity index 100% rename from src/common/MetaRow/MetaItem/PlayIconCircleCentered/styles.less rename to src/common/MetaItem/PlayIconCircleCentered/styles.less diff --git a/src/common/MetaRow/MetaItem/index.js b/src/common/MetaItem/index.js similarity index 100% rename from src/common/MetaRow/MetaItem/index.js rename to src/common/MetaItem/index.js diff --git a/src/common/MetaRow/MetaItem/styles.less b/src/common/MetaItem/styles.less similarity index 61% rename from src/common/MetaRow/MetaItem/styles.less rename to src/common/MetaItem/styles.less index 9c9e2843a..5f8ec6cd0 100644 --- a/src/common/MetaRow/MetaItem/styles.less +++ b/src/common/MetaItem/styles.less @@ -1,34 +1,35 @@ -:import('~stremio/common/Image/styles.less') { - poster-image: image; +:import('~stremio/common/Popup/styles.less') { + popup-menu-container: menu-container; } -:import('~stremio/common/PlayIconCircleCentered/styles.less') { +:import('~stremio/common/Multiselect/styles.less') { + multiselect-menu-container: menu-container; + multiselect-option-container: option-container; + multiselect-option-label: label; +} + +:import('./PlayIconCircleCentered/styles.less') { play-icon-circle-centered-background: background; play-icon-circle-centered-icon: icon; } -:import('~stremio/common/Dropdown/styles.less') { - dropdown-option-container: dropdown-option-container; - dropdown-option-label: label; -} - .meta-item-container { - position: relative; - z-index: 0; - background-color: var(--color-backgroundlight); + overflow: visible; &:hover, &:focus, &:global(.active) { outline-style: solid; - background-color: var(--color-surfacelight); + outline-offset: 0; .title-bar-container { + background-color: var(--color-surfacelight); + .title-label { color: var(--color-backgrounddarker); } - .dropdown-menu-container { - .menu-label-container { - .menu-icon { + .multiselect-container { + .multiselect-label-container { + .icon { fill: var(--color-backgrounddarker); } } @@ -56,7 +57,7 @@ .poster-container { position: relative; - z-index: -1; + z-index: 0; background-color: var(--color-backgroundlight); .poster-image-layer { @@ -65,26 +66,25 @@ right: 0; bottom: 0; left: 0; - z-index: 1; + z-index: -3; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; - .poster-image-container { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; + .poster-image { + flex: none; width: 100%; height: 100%; + object-position: center; + object-fit: cover; + } - .poster-image { - object-fit: cover; - } - - .placeholder-icon { - flex: none; - height: 50%; - width: 80%; - fill: var(--color-surfacelight20); - } + .placeholder-icon { + flex: none; + width: 80%; + height: 50%; + fill: var(--color-surfacelight20); } } @@ -94,7 +94,7 @@ right: 0; bottom: 30%; left: 0; - z-index: 2; + z-index: -2; overflow: visible; .play-icon { @@ -118,7 +118,7 @@ right: 0; bottom: 0; left: 0; - z-index: 3; + z-index: -1; background-color: var(--color-backgroundlighter); .progress-bar { @@ -134,6 +134,8 @@ align-items: center; justify-content: flex-end; height: 2.8rem; + background-color: var(--color-backgroundlight); + overflow: visible; .title-label { flex: 1; @@ -146,52 +148,54 @@ } } - .dropdown-menu-container { + .multiselect-container { flex: none; width: 2.8rem; height: 2.8rem; + overflow: visible; - .menu-label-container { + .multiselect-label-container { width: 100%; height: 100%; padding: 0.75rem; background-color: transparent; &:hover, &:global(.active) { - filter: none; background-color: var(--color-surface80); - - .menu-icon { - fill: var(--color-backgrounddarker); - } } - .menu-icon { + .icon { display: block; width: 100%; height: 100%; fill: var(--color-surfacelighter); } + + .popup-menu-container { + width: auto; + right: initial; + left: 0; + + .multiselect-menu-container { + min-width: 8rem; + max-width: 12rem; + + .multiselect-option-container { + padding: 0.5rem; + background-color: var(--color-surfacelighter); + + &:hover, &:focus { + outline: none; + background-color: var(--color-surfacelight); + } + + .multiselect-option-label { + color: var(--color-backgrounddarker); + } + } + } + } } } } -} - -.menu-container { - min-width: 8rem; - max-width: 12rem; - - .dropdown-option-container { - padding: 0.5rem; - background-color: var(--color-surfacelighter); - - &:hover, &:focus { - outline: none; - background-color: var(--color-surfacelight); - } - - .dropdown-option-label { - color: var(--color-backgrounddarker); - } - } } \ No newline at end of file diff --git a/src/common/MetaRow/MetaItem/MetaItem.js b/src/common/MetaRow/MetaItem/MetaItem.js deleted file mode 100644 index 349f204fe..000000000 --- a/src/common/MetaRow/MetaItem/MetaItem.js +++ /dev/null @@ -1,137 +0,0 @@ -const React = require('react'); -const PropTypes = require('prop-types'); -const classnames = require('classnames'); -const Icon = require('stremio-icons/dom'); -const Button = require('stremio/common/Button'); -const Image = require('stremio/common/Image'); -const Dropdown = require('stremio/common/Dropdown'); -const PlayIconCircleCentered = require('./PlayIconCircleCentered'); -const styles = require('./styles'); - -const ICON_FOR_TYPE = Object.assign(Object.create(null), { - 'movie': 'ic_movies', - 'series': 'ic_series', - 'channel': 'ic_channels', - 'tv': 'ic_tv', - 'other': 'ic_movies' -}); - -const MetaItem = React.memo(({ className, id, type, name, posterShape, poster, title, subtitle, progress, playIcon, menuOptions, onSelect, menuOptionOnSelect }) => { - const [menuOpen, setMenuOpen] = React.useState(false); - const onOpen = React.useCallback(() => { - setMenuOpen(true); - }, []); - const onClose = React.useCallback(() => { - setMenuOpen(false); - }, []); - const metaItemOnClick = React.useCallback((event) => { - if (!event.nativeEvent.selectMetaItemPrevented && typeof onSelect === 'function') { - onSelect(event); - } - }, [onSelect]); - const menuOnClick = React.useCallback((event) => { - event.nativeEvent.selectMetaItemPrevented = true; - }, []); - return ( - - ); -}); - -MetaItem.displayName = 'MetaItem'; - -MetaItem.propTypes = { - className: PropTypes.string, - id: PropTypes.string, - type: PropTypes.string, - name: PropTypes.string, - posterShape: PropTypes.oneOf(['poster', 'landscape', 'square']), - poster: PropTypes.string, - title: PropTypes.string, - subtitle: PropTypes.string, - progress: PropTypes.number, - playIcon: PropTypes.bool, - menuOptions: PropTypes.arrayOf(PropTypes.shape({ - label: PropTypes.string.isRequired, - value: PropTypes.string.isRequired - })), - onSelect: PropTypes.func, - menuOptionOnSelect: PropTypes.func -}; - -module.exports = MetaItem; diff --git a/src/common/MetaRow/MetaRow.js b/src/common/MetaRow/MetaRow.js index 3e046e4bf..9658dff1f 100644 --- a/src/common/MetaRow/MetaRow.js +++ b/src/common/MetaRow/MetaRow.js @@ -3,70 +3,62 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const Button = require('stremio/common/Button'); -const MetaItem = require('./MetaItem'); +const MetaItem = require('stremio/common/MetaItem'); +const MetaRowPlaceholder = require('./MetaRowPlaceholder'); const styles = require('./styles'); -const MetaRow = ({ className, title, message, items, itemMenuOptions }) => { +const MetaRow = ({ className, title, message, items, maximumItemsCount, itemMenuOptions }) => { + maximumItemsCount = maximumItemsCount !== null && isFinite(maximumItemsCount) ? maximumItemsCount : 20; + items = Array.isArray(items) ? items.slice(0, maximumItemsCount) : []; return (
{ typeof title === 'string' && title.length > 0 ? -
{title}
+
{title}
: null } { typeof message === 'string' && message.length > 0 ? -
{message}
+
{message}
: - null - } - { - Array.isArray(items) && items.length > 0 ?
- {items.map((item) => ( + {items.map((item, index) => ( ))} -
-
-
-
-
-
-
-
-
-
+ {Array(Math.max(maximumItemsCount - items.length, 0)).fill(null).map((_, index) => ( +
+ ))}
- : - null }
); }; +MetaRow.Placeholder = MetaRowPlaceholder; + MetaRow.propTypes = { className: PropTypes.string, title: PropTypes.string, message: PropTypes.string, items: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, posterShape: PropTypes.string })), - itemMenuOptions: PropTypes.array + maximumItemsCount: PropTypes.number, + itemMenuOptions: PropTypes.any }; module.exports = MetaRow; diff --git a/src/common/MetaRow/styles.less b/src/common/MetaRow/styles.less index d5f2f954e..277b7176b 100644 --- a/src/common/MetaRow/styles.less +++ b/src/common/MetaRow/styles.less @@ -5,6 +5,7 @@ "title-area title-area" "message-area message-area" "meta-items-area see-all-area"; + overflow: visible; .title-container { grid-area: title-area; @@ -19,10 +20,6 @@ max-height: 3.6em; font-size: 1.3rem; color: var(--color-surfacelighter); - - &~.meta-items-container, &~.see-all-container { - display: none; - } } .meta-items-container { @@ -30,6 +27,7 @@ display: flex; flex-direction: row; align-items: stretch; + overflow: visible; .meta-item { margin-right: 2rem; @@ -59,21 +57,16 @@ &:hover, &:focus { background-color: var(--color-backgroundlighter); - - .see-all-label { - color: var(--color-surfacelighter); - } - - .icon { - fill: var(--color-surfacelighter); - } } - .see-all-label { + .label { + flex-grow: 0; + flex-shrink: 1; + flex-basis: auto; max-height: 2.4em; font-size: 1.2rem; text-align: center; - color: var(--color-surfacelight); + color: var(--color-surfacelighter); } .icon { @@ -81,7 +74,7 @@ width: 1.3rem; height: 1.3rem; margin-left: 0.3rem; - fill: var(--color-surfacelight); + fill: var(--color-surfacelighter); } } } \ No newline at end of file diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js index 9baa9207f..1b626f6ca 100644 --- a/src/routes/Board/Board.js +++ b/src/routes/Board/Board.js @@ -1,5 +1,5 @@ const React = require('react'); -const { MainNavBar, MetaRow, MetaRowPlaceholder } = require('stremio/common'); +const { MainNavBar, MetaRow } = require('stremio/common'); const useCatalogs = require('./useCatalogs'); const styles = require('./styles'); @@ -42,9 +42,10 @@ const Board = () => { ); case 'Loading': return ( - ); } diff --git a/src/routes/Board/styles.less b/src/routes/Board/styles.less index 7938c6f69..3e9d06859 100644 --- a/src/routes/Board/styles.less +++ b/src/routes/Board/styles.less @@ -4,7 +4,7 @@ meta-item: meta-item; } -:import('~stremio/common/MetaRowPlaceholder/styles.less') { +:import('~stremio/common/MetaRow/MetaRowPlaceholder/styles.less') { meta-item-placeholder: meta-item; } From b6f275c3c86726f42e9500ec3fbd6a9e95f3ba21 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 14 Oct 2019 15:15:03 +0300 Subject: [PATCH 74/96] FocusedRoute context renamed to RouteFocused --- src/router/FocusedRouteContext/FocusedRouteContext.js | 7 ------- src/router/FocusedRouteContext/index.js | 7 ------- src/router/FocusedRouteContext/useFocusedRoute.js | 8 -------- src/router/RouteFocusedContext/RouteFocusedContext.js | 7 +++++++ src/router/RouteFocusedContext/index.js | 7 +++++++ src/router/RouteFocusedContext/useRouteFocused.js | 8 ++++++++ src/router/Router/Router.js | 6 +++--- src/router/index.js | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 src/router/FocusedRouteContext/FocusedRouteContext.js delete mode 100644 src/router/FocusedRouteContext/index.js delete mode 100644 src/router/FocusedRouteContext/useFocusedRoute.js create mode 100644 src/router/RouteFocusedContext/RouteFocusedContext.js create mode 100644 src/router/RouteFocusedContext/index.js create mode 100644 src/router/RouteFocusedContext/useRouteFocused.js diff --git a/src/router/FocusedRouteContext/FocusedRouteContext.js b/src/router/FocusedRouteContext/FocusedRouteContext.js deleted file mode 100644 index d1323e8e9..000000000 --- a/src/router/FocusedRouteContext/FocusedRouteContext.js +++ /dev/null @@ -1,7 +0,0 @@ -const React = require('react'); - -const FocusedRouteContext = React.createContext(false); - -FocusedRouteContext.displayName = 'FocusedRouteContext'; - -module.exports = FocusedRouteContext; diff --git a/src/router/FocusedRouteContext/index.js b/src/router/FocusedRouteContext/index.js deleted file mode 100644 index 5163ca12c..000000000 --- a/src/router/FocusedRouteContext/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const FocusedRouteContext = require('./FocusedRouteContext'); -const useFocusedRoute = require('./useFocusedRoute'); - -module.exports = { - FocusedRouteProvider: FocusedRouteContext.Provider, - useFocusedRoute -}; diff --git a/src/router/FocusedRouteContext/useFocusedRoute.js b/src/router/FocusedRouteContext/useFocusedRoute.js deleted file mode 100644 index 3c893f027..000000000 --- a/src/router/FocusedRouteContext/useFocusedRoute.js +++ /dev/null @@ -1,8 +0,0 @@ -const React = require('react'); -const FocusedRouteContext = require('./FocusedRouteContext'); - -const useFocusedRoute = () => { - return React.useContext(FocusedRouteContext); -}; - -module.exports = useFocusedRoute; diff --git a/src/router/RouteFocusedContext/RouteFocusedContext.js b/src/router/RouteFocusedContext/RouteFocusedContext.js new file mode 100644 index 000000000..0d2eca21a --- /dev/null +++ b/src/router/RouteFocusedContext/RouteFocusedContext.js @@ -0,0 +1,7 @@ +const React = require('react'); + +const RouteFocusedContext = React.createContext(false); + +RouteFocusedContext.displayName = 'RouteFocusedContext'; + +module.exports = RouteFocusedContext; diff --git a/src/router/RouteFocusedContext/index.js b/src/router/RouteFocusedContext/index.js new file mode 100644 index 000000000..986b2204e --- /dev/null +++ b/src/router/RouteFocusedContext/index.js @@ -0,0 +1,7 @@ +const RouteFocusedContext = require('./RouteFocusedContext'); +const useRouteFocused = require('./useRouteFocused'); + +module.exports = { + RouteFocusedProvider: RouteFocusedContext.Provider, + useRouteFocused +}; diff --git a/src/router/RouteFocusedContext/useRouteFocused.js b/src/router/RouteFocusedContext/useRouteFocused.js new file mode 100644 index 000000000..79813c58d --- /dev/null +++ b/src/router/RouteFocusedContext/useRouteFocused.js @@ -0,0 +1,8 @@ +const React = require('react'); +const RouteFocusedContext = require('./RouteFocusedContext'); + +const useRouteFocused = () => { + return React.useContext(RouteFocusedContext); +}; + +module.exports = useRouteFocused; diff --git a/src/router/Router/Router.js b/src/router/Router/Router.js index e8617bfca..7fbaa26c3 100644 --- a/src/router/Router/Router.js +++ b/src/router/Router/Router.js @@ -3,7 +3,7 @@ const ReactIs = require('react-is'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const UrlUtils = require('url'); -const { FocusedRouteProvider } = require('../FocusedRouteContext'); +const { RouteFocusedProvider } = require('../RouteFocusedContext'); const Route = require('../Route'); const Router = ({ className, onPathNotMatch, ...props }) => { @@ -102,11 +102,11 @@ const Router = ({ className, onPathNotMatch, ...props }) => { views .filter(view => view !== null) .map(({ key, component, urlParams, queryParams }, index, views) => ( - + {React.createElement(component, { urlParams, queryParams })} - + )) }
diff --git a/src/router/index.js b/src/router/index.js index 71623af39..56eb8b422 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,9 +1,9 @@ -const { useFocusedRoute } = require('./FocusedRouteContext'); +const { useRouteFocused } = require('./RouteFocusedContext'); const Modal = require('./Modal'); const Router = require('./Router'); module.exports = { - useFocusedRoute, + useRouteFocused, Modal, Router }; From 7a4b2f514389b9e8310cd0dd33b51357a0fc8d81 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 14 Oct 2019 15:16:30 +0300 Subject: [PATCH 75/96] intro adapted to router changes --- src/routes/Intro/Intro.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index c3d338827..e638aa28a 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -1,7 +1,7 @@ const React = require('react'); 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 CredentialsTextInput = require('./CredentialsTextInput'); const ConsentCheckbox = require('./ConsentCheckbox'); @@ -11,7 +11,7 @@ const LOGIN_FORM = 'LOGIN_FORM'; const SIGNUP_FORM = 'SIGNUP_FORM'; const Intro = () => { - const focusable = useFocusable(); + const routeFocused = useRouteFocused(); const emailRef = React.useRef(); const passwordRef = React.useRef(); const confirmPasswordRef = React.useRef(); @@ -137,10 +137,10 @@ const Intro = () => { } }, [state.error]); React.useEffect(() => { - if (focusable) { + if (routeFocused) { emailRef.current.focus(); } - }, [state.form, focusable]); + }, [state.form, routeFocused]); return (
From 5796fd65e8f6d626a7ce9582c266f69fcd3ae5e7 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Mon, 14 Oct 2019 15:17:03 +0300 Subject: [PATCH 76/96] clear the error on credentials changed --- src/routes/Intro/Intro.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index e638aa28a..9f1ad474f 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -36,11 +36,13 @@ const Intro = () => { case 'change-credentials': return { ...state, + error: '', [action.name]: action.value }; case 'toggle-checkbox': return { ...state, + error: '', [action.name]: !state[action.name] }; case 'error': From 5d9e7ab41b7f0eb638b9dead4c253658d6893de3 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Tue, 15 Oct 2019 11:04:53 +0300 Subject: [PATCH 77/96] Popup content cursor reset --- src/common/Popup/styles.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less index dfb675caa..8c00b148d 100644 --- a/src/common/Popup/styles.less +++ b/src/common/Popup/styles.less @@ -10,6 +10,7 @@ visibility: hidden; box-shadow: 0 1.35rem 2.7rem var(--color-backgrounddarker40), 0 1.1rem 0.85rem var(--color-backgrounddarker20); + cursor: auto; &.menu-direction-bottom { top: 100%; From 284043eed3f22e497dfb6e14d2e9d9f8e2687100 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Tue, 15 Oct 2019 11:06:26 +0300 Subject: [PATCH 78/96] button prevent flags changed --- src/common/Button/Button.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/Button/Button.js b/src/common/Button/Button.js index 53e1bc01a..fa52ca1d3 100644 --- a/src/common/Button/Button.js +++ b/src/common/Button/Button.js @@ -8,7 +8,7 @@ const Button = React.forwardRef(({ children, ...props }, ref) => { props.onKeyUp(event); } - if (event.key === 'Enter' && !event.nativeEvent.clickPrevented) { + if (event.key === 'Enter' && !event.nativeEvent.buttonClickPrevented) { event.currentTarget.click(); } }, [props.onKeyUp]); @@ -17,7 +17,7 @@ const Button = React.forwardRef(({ children, ...props }, ref) => { props.onMouseDown(event); } - if (!event.nativeEvent.blurPrevented) { + if (!event.nativeEvent.buttonBlurPrevented) { event.preventDefault(); if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); From d7b7493a8572f56f0e30ebe047b5a0f5236c5113 Mon Sep 17 00:00:00 2001 From: NikolaBorislavovHristov Date: Tue, 15 Oct 2019 11:09:11 +0300 Subject: [PATCH 79/96] SearchBar uses new route focused hook --- src/common/NavBar/SearchBar/SearchBar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/NavBar/SearchBar/SearchBar.js b/src/common/NavBar/SearchBar/SearchBar.js index 9f364c0af..7f8961298 100644 --- a/src/common/NavBar/SearchBar/SearchBar.js +++ b/src/common/NavBar/SearchBar/SearchBar.js @@ -3,7 +3,7 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const UrlUtils = require('url'); 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 routesRegexp = require('stremio/common/routesRegexp'); @@ -13,7 +13,7 @@ const styles = require('./styles'); const SearchBar = ({ className }) => { const locationHash = useLocationHash(); - const focusable = useFocusable(); + const routeFocused = useRouteFocused(); const searchInputRef = React.useRef(null); const active = useRouteActive(routesRegexp.search.regexp); const query = React.useMemo(() => { @@ -36,10 +36,10 @@ const SearchBar = ({ className }) => { } }, [active]); React.useEffect(() => { - if (active && focusable) { + if (active && routeFocused) { searchInputRef.current.focus(); } - }, [active, focusable, query]); + }, [active, routeFocused, query]); return (