From e8a6e72b13297bdd77ea1ce3cd877d77fedfbeb1 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 17:06:26 +0200 Subject: [PATCH 01/40] feat(NumberInput): added NumberInput common component --- src/components/NumberInput/NumberInput.tsx | 84 ++++++++++++++++++++++ src/components/NumberInput/index.ts | 5 ++ src/components/NumberInput/styles.less | 75 +++++++++++++++++++ src/components/index.ts | 2 + 4 files changed, 166 insertions(+) create mode 100644 src/components/NumberInput/NumberInput.tsx create mode 100644 src/components/NumberInput/index.ts create mode 100644 src/components/NumberInput/styles.less diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx new file mode 100644 index 000000000..fe6b84806 --- /dev/null +++ b/src/components/NumberInput/NumberInput.tsx @@ -0,0 +1,84 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import React, { forwardRef, useCallback, useState } from 'react'; +import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; +import classnames from 'classnames'; +import styles from './styles.less'; +import Button from '../Button'; +import Icon from '@stremio/stremio-icons/react'; + +type Props = InputHTMLAttributes & { + containerClassName?: string; + className?: string; + disabled?: boolean; + showButtons?: boolean; + defaultValue?: number; + label?: string; + min?: number; + max?: number; + onKeyDown?: (event: KeyboardEvent) => void; + onSubmit?: (event: KeyboardEvent) => void; +}; + +const NumberInput = forwardRef(({ defaultValue, ...props }, ref) => { + const [value, setValue] = useState(defaultValue || 1); + const onKeyDown = useCallback((event: KeyboardEvent) => { + props.onKeyDown && props.onKeyDown(event); + + if (event.key === 'Enter' ) { + props.onSubmit && props.onSubmit(event); + } + }, [props.onKeyDown, props.onSubmit]); + + const handleIncrease = () => { + const { max } = props; + if (max) { + return setValue((prevVal) => + prevVal + 1 > max ? max : prevVal + 1 + ); + } + setValue((prevVal) => prevVal + 1); + }; + + const handleDecrease = () => { + const { min } = props; + if (min) { + return setValue((prevVal) => + prevVal - 1 < min ? min : prevVal - 1 + ); + } + setValue((prevVal) => prevVal - 1); + }; + + return ( +
+ {props.showButtons ? : null} +
+ {props.label &&
{props.label}
} + setValue(event.value)} + onKeyDown={onKeyDown} + /> +
+ {props.showButtons ? : null} +
+ ); +}); + +NumberInput.displayName = 'NumberInput'; + +export default NumberInput; diff --git a/src/components/NumberInput/index.ts b/src/components/NumberInput/index.ts new file mode 100644 index 000000000..4a25f86df --- /dev/null +++ b/src/components/NumberInput/index.ts @@ -0,0 +1,5 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import NumberInput from './NumberInput'; + +export default NumberInput; diff --git a/src/components/NumberInput/styles.less b/src/components/NumberInput/styles.less new file mode 100644 index 000000000..0fe8e8c81 --- /dev/null +++ b/src/components/NumberInput/styles.less @@ -0,0 +1,75 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +.number-input { + user-select: text; + display: flex; + max-width: 12rem; + margin-bottom: 1rem; + color: var(--primary-foreground-color); + background: var(--overlay-color); + border-radius: 100rem; + + .btn { + width: 2.875rem; + height: 2.875rem; + background: var(--overlay-color); + border: none; + border-radius: 50%; + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.3s; + z-index: 1; + + svg { + width: 1.625rem; + } + } + + .number-display { + height: 2.875rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 0 1rem; + + &::-moz-focus-inner { + border: none; + } + + &.with-btns { + padding: 0 1.9375rem; + margin-left: -1.4375rem; + margin-right: -1.4375rem; + max-width: 9.125rem; + } + } + + /* Label */ + .number-display .label { + font-size: 0.625rem; + font-weight: 400; + opacity: 0.7; + } + + /* Value */ + .number-display .value { + font-size: 1.3125rem; + display: flex; + width: 100%; + color: white; + text-align: center; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + -moz-appearance: textfield; + margin: 0; + } + } + + } diff --git a/src/components/index.ts b/src/components/index.ts index f65d66f81..ca3561bcd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -17,6 +17,7 @@ import ModalDialog from './ModalDialog'; import Multiselect from './Multiselect'; import MultiselectMenu from './MultiselectMenu'; import { HorizontalNavBar, VerticalNavBar } from './NavBar'; +import NumberInput from './NumberInput'; import Popup from './Popup'; import RadioButton from './RadioButton'; import SearchBar from './SearchBar'; @@ -48,6 +49,7 @@ export { MultiselectMenu, HorizontalNavBar, VerticalNavBar, + NumberInput, Popup, RadioButton, SearchBar, From 15c6a231a6d0fc1ceff4423aba30a98028c32371 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 17:27:53 +0200 Subject: [PATCH 02/40] feat(EpisodePicker): added season and episode picker when no streams loaded --- src/routes/MetaDetails/MetaDetails.js | 15 +++++++- .../EpisodePicker/EpisodePicker.less | 19 ++++++++++ .../EpisodePicker/EpisodePicker.tsx | 35 +++++++++++++++++++ .../StreamsList/EpisodePicker/index.ts | 7 ++++ .../MetaDetails/StreamsList/StreamsList.js | 13 +++++-- 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less create mode 100644 src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx create mode 100644 src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index 24c904cfd..a8338df53 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -76,6 +76,18 @@ const MetaDetails = ({ urlParams, queryParams }) => { const seasonOnSelect = React.useCallback((event) => { setSeason(event.value); }, [setSeason]); + const handleEpisodeSearch = React.useCallback((season, episode) => { + const searchVideoId = streamPath.id.replace(/(:\d+:\d+)$/, `:${season}:${episode}`); + const videoFound = metaDetails.metaItem.content.content.videos.find((video) => video.id === searchVideoId); + if (videoFound) { + if (typeof videoFound.deepLinks.player === 'string') { + window.location = videoFound.deepLinks.player; + } else if (typeof videoFound.deepLinks.metaDetailsStreams === 'string') { + window.location.replace(videoFound.deepLinks.metaDetailsStreams); + } + } + }, [streamPath, metaDetails.metaItem]); + const renderBackgroundImageFallback = React.useCallback(() => null, []); const renderBackground = React.useMemo(() => !!( metaPath && @@ -129,7 +141,7 @@ const MetaDetails = ({ urlParams, queryParams }) => { metaDetails.metaItem === null ?
{' -
No addons ware requested for this meta!
+
No addons were requested for this meta!
: metaDetails.metaItem.content.type === 'Err' ? @@ -169,6 +181,7 @@ const MetaDetails = ({ urlParams, queryParams }) => { className={styles['streams-list']} streams={metaDetails.streams} video={video} + onEpisodeSearch={handleEpisodeSearch} /> : metaPath !== null ? diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less new file mode 100644 index 000000000..879cff167 --- /dev/null +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less @@ -0,0 +1,19 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +.button-container { + display: flex; + align-items: center; + justify-content: center; + padding: 1rem 2.625rem; + margin: 2rem 0; + background-color: var(--primary-accent-color); + transition: 0.3s all ease-in-out; + border-radius: 2.625rem; + color: var(--primary-foreground-color); + border: 2px solid transparent; + + &:hover, &:focus { + outline: var(--focus-outline-size) solid var(--primary-foreground-color); + background-color: transparent; + } +} \ No newline at end of file diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx new file mode 100644 index 000000000..b6c6e9fe0 --- /dev/null +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -0,0 +1,35 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import React, { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, NumberInput } from 'stremio/components'; +import styles from './EpisodePicker.less'; + +type Props = { + className?: string, + seriesId: string; + onSubmit: (season: number, episode: number) => void; +}; +export const EpisodePicker = ({ className, seriesId, onSubmit }: Props) => { + const { t } = useTranslation(); + const [initialSeason, initialEpisode] = React.useMemo(() => { + const [, season, episode] = seriesId ? seriesId.split(':') : []; + return [parseInt(season || '1'), parseInt(episode || '1')]; + }, [seriesId]); + const seasonRef = useRef(null); + const episodeRef = useRef(null); + + const handleSubmit = React.useCallback(() => { + const season = seasonRef.current?.value; + const episode = episodeRef.current?.value; + if (typeof onSubmit === 'function') onSubmit(season, episode); + }, [onSubmit, seasonRef, episodeRef]); + + return
+ + + +
; +}; + +export default EpisodePicker; diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts b/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts new file mode 100644 index 000000000..fca5cad2e --- /dev/null +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts @@ -0,0 +1,7 @@ +// Copyright (C) 2017-2025 Smart code 203358507 + +import SeasonEpisodePicker from './EpisodePicker'; + +export default { + SeasonEpisodePicker +}; diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index bcb5cb015..2353a4268 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -10,10 +10,11 @@ const { useServices } = require('stremio/services'); const Stream = require('./Stream'); const styles = require('./styles'); const { usePlatform, useProfile } = require('stremio/common'); +const { SeasonEpisodePicker } = require('./EpisodePicker'); const ALL_ADDONS_KEY = 'ALL'; -const StreamsList = ({ className, video, ...props }) => { +const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { const { t } = useTranslation(); const { core } = useServices(); const platform = usePlatform(); @@ -93,6 +94,11 @@ const StreamsList = ({ className, video, ...props }) => { onSelect: onAddonSelected }; }, [streamsByAddon, selectedAddon]); + + const handleEpisodePicker = React.useCallback((season, episode) => { + onEpisodeSearch(season, episode); + }, [onEpisodeSearch]); + return (
@@ -122,12 +128,14 @@ const StreamsList = ({ className, video, ...props }) => { { props.streams.length === 0 ?
+ {'
No addons were requested for streams!
: props.streams.every((streams) => streams.content.type === 'Err') ?
+ {'
{t('NO_STREAM')}
{ @@ -193,7 +201,8 @@ const StreamsList = ({ className, video, ...props }) => { StreamsList.propTypes = { className: PropTypes.string, streams: PropTypes.arrayOf(PropTypes.object).isRequired, - video: PropTypes.object + video: PropTypes.object, + onEpisodeSearch: PropTypes.func }; module.exports = StreamsList; From 461c9d3d53a92e9a223c72f748d2cde75ec5e703 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 17:49:27 +0200 Subject: [PATCH 03/40] fix(EpisodePicker): fix export --- src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts b/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts index fca5cad2e..e71d7147d 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts @@ -2,6 +2,6 @@ import SeasonEpisodePicker from './EpisodePicker'; -export default { +export { SeasonEpisodePicker }; From 39f168a34c0cf57d1bb76afe465ffb626cc83b89 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 18:22:24 +0200 Subject: [PATCH 04/40] fix(EpisodePicker): handle season 0 as value --- src/components/NumberInput/NumberInput.tsx | 2 +- .../MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index fe6b84806..391017fc1 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -21,7 +21,7 @@ type Props = InputHTMLAttributes & { }; const NumberInput = forwardRef(({ defaultValue, ...props }, ref) => { - const [value, setValue] = useState(defaultValue || 1); + const [value, setValue] = useState(defaultValue || 0); const onKeyDown = useCallback((event: KeyboardEvent) => { props.onKeyDown && props.onKeyDown(event); diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index b6c6e9fe0..67ceba055 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -14,7 +14,9 @@ export const EpisodePicker = ({ className, seriesId, onSubmit }: Props) => { const { t } = useTranslation(); const [initialSeason, initialEpisode] = React.useMemo(() => { const [, season, episode] = seriesId ? seriesId.split(':') : []; - return [parseInt(season || '1'), parseInt(episode || '1')]; + const initialSeason = isNaN(parseInt(season)) ? 1 : parseInt(season); + const initialEpisode = isNaN(parseInt(episode)) ? 1 : parseInt(episode); + return [initialSeason, initialEpisode]; }, [seriesId]); const seasonRef = useRef(null); const episodeRef = useRef(null); @@ -26,7 +28,7 @@ export const EpisodePicker = ({ className, seriesId, onSubmit }: Props) => { }, [onSubmit, seasonRef, episodeRef]); return
- +
; From 538e462b12de94fc7d1e74a91e5b13c8e612fcb8 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 18:23:28 +0200 Subject: [PATCH 05/40] fix(StreamsList): hide Install addons button if episode is upcoming --- src/routes/MetaDetails/StreamsList/StreamsList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 2353a4268..cb2b437d0 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -26,8 +26,8 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { setSelectedAddon(event.value); }, [platform]); const showInstallAddonsButton = React.useMemo(() => { - return !profile || profile.auth === null || profile.auth?.user?.isNewUser === true; - }, [profile]); + return !profile || profile.auth === null || profile.auth?.user?.isNewUser === true && !video?.upcoming; + }, [profile, video]); const backButtonOnClick = React.useCallback(() => { if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') { window.location.replace(video.deepLinks.metaDetailsVideos + ( From 36a2896525cac77fee0d85090ccbb2fa522a8a06 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 19:13:23 +0200 Subject: [PATCH 06/40] fix(NumberInput): use color variable for font color --- src/components/NumberInput/styles.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/NumberInput/styles.less b/src/components/NumberInput/styles.less index 0fe8e8c81..484f3661a 100644 --- a/src/components/NumberInput/styles.less +++ b/src/components/NumberInput/styles.less @@ -15,7 +15,7 @@ background: var(--overlay-color); border: none; border-radius: 50%; - color: white; + color: var(--primary-foreground-color); display: flex; align-items: center; justify-content: center; @@ -61,7 +61,7 @@ font-size: 1.3125rem; display: flex; width: 100%; - color: white; + color: var(--primary-foreground-color); text-align: center; &::-webkit-outer-spin-button, From 7fa4f462c5e8183b9e6c3e6f84488958bda3e632 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 7 Feb 2025 19:23:33 +0200 Subject: [PATCH 07/40] feat(StreamsList): added upcoming label when no results --- src/routes/MetaDetails/StreamsList/StreamsList.js | 1 + src/routes/MetaDetails/StreamsList/styles.less | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index cb2b437d0..0ccb05e28 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -136,6 +136,7 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { props.streams.every((streams) => streams.content.type === 'Err') ?
+ {video?.upcoming &&
{t('UPCOMING')}...
} {'
{t('NO_STREAM')}
{ diff --git a/src/routes/MetaDetails/StreamsList/styles.less b/src/routes/MetaDetails/StreamsList/styles.less index 0f9ab2a0a..8c274c761 100644 --- a/src/routes/MetaDetails/StreamsList/styles.less +++ b/src/routes/MetaDetails/StreamsList/styles.less @@ -38,6 +38,7 @@ font-size: 1.4rem; text-align: center; color: var(--primary-foreground-color); + margin-bottom: 2rem; } } @@ -171,6 +172,7 @@ max-height: 3.6em; text-align: center; color: var(--primary-foreground-color); + margin-bottom: 0; } } } From 5e983558967f0b981bc2b8477355f49e0c86aab2 Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 12:37:40 +0200 Subject: [PATCH 08/40] fix(NumberInput): fix check for min and max values --- src/components/NumberInput/NumberInput.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 391017fc1..874019162 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -32,7 +32,7 @@ const NumberInput = forwardRef(({ defaultValue, ...prop const handleIncrease = () => { const { max } = props; - if (max) { + if (typeof max !== 'undefined') { return setValue((prevVal) => prevVal + 1 > max ? max : prevVal + 1 ); @@ -42,7 +42,7 @@ const NumberInput = forwardRef(({ defaultValue, ...prop const handleDecrease = () => { const { min } = props; - if (min) { + if (typeof min !== 'undefined') { return setValue((prevVal) => prevVal - 1 < min ? min : prevVal - 1 ); @@ -55,7 +55,7 @@ const NumberInput = forwardRef(({ defaultValue, ...prop {props.showButtons ? : null}
@@ -72,7 +72,7 @@ const NumberInput = forwardRef(({ defaultValue, ...prop />
{props.showButtons ? : null}
From 6b30b90893384d31310cdc0e5d80eb13a374cccd Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 13:31:44 +0200 Subject: [PATCH 09/40] fix(MetaDetails): handle search for any episode and season --- src/routes/MetaDetails/MetaDetails.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index a8338df53..e2c89bb74 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -77,16 +77,11 @@ const MetaDetails = ({ urlParams, queryParams }) => { setSeason(event.value); }, [setSeason]); const handleEpisodeSearch = React.useCallback((season, episode) => { - const searchVideoId = streamPath.id.replace(/(:\d+:\d+)$/, `:${season}:${episode}`); - const videoFound = metaDetails.metaItem.content.content.videos.find((video) => video.id === searchVideoId); - if (videoFound) { - if (typeof videoFound.deepLinks.player === 'string') { - window.location = videoFound.deepLinks.player; - } else if (typeof videoFound.deepLinks.metaDetailsStreams === 'string') { - window.location.replace(videoFound.deepLinks.metaDetailsStreams); - } - } - }, [streamPath, metaDetails.metaItem]); + const searchVideoHash = encodeURIComponent(`${urlParams.id}:${season}:${episode}`); + const url = window.location.hash; + const searchVideoPath = url.replace(encodeURIComponent(urlParams.videoId), searchVideoHash); + window.location = searchVideoPath; + }, [urlParams, window.location]); const renderBackgroundImageFallback = React.useCallback(() => null, []); const renderBackground = React.useMemo(() => !!( From 34808d6014826e8318e00c14bc8e6bc247bb0c15 Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 14:18:13 +0200 Subject: [PATCH 10/40] fix(EpisodePicker): set season and episode from url --- src/components/NumberInput/NumberInput.tsx | 8 +++---- .../EpisodePicker/EpisodePicker.tsx | 22 ++++++++++--------- .../MetaDetails/StreamsList/StreamsList.js | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 874019162..37daf6401 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { forwardRef, useCallback, useState } from 'react'; +import React, { ChangeEvent, forwardRef, useCallback, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './styles.less'; @@ -55,7 +55,7 @@ const NumberInput = forwardRef(({ defaultValue, ...prop {props.showButtons ? : null}
@@ -67,12 +67,12 @@ const NumberInput = forwardRef(({ defaultValue, ...prop value={value} {...props} className={classnames(props.className, styles['value'], { 'disabled': props.disabled })} - onChange={(event) => setValue(event.value)} + onChange={(event: ChangeEvent) => setValue(parseInt(event.target.value))} onKeyDown={onKeyDown} />
{props.showButtons ? : null}
diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 67ceba055..51e13ee7a 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -10,21 +10,23 @@ type Props = { seriesId: string; onSubmit: (season: number, episode: number) => void; }; -export const EpisodePicker = ({ className, seriesId, onSubmit }: Props) => { +export const EpisodePicker = ({ className, onSubmit }: Props) => { const { t } = useTranslation(); + const splitPath = window.location.hash.split('/'); + const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); const [initialSeason, initialEpisode] = React.useMemo(() => { - const [, season, episode] = seriesId ? seriesId.split(':') : []; - const initialSeason = isNaN(parseInt(season)) ? 1 : parseInt(season); - const initialEpisode = isNaN(parseInt(episode)) ? 1 : parseInt(episode); + const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; + const initialSeason = isNaN(parseInt(pathSeason)) ? 1 : parseInt(pathSeason); + const initialEpisode = isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode); return [initialSeason, initialEpisode]; - }, [seriesId]); - const seasonRef = useRef(null); - const episodeRef = useRef(null); + }, [videoId]); + const seasonRef = useRef(null); + const episodeRef = useRef(null); const handleSubmit = React.useCallback(() => { - const season = seasonRef.current?.value; - const episode = episodeRef.current?.value; - if (typeof onSubmit === 'function') onSubmit(season, episode); + const season = seasonRef.current?.value || 1; + const episode = episodeRef.current?.value || 1; + if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(parseInt(episode))) onSubmit(season, episode); }, [onSubmit, seasonRef, episodeRef]); return
diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 0ccb05e28..3964a6e53 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -128,14 +128,14 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { { props.streams.length === 0 ?
- + {'
No addons were requested for streams!
: props.streams.every((streams) => streams.content.type === 'Err') ?
- + {video?.upcoming &&
{t('UPCOMING')}...
} {'
{t('NO_STREAM')}
From d407e6c7b7ae8bfb9f3f6fc54324f4af3f706c25 Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 15:12:43 +0200 Subject: [PATCH 11/40] fix(NumberInput): min & max validation when entering value from keyboard --- src/components/NumberInput/NumberInput.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 37daf6401..303e04cbf 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -67,7 +67,12 @@ const NumberInput = forwardRef(({ defaultValue, ...prop value={value} {...props} className={classnames(props.className, styles['value'], { 'disabled': props.disabled })} - onChange={(event: ChangeEvent) => setValue(parseInt(event.target.value))} + onChange={(event: ChangeEvent) => { + const newValue = parseInt(event.target.value); + if (props.min !== undefined && newValue < props.min) return props.min; + if (props.max !== undefined && newValue > props.max) return props.max; + setValue(newValue); + }} onKeyDown={onKeyDown} />
From fbdfa110b50729d4e37046e379c3a4421f0f2c5e Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 17:38:32 +0200 Subject: [PATCH 12/40] fix(StreamsList): add scroll when episode picker is shown on landscape orientation on mobile --- src/routes/MetaDetails/StreamsList/StreamsList.js | 4 ++-- src/routes/MetaDetails/StreamsList/styles.less | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 3964a6e53..ef7cea157 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -128,14 +128,14 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { { props.streams.length === 0 ?
- + {'
No addons were requested for streams!
: props.streams.every((streams) => streams.content.type === 'Err') ?
- + {video?.upcoming &&
{t('UPCOMING')}...
} {'
{t('NO_STREAM')}
diff --git a/src/routes/MetaDetails/StreamsList/styles.less b/src/routes/MetaDetails/StreamsList/styles.less index 8c274c761..0bffa8fcc 100644 --- a/src/routes/MetaDetails/StreamsList/styles.less +++ b/src/routes/MetaDetails/StreamsList/styles.less @@ -22,6 +22,10 @@ padding: 1rem; overflow-y: auto; + .search { + flex: none; + } + .image { flex: none; width: 10rem; From f7494d6e9714b20f4f2d2d3915549c64573a177a Mon Sep 17 00:00:00 2001 From: Botzy Date: Mon, 10 Feb 2025 17:52:57 +0200 Subject: [PATCH 13/40] fix(EpisodePicker): typings --- .../StreamsList/EpisodePicker/EpisodePicker.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 51e13ee7a..aa70d021e 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -24,9 +24,11 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const episodeRef = useRef(null); const handleSubmit = React.useCallback(() => { - const season = seasonRef.current?.value || 1; - const episode = episodeRef.current?.value || 1; - if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(parseInt(episode))) onSubmit(season, episode); + const season = parseInt(seasonRef.current?.value || '1'); + const episode = parseInt(episodeRef.current?.value || '1'); + if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { + onSubmit(season, episode); + } }, [onSubmit, seasonRef, episodeRef]); return
From 6ca94a21248baa5e572d01c7933e5c9302b79a5d Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 11 Feb 2025 16:41:36 +0200 Subject: [PATCH 14/40] fix(EpisodePicker): unify styles with install addons button --- .../EpisodePicker/EpisodePicker.less | 26 +++++++++++++------ .../EpisodePicker/EpisodePicker.tsx | 25 +++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less index 879cff167..f5e1f509c 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less @@ -1,19 +1,29 @@ // Copyright (C) 2017-2025 Smart code 203358507 .button-container { + flex: none; + align-self: stretch; display: flex; align-items: center; justify-content: center; - padding: 1rem 2.625rem; - margin: 2rem 0; + border: var(--focus-outline-size) solid var(--primary-accent-color); background-color: var(--primary-accent-color); - transition: 0.3s all ease-in-out; - border-radius: 2.625rem; - color: var(--primary-foreground-color); - border: 2px solid transparent; + height: 4rem; + padding: 0 2rem; + margin: 1rem auto; + border-radius: 2rem; - &:hover, &:focus { - outline: var(--focus-outline-size) solid var(--primary-foreground-color); + &:hover { background-color: transparent; } + + .label { + flex: 0 1 auto; + font-size: 1rem; + font-weight: 700; + max-height: 3.6em; + text-align: center; + color: var(--primary-foreground-color); + margin-bottom: 0; + } } \ No newline at end of file diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index aa70d021e..44da39bfa 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -14,15 +14,22 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const { t } = useTranslation(); const splitPath = window.location.hash.split('/'); const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); - const [initialSeason, initialEpisode] = React.useMemo(() => { - const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; + const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; + const [season, setSeason] = React.useState(() => { const initialSeason = isNaN(parseInt(pathSeason)) ? 1 : parseInt(pathSeason); + return initialSeason; + }); + const [episode, setEpisode] = React.useState(() => { const initialEpisode = isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode); - return [initialSeason, initialEpisode]; - }, [videoId]); + return initialEpisode; + }); const seasonRef = useRef(null); const episodeRef = useRef(null); + const handleSeasonChange = (value?: number) => setSeason(value !== undefined ? value : 1); + + const handleEpisodeChange = (value?: number) => setEpisode(value !== undefined ? value : 1); + const handleSubmit = React.useCallback(() => { const season = parseInt(seasonRef.current?.value || '1'); const episode = parseInt(episodeRef.current?.value || '1'); @@ -31,10 +38,14 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { } }, [onSubmit, seasonRef, episodeRef]); + const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); + return
- - - + + +
; }; From 3f60df9073b5ea8493a0b4dead5ae4f471074b1a Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 11 Feb 2025 19:05:46 +0200 Subject: [PATCH 15/40] refactor(EpisodePicker): improve styles and typings --- src/components/NumberInput/NumberInput.tsx | 90 +++++++++----- src/components/NumberInput/styles.less | 112 ++++++++---------- .../EpisodePicker/EpisodePicker.less | 2 +- .../EpisodePicker/EpisodePicker.tsx | 19 ++- 4 files changed, 119 insertions(+), 104 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 303e04cbf..2add02be5 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { ChangeEvent, forwardRef, useCallback, useState } from 'react'; +import React, { ChangeEvent, forwardRef, useCallback, useEffect, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './styles.less'; @@ -18,10 +18,11 @@ type Props = InputHTMLAttributes & { max?: number; onKeyDown?: (event: KeyboardEvent) => void; onSubmit?: (event: KeyboardEvent) => void; + onUpdate?: (value: number) => void; }; -const NumberInput = forwardRef(({ defaultValue, ...props }, ref) => { - const [value, setValue] = useState(defaultValue || 0); +const NumberInput = forwardRef(({ defaultValue, showButtons, onUpdate, ...props }, ref) => { + const [value, setValue] = useState(defaultValue || 0); const onKeyDown = useCallback((event: KeyboardEvent) => { props.onKeyDown && props.onKeyDown(event); @@ -32,33 +33,63 @@ const NumberInput = forwardRef(({ defaultValue, ...prop const handleIncrease = () => { const { max } = props; - if (typeof max !== 'undefined') { - return setValue((prevVal) => - prevVal + 1 > max ? max : prevVal + 1 - ); + if (max !== undefined) { + return setValue((prevVal) => { + const value = prevVal || 0; + return value + 1 > max ? max : value + 1; + }); } - setValue((prevVal) => prevVal + 1); + setValue((prevVal) => { + const value = prevVal || 0; + return value + 1; + }); }; const handleDecrease = () => { const { min } = props; - if (typeof min !== 'undefined') { - return setValue((prevVal) => - prevVal - 1 < min ? min : prevVal - 1 - ); + if (min !== undefined) { + return setValue((prevVal) => { + const value = prevVal || 0; + return value - 1 < min ? min : value - 1; + }); } - setValue((prevVal) => prevVal - 1); + setValue((prevVal) => { + const value = prevVal || 0; + return value - 1; + }); }; + const handleChange = (event: ChangeEvent) => { + const min = props.min || 0; + let newValue = event.target.valueAsNumber; + if (newValue && newValue < min) { + newValue = min; + } + if (props.max !== undefined && newValue && newValue > props.max) { + newValue = props.max; + } + setValue(newValue); + }; + + useEffect(() => { + if (typeof onUpdate === 'function') { + onUpdate(value); + } + }, [value]); + return (
- {props.showButtons ? : null} -
+ { + showButtons ? + + : null + } +
{props.label &&
{props.label}
} (({ defaultValue, ...prop value={value} {...props} className={classnames(props.className, styles['value'], { 'disabled': props.disabled })} - onChange={(event: ChangeEvent) => { - const newValue = parseInt(event.target.value); - if (props.min !== undefined && newValue < props.min) return props.min; - if (props.max !== undefined && newValue > props.max) return props.max; - setValue(newValue); - }} + onChange={handleChange} onKeyDown={onKeyDown} />
- {props.showButtons ? : null} + { + showButtons ? + + : null + }
); }); diff --git a/src/components/NumberInput/styles.less b/src/components/NumberInput/styles.less index 484f3661a..a88bc6d20 100644 --- a/src/components/NumberInput/styles.less +++ b/src/components/NumberInput/styles.less @@ -3,73 +3,63 @@ .number-input { user-select: text; display: flex; - max-width: 12rem; + max-width: 14rem; + height: 3.5rem; margin-bottom: 1rem; color: var(--primary-foreground-color); background: var(--overlay-color); - border-radius: 100rem; + border-radius: 3.5rem; - .btn { - width: 2.875rem; - height: 2.875rem; - background: var(--overlay-color); - border: none; - border-radius: 50%; - color: var(--primary-foreground-color); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: background 0.3s; - z-index: 1; - - svg { - width: 1.625rem; - } - } - - .number-display { - height: 2.875rem; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 0 1rem; - - &::-moz-focus-inner { + .button { + flex: none; + width: 3.5rem; + height: 3.5rem; + padding: 1rem; + background: var(--overlay-color); border: none; + border-radius: 100%; + cursor: pointer; + z-index: 1; + + .icon { + width: 100%; + height: 100%; + } } - &.with-btns { - padding: 0 1.9375rem; - margin-left: -1.4375rem; - margin-right: -1.4375rem; - max-width: 9.125rem; - } - } - - /* Label */ - .number-display .label { - font-size: 0.625rem; - font-weight: 400; - opacity: 0.7; - } - - /* Value */ - .number-display .value { - font-size: 1.3125rem; - display: flex; - width: 100%; - color: var(--primary-foreground-color); - text-align: center; + .number-display { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 0 1rem; - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - -moz-appearance: textfield; - margin: 0; + &::-moz-focus-inner { + border: none; + } + + .label { + font-size: 0.8rem; + font-weight: 400; + opacity: 0.7; + } + + .value { + font-size: 1.2rem; + display: flex; + justify-content: center; + width: 100%; + color: var(--primary-foreground-color); + text-align: center; + appearance: none; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + } } - } - - } +} \ No newline at end of file diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less index f5e1f509c..260eb8ebe 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less @@ -21,7 +21,7 @@ flex: 0 1 auto; font-size: 1rem; font-weight: 700; - max-height: 3.6em; + max-height: 3.5rem; text-align: center; color: var(--primary-foreground-color); margin-bottom: 0; diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 44da39bfa..ddb38e353 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { useRef } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { Button, NumberInput } from 'stremio/components'; import styles from './EpisodePicker.less'; @@ -16,33 +16,28 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; const [season, setSeason] = React.useState(() => { - const initialSeason = isNaN(parseInt(pathSeason)) ? 1 : parseInt(pathSeason); + const initialSeason = isNaN(parseInt(pathSeason)) ? 0 : parseInt(pathSeason); return initialSeason; }); const [episode, setEpisode] = React.useState(() => { const initialEpisode = isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode); return initialEpisode; }); - const seasonRef = useRef(null); - const episodeRef = useRef(null); + const handleSeasonChange = (value: number) => setSeason(!isNaN(value) ? value : 1); - const handleSeasonChange = (value?: number) => setSeason(value !== undefined ? value : 1); - - const handleEpisodeChange = (value?: number) => setEpisode(value !== undefined ? value : 1); + const handleEpisodeChange = (value: number) => setEpisode(!isNaN(value) ? value : 1); const handleSubmit = React.useCallback(() => { - const season = parseInt(seasonRef.current?.value || '1'); - const episode = parseInt(episodeRef.current?.value || '1'); if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { onSubmit(season, episode); } - }, [onSubmit, seasonRef, episodeRef]); + }, [onSubmit, season, episode]); const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); return
- - + + From 3c2ab92bd698b6c0b5c35093422793c3d976e225 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 11 Feb 2025 19:27:49 +0200 Subject: [PATCH 16/40] fix(EpisodePicker): make 0 the initial value for season when no value provided, remove placeholder --- .../MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index ddb38e353..031e3883d 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -23,7 +23,7 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const initialEpisode = isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode); return initialEpisode; }); - const handleSeasonChange = (value: number) => setSeason(!isNaN(value) ? value : 1); + const handleSeasonChange = (value: number) => setSeason(!isNaN(value) ? value : 0); const handleEpisodeChange = (value: number) => setEpisode(!isNaN(value) ? value : 1); @@ -36,7 +36,7 @@ export const EpisodePicker = ({ className, onSubmit }: Props) => { const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); return
- + -
; +return ( +
+ + + +
+ ); }; export default EpisodePicker; From aa3dedf8be56db2c53a5bc6092ce4f4e6c215037 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 12 Feb 2025 15:38:14 +0200 Subject: [PATCH 25/40] fix: linting --- src/components/NumberInput/NumberInput.tsx | 4 ++-- .../MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx | 2 +- src/routes/MetaDetails/StreamsList/StreamsList.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 71879a1fe..b883d3e2e 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -91,9 +91,9 @@ const NumberInput = forwardRef(({ defaultValue, showBut }
{ - props.label ? + props.label ?
{props.label}
- : null + : null } { const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); -return ( + return (
diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 7bb8a33f7..576e13669 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -137,9 +137,9 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => {
{ - video?.upcoming ? + video?.upcoming ?
{t('UPCOMING')}...
- : null + : null } {'
{t('NO_STREAM')}
From 10a36d2c4dd565456927e857123d41f71e40ef02 Mon Sep 17 00:00:00 2001 From: Botsy Date: Wed, 12 Feb 2025 15:46:52 +0200 Subject: [PATCH 26/40] Update src/components/NumberInput/NumberInput.tsx Co-authored-by: Timothy Z. --- src/components/NumberInput/NumberInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index b883d3e2e..38433d874 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -89,7 +89,7 @@ const NumberInput = forwardRef(({ defaultValue, showBut : null } -
+
{ props.label ?
{props.label}
From f678375633e0c1fe0f88c10c77817a77b2eea484 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 12 Feb 2025 16:00:41 +0200 Subject: [PATCH 27/40] refactor(NumberInput): apply suggested improvements --- src/components/NumberInput/NumberInput.tsx | 52 ++++++++-------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 38433d874..457db28b8 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -31,52 +31,38 @@ const NumberInput = forwardRef(({ defaultValue, showBut } }, [props.onKeyDown, props.onSubmit]); + const updateValueAndNotify = (valueAsNumber: number) => { + setValue(valueAsNumber); + onUpdate?.(valueAsNumber); + }; + const handleIncrease = () => { - const { max } = props; - if (max !== undefined) { - return setValue((prevVal) => { - const value = prevVal || 0; - return value + 1 > max ? max : value + 1; - }); + if (props.max !== undefined) { + updateValueAndNotify(Math.min(props.max, (value || 0) + 1)); + return; } - setValue((prevVal) => { - const value = prevVal || 0; - return value + 1; - }); + updateValueAndNotify((value || 0) + 1); }; const handleDecrease = () => { - const { min } = props; - if (min !== undefined) { - return setValue((prevVal) => { - const value = prevVal || 0; - return value - 1 < min ? min : value - 1; - }); + if (props.min !== undefined) { + updateValueAndNotify(Math.max(props.min, (value || 0) - 1)); + return; } - setValue((prevVal) => { - const value = prevVal || 0; - return value - 1; - }); + updateValueAndNotify((value || 0) - 1); }; - const handleChange = (event: ChangeEvent) => { + const handleChange = ({ target: { valueAsNumber }}: ChangeEvent) => { const min = props.min || 0; - let newValue = event.target.valueAsNumber; - if (newValue && newValue < min) { - newValue = min; + if (valueAsNumber && valueAsNumber < min) { + valueAsNumber = min; } - if (props.max !== undefined && newValue && newValue > props.max) { - newValue = props.max; + if (props.max !== undefined && valueAsNumber && valueAsNumber > props.max) { + valueAsNumber = props.max; } - setValue(newValue); + updateValueAndNotify(valueAsNumber); }; - useEffect(() => { - if (typeof onUpdate === 'function') { - onUpdate(value); - } - }, [value]); - return (
{ From 4cd9db53d1ad8beae5d71dff878010f7fc342812 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 12 Feb 2025 16:01:28 +0200 Subject: [PATCH 28/40] fix(NumberInput): remove unused import --- src/components/NumberInput/NumberInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 457db28b8..0d3e55c53 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2017-2025 Smart code 203358507 import Icon from '@stremio/stremio-icons/react'; -import React, { ChangeEvent, forwardRef, useCallback, useEffect, useState } from 'react'; +import React, { ChangeEvent, forwardRef, useCallback, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './NumberInput.less'; From 07cc2a9b2d56767739537fb55b2447436b24e683 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 12 Feb 2025 16:59:37 +0200 Subject: [PATCH 29/40] refactor(EpisodePicker): apply suggested improvements --- .../EpisodePicker/EpisodePicker.tsx | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index d47d44abd..4a82ec591 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, NumberInput } from 'stremio/components'; import styles from './EpisodePicker.less'; @@ -13,22 +13,32 @@ type Props = { const EpisodePicker = ({ className, onSubmit }: Props) => { const { t } = useTranslation(); - const splitPath = window.location.hash.split('/'); - const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); - const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; - const [season, setSeason] = React.useState(isNaN(parseInt(pathSeason)) ? 0 : parseInt(pathSeason)); - const [episode, setEpisode] = React.useState(isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode)); - const handleSeasonChange = (value: number) => setSeason(!isNaN(value) ? value : 0); - const handleEpisodeChange = (value: number) => setEpisode(!isNaN(value) ? value : 1); + const { initialSeason, initialEpisode } = useMemo(() => { + const splitPath = window.location.hash.split('/'); + const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); + const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; + return { + initialSeason: isNaN(parseInt(pathSeason)) ? 0 : parseInt(pathSeason), + initialEpisode: isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode) + }; + }, []); - const handleSubmit = React.useCallback(() => { + const [season, setSeason] = useState(initialSeason); + const [episode, setEpisode] = useState(initialEpisode); + + const handleSeasonChange = useCallback((value: number) => setSeason(!isNaN(value) ? value : 0), []); + const handleEpisodeChange = useCallback((value: number) => setEpisode(!isNaN(value) ? value : 1), []); + + const handleSubmit = useCallback(() => { if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { onSubmit(season, episode); } }, [onSubmit, season, episode]); - const disabled = React.useMemo(() => season === parseInt(pathSeason) && episode === parseInt(pathEpisode), [pathSeason, pathEpisode, season, episode]); + const disabled = useMemo(() => { + return season === initialSeason && episode === initialEpisode; + }, [season, episode, initialSeason, initialEpisode]); return (
From 675328ca081553068adab8db07b3c93f9fa2902b Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 12 Feb 2025 17:27:37 +0200 Subject: [PATCH 30/40] fix(StreamsList): show EpisodePicker only if type is series --- src/routes/MetaDetails/MetaDetails.js | 1 + src/routes/MetaDetails/StreamsList/StreamsList.js | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index e2c89bb74..8a50b59c1 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -176,6 +176,7 @@ const MetaDetails = ({ urlParams, queryParams }) => { className={styles['streams-list']} streams={metaDetails.streams} video={video} + type={streamPath.type} onEpisodeSearch={handleEpisodeSearch} /> : diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 576e13669..2d44d35fc 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -14,7 +14,7 @@ const { default: SeasonEpisodePicker } = require('./EpisodePicker'); const ALL_ADDONS_KEY = 'ALL'; -const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { +const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => { const { t } = useTranslation(); const { core } = useServices(); const platform = usePlatform(); @@ -128,14 +128,22 @@ const StreamsList = ({ className, video, onEpisodeSearch, ...props }) => { { props.streams.length === 0 ?
- + { + type === 'series' ? + + : null + } {'
No addons were requested for streams!
: props.streams.every((streams) => streams.content.type === 'Err') ?
- + { + type === 'series' ? + + : null + } { video?.upcoming ?
{t('UPCOMING')}...
@@ -207,6 +215,7 @@ StreamsList.propTypes = { className: PropTypes.string, streams: PropTypes.arrayOf(PropTypes.object).isRequired, video: PropTypes.object, + type: PropTypes.string, onEpisodeSearch: PropTypes.func }; From 6999ef6a8d45b58fe05692e6c11892380f7e309c Mon Sep 17 00:00:00 2001 From: Botsy Date: Thu, 13 Feb 2025 12:37:36 +0200 Subject: [PATCH 31/40] Update src/components/NumberInput/NumberInput.tsx Co-authored-by: Timothy Z. --- src/components/NumberInput/NumberInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 0d3e55c53..29070c70c 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -26,7 +26,7 @@ const NumberInput = forwardRef(({ defaultValue, showBut const onKeyDown = useCallback((event: KeyboardEvent) => { props.onKeyDown && props.onKeyDown(event); - if (event.key === 'Enter' ) { + if (event.key === 'Enter') { props.onSubmit && props.onSubmit(event); } }, [props.onKeyDown, props.onSubmit]); From 56762353f284016d959e776b87feba5bd2c5d1fc Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 25 Mar 2025 18:03:36 +0200 Subject: [PATCH 32/40] refactor(NumberInput): simplify --- src/components/NumberInput/NumberInput.tsx | 82 ++++++++++++---------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 29070c70c..90cb565ac 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2017-2025 Smart code 203358507 import Icon from '@stremio/stremio-icons/react'; -import React, { ChangeEvent, forwardRef, useCallback, useState } from 'react'; +import React, { ChangeEvent, forwardRef, memo, useCallback, useMemo, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './NumberInput.less'; @@ -12,55 +12,61 @@ type Props = InputHTMLAttributes & { className?: string; disabled?: boolean; showButtons?: boolean; - defaultValue?: number; label?: string; min?: number; max?: number; + value?: number; + defaultValue?: number; onKeyDown?: (event: KeyboardEvent) => void; onSubmit?: (event: KeyboardEvent) => void; onUpdate?: (value: number) => void; + onChange?: (event: ChangeEvent) => void; }; -const NumberInput = forwardRef(({ defaultValue, showButtons, onUpdate, ...props }, ref) => { - const [value, setValue] = useState(defaultValue || 0); - const onKeyDown = useCallback((event: KeyboardEvent) => { - props.onKeyDown && props.onKeyDown(event); +const NumberInput = forwardRef(({ defaultValue = 1, showButtons, onUpdate, onKeyDown, onSubmit, min, max, onChange, ...props }, ref) => { + const [value, setValue] = useState(defaultValue); + const displayValue = useMemo(() => props.value ?? value, [props.value, value]); + + const handleKeyDown = useCallback((event: KeyboardEvent) => { + onKeyDown && onKeyDown(event); if (event.key === 'Enter') { - props.onSubmit && props.onSubmit(event); + onSubmit && onSubmit(event); } - }, [props.onKeyDown, props.onSubmit]); + }, [onKeyDown, onSubmit]); - const updateValueAndNotify = (valueAsNumber: number) => { - setValue(valueAsNumber); - onUpdate?.(valueAsNumber); + const handleValueChange = (value: number) => { + if (props.value === undefined) { + setValue(value); + } + onUpdate?.(value); + onChange?.({ target: { value: value.toString() }} as ChangeEvent); }; - const handleIncrease = () => { - if (props.max !== undefined) { - updateValueAndNotify(Math.min(props.max, (value || 0) + 1)); - return; - } - updateValueAndNotify((value || 0) + 1); + const handleIncrement = () => { + handleValueChange(clampValueToRange((displayValue || 0) + 1)); }; - const handleDecrease = () => { - if (props.min !== undefined) { - updateValueAndNotify(Math.max(props.min, (value || 0) - 1)); - return; - } - updateValueAndNotify((value || 0) - 1); + const handleDecrement = () => { + handleValueChange(clampValueToRange((displayValue || 0) - 1)); }; - const handleChange = ({ target: { valueAsNumber }}: ChangeEvent) => { - const min = props.min || 0; - if (valueAsNumber && valueAsNumber < min) { - valueAsNumber = min; + const clampValueToRange = (value: number): number => { + const minValue = min ?? 0; + + if (value < minValue) { + return minValue; } - if (props.max !== undefined && valueAsNumber && valueAsNumber > props.max) { - valueAsNumber = props.max; + + if (max !== undefined && value > max) { + return max; } - updateValueAndNotify(valueAsNumber); + + return value; + }; + + const handleInputChange = ({ target: { valueAsNumber }}: ChangeEvent) => { + handleValueChange(clampValueToRange(valueAsNumber || 0)); }; return ( @@ -69,8 +75,8 @@ const NumberInput = forwardRef(({ defaultValue, showBut showButtons ? : null @@ -85,17 +91,17 @@ const NumberInput = forwardRef(({ defaultValue, showBut ref={ref} type={'number'} tabIndex={0} - value={value} + value={displayValue} {...props} - className={classnames(props.className, styles['value'], { 'disabled': props.disabled })} - onChange={handleChange} - onKeyDown={onKeyDown} + className={classnames(props.className, styles['value'], { [styles.disabled]: props.disabled })} + onChange={handleInputChange} + onKeyDown={handleKeyDown} />
{ showButtons ? : null @@ -106,4 +112,4 @@ const NumberInput = forwardRef(({ defaultValue, showBut NumberInput.displayName = 'NumberInput'; -export default NumberInput; +export default memo(NumberInput); From 60ba559500b0ec0a85fc5466ec1431519b46d73f Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 25 Mar 2025 18:11:14 +0200 Subject: [PATCH 33/40] refactor(EpisodePicker): simplify --- src/components/NumberInput/NumberInput.tsx | 14 ++++++-------- .../EpisodePicker/EpisodePicker.tsx | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 90cb565ac..9298cf8ea 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -12,20 +12,19 @@ type Props = InputHTMLAttributes & { className?: string; disabled?: boolean; showButtons?: boolean; + defaultValue?: number; label?: string; min?: number; max?: number; value?: number; - defaultValue?: number; onKeyDown?: (event: KeyboardEvent) => void; onSubmit?: (event: KeyboardEvent) => void; - onUpdate?: (value: number) => void; onChange?: (event: ChangeEvent) => void; }; -const NumberInput = forwardRef(({ defaultValue = 1, showButtons, onUpdate, onKeyDown, onSubmit, min, max, onChange, ...props }, ref) => { +const NumberInput = forwardRef(({ defaultValue = 0, showButtons, onKeyDown, onSubmit, min, max, onChange, ...props }, ref) => { const [value, setValue] = useState(defaultValue); - const displayValue = useMemo(() => props.value ?? value, [props.value, value]); + const displayValue = props.value ?? value; const handleKeyDown = useCallback((event: KeyboardEvent) => { onKeyDown && onKeyDown(event); @@ -35,12 +34,11 @@ const NumberInput = forwardRef(({ defaultValue = 1, sho } }, [onKeyDown, onSubmit]); - const handleValueChange = (value: number) => { + const handleValueChange = (newValue: number) => { if (props.value === undefined) { - setValue(value); + setValue(newValue); } - onUpdate?.(value); - onChange?.({ target: { value: value.toString() }} as ChangeEvent); + onChange?.({ target: { value: newValue.toString() }} as ChangeEvent); }; const handleIncrement = () => { diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 4a82ec591..24d12229f 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, NumberInput } from 'stremio/components'; import styles from './EpisodePicker.less'; @@ -19,16 +19,21 @@ const EpisodePicker = ({ className, onSubmit }: Props) => { const videoId = decodeURIComponent(splitPath[splitPath.length - 1]); const [, pathSeason, pathEpisode] = videoId ? videoId.split(':') : []; return { - initialSeason: isNaN(parseInt(pathSeason)) ? 0 : parseInt(pathSeason), - initialEpisode: isNaN(parseInt(pathEpisode)) ? 1 : parseInt(pathEpisode) + initialSeason: parseInt(pathSeason) || 0, + initialEpisode: parseInt(pathEpisode) || 1 }; }, []); const [season, setSeason] = useState(initialSeason); const [episode, setEpisode] = useState(initialEpisode); - const handleSeasonChange = useCallback((value: number) => setSeason(!isNaN(value) ? value : 0), []); - const handleEpisodeChange = useCallback((value: number) => setEpisode(!isNaN(value) ? value : 1), []); + const handleSeasonChange = useCallback((event: ChangeEvent) => { + setSeason(parseInt(event.target.value)); + }, []); + + const handleEpisodeChange = useCallback((event: ChangeEvent) => { + setEpisode(parseInt(event.target.value)); + }, []); const handleSubmit = useCallback(() => { if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { @@ -42,8 +47,8 @@ const EpisodePicker = ({ className, onSubmit }: Props) => { return (
- - + + From cdaad5c83415906ae129208b125e1a6c151157e3 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 25 Mar 2025 18:12:29 +0200 Subject: [PATCH 34/40] fix(NumberInput): lint --- src/components/NumberInput/NumberInput.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 9298cf8ea..7094aca51 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2017-2025 Smart code 203358507 import Icon from '@stremio/stremio-icons/react'; -import React, { ChangeEvent, forwardRef, memo, useCallback, useMemo, useState } from 'react'; +import React, { ChangeEvent, forwardRef, memo, useCallback, useState } from 'react'; import { type KeyboardEvent, type InputHTMLAttributes } from 'react'; import classnames from 'classnames'; import styles from './NumberInput.less'; @@ -25,7 +25,7 @@ type Props = InputHTMLAttributes & { const NumberInput = forwardRef(({ defaultValue = 0, showButtons, onKeyDown, onSubmit, min, max, onChange, ...props }, ref) => { const [value, setValue] = useState(defaultValue); const displayValue = props.value ?? value; - + const handleKeyDown = useCallback((event: KeyboardEvent) => { onKeyDown && onKeyDown(event); @@ -51,15 +51,15 @@ const NumberInput = forwardRef(({ defaultValue = 0, sho const clampValueToRange = (value: number): number => { const minValue = min ?? 0; - + if (value < minValue) { return minValue; } - + if (max !== undefined && value > max) { return max; } - + return value; }; From b2f5fb74c8fddcf3c73b0aa0d5c4b88233521f7c Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 31 Mar 2025 13:51:31 +0300 Subject: [PATCH 35/40] refactor(EpisodePicker): simplify --- .../StreamsList/EpisodePicker/EpisodePicker.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index 24d12229f..e89674cbe 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -35,16 +35,15 @@ const EpisodePicker = ({ className, onSubmit }: Props) => { setEpisode(parseInt(event.target.value)); }, []); - const handleSubmit = useCallback(() => { - if (typeof onSubmit === 'function' && !isNaN(season) && !isNaN(episode)) { - onSubmit(season, episode); - } - }, [onSubmit, season, episode]); - const disabled = useMemo(() => { return season === initialSeason && episode === initialEpisode; }, [season, episode, initialSeason, initialEpisode]); + + const handleSubmit = () => { + onSubmit(season, episode); + }; + return (
From 7915424fa4abd2b0fdfeefed5086a6de0e539b0a Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 31 Mar 2025 13:53:38 +0300 Subject: [PATCH 36/40] fix(EpisodePicker): lint --- .../MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index e89674cbe..f144c2755 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -39,7 +39,6 @@ const EpisodePicker = ({ className, onSubmit }: Props) => { return season === initialSeason && episode === initialEpisode; }, [season, episode, initialSeason, initialEpisode]); - const handleSubmit = () => { onSubmit(season, episode); }; From 4eca979d97a23736b7d41cae7a315ff53096191a Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 1 Apr 2025 11:57:37 +0300 Subject: [PATCH 37/40] refactor(EpisodePicker): simplify --- .../EpisodePicker/EpisodePicker.tsx | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx index f144c2755..256c827a9 100644 --- a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx +++ b/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx @@ -35,19 +35,33 @@ const EpisodePicker = ({ className, onSubmit }: Props) => { setEpisode(parseInt(event.target.value)); }, []); - const disabled = useMemo(() => { - return season === initialSeason && episode === initialEpisode; - }, [season, episode, initialSeason, initialEpisode]); - const handleSubmit = () => { onSubmit(season, episode); }; + const disabled = season === initialSeason && episode === initialEpisode; + return (
- - -
From e156c27b64949e78858516cc936c42ef82a18f36 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 1 Apr 2025 11:57:45 +0300 Subject: [PATCH 38/40] refactor(NumberInput): simplify --- src/components/NumberInput/NumberInput.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index 7094aca51..a286decf4 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -27,10 +27,10 @@ const NumberInput = forwardRef(({ defaultValue = 0, sho const displayValue = props.value ?? value; const handleKeyDown = useCallback((event: KeyboardEvent) => { - onKeyDown && onKeyDown(event); + onKeyDown?.(event); if (event.key === 'Enter') { - onSubmit && onSubmit(event); + onSubmit?.(event); } }, [onKeyDown, onSubmit]); @@ -63,9 +63,9 @@ const NumberInput = forwardRef(({ defaultValue = 0, sho return value; }; - const handleInputChange = ({ target: { valueAsNumber }}: ChangeEvent) => { + const handleInputChange = useCallback(({ target: { valueAsNumber }}: ChangeEvent) => { handleValueChange(clampValueToRange(valueAsNumber || 0)); - }; + }, []); return (
From 639d5f8d1c0bcb7678b7413675efcc22a20b0085 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 2 Apr 2025 14:25:41 +0300 Subject: [PATCH 39/40] refactor(EpisodePicker): moved EpisodePicker to higher level in MetaDetails folder --- .../{StreamsList => }/EpisodePicker/EpisodePicker.less | 0 .../{StreamsList => }/EpisodePicker/EpisodePicker.tsx | 0 src/routes/MetaDetails/{StreamsList => }/EpisodePicker/index.ts | 0 src/routes/MetaDetails/StreamsList/StreamsList.js | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename src/routes/MetaDetails/{StreamsList => }/EpisodePicker/EpisodePicker.less (100%) rename src/routes/MetaDetails/{StreamsList => }/EpisodePicker/EpisodePicker.tsx (100%) rename src/routes/MetaDetails/{StreamsList => }/EpisodePicker/index.ts (100%) diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less b/src/routes/MetaDetails/EpisodePicker/EpisodePicker.less similarity index 100% rename from src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.less rename to src/routes/MetaDetails/EpisodePicker/EpisodePicker.less diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx b/src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx similarity index 100% rename from src/routes/MetaDetails/StreamsList/EpisodePicker/EpisodePicker.tsx rename to src/routes/MetaDetails/EpisodePicker/EpisodePicker.tsx diff --git a/src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts b/src/routes/MetaDetails/EpisodePicker/index.ts similarity index 100% rename from src/routes/MetaDetails/StreamsList/EpisodePicker/index.ts rename to src/routes/MetaDetails/EpisodePicker/index.ts diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 2d44d35fc..82ba57a51 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -10,7 +10,7 @@ const { useServices } = require('stremio/services'); const Stream = require('./Stream'); const styles = require('./styles'); const { usePlatform, useProfile } = require('stremio/common'); -const { default: SeasonEpisodePicker } = require('./EpisodePicker'); +const { default: SeasonEpisodePicker } = require('../EpisodePicker'); const ALL_ADDONS_KEY = 'ALL'; From 7c932a93e57a0eb3bbf99b9f8c0f4c08fc18d757 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 2 Apr 2025 14:35:04 +0300 Subject: [PATCH 40/40] feat(MetaDetails): added season picker when no metadetails loaded in videos list --- src/routes/MetaDetails/VideosList/VideosList.js | 11 +++++++++++ src/routes/MetaDetails/VideosList/styles.less | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/routes/MetaDetails/VideosList/VideosList.js b/src/routes/MetaDetails/VideosList/VideosList.js index 58614c9d9..a47b2f517 100644 --- a/src/routes/MetaDetails/VideosList/VideosList.js +++ b/src/routes/MetaDetails/VideosList/VideosList.js @@ -7,6 +7,7 @@ const { t } = require('i18next'); const { useServices } = require('stremio/services'); const { Image, SearchBar, Toggle, Video } = require('stremio/components'); const SeasonsBar = require('./SeasonsBar'); +const { default: EpisodePicker } = require('../EpisodePicker'); const styles = require('./styles'); const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, toggleNotifications }) => { @@ -92,6 +93,15 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, }); }; + const onSeasonSearch = (value) => { + if (value) { + seasonOnSelect({ + type: 'select', + value, + }); + } + }; + return (
{ @@ -110,6 +120,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, : metaItem.content.type === 'Err' || videosForSeason.length === 0 ?
+ {'
No videos found for this meta!
diff --git a/src/routes/MetaDetails/VideosList/styles.less b/src/routes/MetaDetails/VideosList/styles.less index 9f9a7edee..22d51116c 100644 --- a/src/routes/MetaDetails/VideosList/styles.less +++ b/src/routes/MetaDetails/VideosList/styles.less @@ -13,10 +13,13 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; padding: 2rem; overflow-y: auto; + .episode-picker { + margin-bottom: 2rem; + } + .image { flex: none; width: 10rem;