1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-26 22:56:23 -04:00

Use react-query for series

This commit is contained in:
Mark McDowall
2025-11-28 19:37:17 -08:00
parent 49db4a1d76
commit 0521a6c390
91 changed files with 1961 additions and 2173 deletions
@@ -1,5 +1,4 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -11,9 +10,11 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import { setSeriesOverviewOption } from 'Store/Actions/seriesIndexActions';
import {
setSeriesOverviewOptions,
useSeriesOverviewOptions,
} from 'Series/seriesOptionsStore';
import translate from 'Utilities/String/translate';
import selectOverviewOptions from '../selectOverviewOptions';
const posterSizeOptions: EnhancedSelectInputValue<string>[] = [
{
@@ -40,11 +41,9 @@ interface SeriesIndexOverviewOptionsModalContentProps {
onModalClose(...args: unknown[]): void;
}
function SeriesIndexOverviewOptionsModalContent(
props: SeriesIndexOverviewOptionsModalContentProps
) {
const { onModalClose } = props;
function SeriesIndexOverviewOptionsModalContent({
onModalClose,
}: SeriesIndexOverviewOptionsModalContentProps) {
const {
detailedProgressBar,
size,
@@ -58,15 +57,13 @@ function SeriesIndexOverviewOptionsModalContent(
showSizeOnDisk,
showTags,
showSearchAction,
} = useSelector(selectOverviewOptions);
const dispatch = useDispatch();
} = useSeriesOverviewOptions();
const onOverviewOptionChange = useCallback(
({ name, value }: { name: string; value: unknown }) => {
dispatch(setSeriesOverviewOption({ [name]: value }));
setSeriesOverviewOptions({ [name]: value });
},
[dispatch]
[]
);
return (
@@ -1,6 +1,6 @@
import classNames from 'classnames';
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import TextTruncate from 'react-text-truncate';
import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import IconButton from 'Components/Link/IconButton';
@@ -13,13 +13,13 @@ import EditSeriesModal from 'Series/Edit/EditSeriesModal';
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
import { Statistics } from 'Series/Series';
import { useSeriesOverviewOptions } from 'Series/seriesOptionsStore';
import SeriesPoster from 'Series/SeriesPoster';
import { executeCommand } from 'Store/Actions/commandActions';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
import translate from 'Utilities/String/translate';
import createSeriesIndexItemSelector from '../createSeriesIndexItemSelector';
import selectOverviewOptions from './selectOverviewOptions';
import useSeriesIndexItem from '../useSeriesIndexItem';
import SeriesIndexOverviewInfo from './SeriesIndexOverviewInfo';
import styles from './SeriesIndexOverview.css';
@@ -56,9 +56,9 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
} = props;
const { series, qualityProfile, isRefreshingSeries, isSearchingSeries } =
useSelector(createSeriesIndexItemSelector(props.seriesId));
useSeriesIndexItem(seriesId);
const overviewOptions = useSelector(selectOverviewOptions);
const overviewOptions = useSeriesOverviewOptions();
const {
title,
@@ -1,12 +1,11 @@
import { throttle } from 'lodash';
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
import useMeasure from 'Helpers/Hooks/useMeasure';
import Series from 'Series/Series';
import { useSeriesOverviewOptions } from 'Series/seriesOptionsStore';
import dimensions from 'Styles/Variables/dimensions';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import selectOverviewOptions from './selectOverviewOptions';
import SeriesIndexOverview from './SeriesIndexOverview';
// Poster container dimensions
@@ -72,9 +71,7 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
isSmallScreen,
} = props;
const { size: posterSize, detailedProgressBar } = useSelector(
selectOverviewOptions
);
const { size: posterSize, detailedProgressBar } = useSeriesOverviewOptions();
const listRef = useRef<List>(null);
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
@@ -1,9 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectOverviewOptions = createSelector(
(state: AppState) => state.seriesIndex.overviewOptions,
(overviewOptions) => overviewOptions
);
export default selectOverviewOptions;
@@ -1,5 +1,4 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -11,8 +10,10 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import selectPosterOptions from 'Series/Index/Posters/selectPosterOptions';
import { setSeriesPosterOption } from 'Store/Actions/seriesIndexActions';
import {
setSeriesPosterOptions,
useSeriesPosterOptions,
} from 'Series/seriesOptionsStore';
import translate from 'Utilities/String/translate';
const posterSizeOptions: EnhancedSelectInputValue<string>[] = [
@@ -40,13 +41,9 @@ interface SeriesIndexPosterOptionsModalContentProps {
onModalClose(...args: unknown[]): unknown;
}
function SeriesIndexPosterOptionsModalContent(
props: SeriesIndexPosterOptionsModalContentProps
) {
const { onModalClose } = props;
const posterOptions = useSelector(selectPosterOptions);
function SeriesIndexPosterOptionsModalContent({
onModalClose,
}: SeriesIndexPosterOptionsModalContentProps) {
const {
detailedProgressBar,
size,
@@ -55,15 +52,13 @@ function SeriesIndexPosterOptionsModalContent(
showQualityProfile,
showTags,
showSearchAction,
} = posterOptions;
const dispatch = useDispatch();
} = useSeriesPosterOptions();
const onPosterOptionChange = useCallback(
({ name, value }: { name: string; value: unknown }) => {
dispatch(setSeriesPosterOption({ [name]: value }));
setSeriesPosterOptions({ [name]: value });
},
[dispatch]
[]
);
return (
@@ -13,14 +13,14 @@ import EditSeriesModal from 'Series/Edit/EditSeriesModal';
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
import { Statistics } from 'Series/Series';
import { useSeriesPosterOptions } from 'Series/seriesOptionsStore';
import SeriesPoster from 'Series/SeriesPoster';
import { executeCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import createSeriesIndexItemSelector from '../createSeriesIndexItemSelector';
import selectPosterOptions from './selectPosterOptions';
import useSeriesIndexItem from '../useSeriesIndexItem';
import SeriesIndexPosterInfo from './SeriesIndexPosterInfo';
import styles from './SeriesIndexPoster.css';
@@ -36,7 +36,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
const { seriesId, sortKey, isSelectMode, posterWidth, posterHeight } = props;
const { series, qualityProfile, isRefreshingSeries, isSearchingSeries } =
useSelector(createSeriesIndexItemSelector(props.seriesId));
useSeriesIndexItem(seriesId);
const {
detailedProgressBar,
@@ -45,7 +45,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
showQualityProfile,
showTags,
showSearchAction,
} = useSelector(selectPosterOptions);
} = useSeriesPosterOptions();
const { showRelativeDates, shortDateFormat, longDateFormat, timeFormat } =
useSelector(createUISettingsSelector());
@@ -1,13 +1,11 @@
import { throttle } from 'lodash';
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { FixedSizeGrid as Grid, GridChildComponentProps } from 'react-window';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import useMeasure from 'Helpers/Hooks/useMeasure';
import { SortDirection } from 'Helpers/Props/sortDirections';
import SeriesIndexPoster from 'Series/Index/Posters/SeriesIndexPoster';
import Series from 'Series/Series';
import { useSeriesPosterOptions } from 'Series/seriesOptionsStore';
import dimensions from 'Styles/Variables/dimensions';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
@@ -51,15 +49,6 @@ interface SeriesIndexPostersProps {
isSmallScreen: boolean;
}
const seriesIndexSelector = createSelector(
(state: AppState) => state.seriesIndex.posterOptions,
(posterOptions) => {
return {
posterOptions,
};
}
);
function Cell({
columnIndex,
rowIndex,
@@ -98,17 +87,22 @@ function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
}
export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
export default function SeriesIndexPosters({
scrollerRef,
items,
sortKey,
jumpToCharacter,
isSelectMode,
isSmallScreen,
}: SeriesIndexPostersProps) {
const {
scrollerRef,
items,
sortKey,
jumpToCharacter,
isSelectMode,
isSmallScreen,
} = props;
const { posterOptions } = useSelector(seriesIndexSelector);
detailedProgressBar,
showTitle,
showMonitored,
showQualityProfile,
showTags,
size: posterSize,
} = useSeriesPosterOptions();
const ref = useRef<Grid>(null);
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
@@ -120,30 +114,18 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
const remainder = width % maximumColumnWidth;
return remainder === 0
? maximumColumnWidth
: Math.floor(
width / (columns + ADDITIONAL_COLUMN_COUNT[posterOptions.size])
);
}, [isSmallScreen, posterOptions, size]);
: Math.floor(width / (columns + ADDITIONAL_COLUMN_COUNT[posterSize]));
}, [isSmallScreen, posterSize, size]);
const columnCount = useMemo(
() => Math.max(Math.floor(size.width / columnWidth), 1),
[size, columnWidth]
);
const padding = props.isSmallScreen
? columnPaddingSmallScreen
: columnPadding;
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
const posterWidth = columnWidth - padding * 2;
const posterHeight = Math.ceil((250 / 170) * posterWidth);
const rowHeight = useMemo(() => {
const {
detailedProgressBar,
showTitle,
showMonitored,
showQualityProfile,
showTags,
} = posterOptions;
const nextAiringHeight = 19;
const heights = [
@@ -193,7 +175,16 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
}
return heights.reduce((acc, height) => acc + height, 0);
}, [isSmallScreen, posterOptions, sortKey, posterHeight]);
}, [
isSmallScreen,
detailedProgressBar,
showTitle,
showMonitored,
showQualityProfile,
showTags,
sortKey,
posterHeight,
]);
useEffect(() => {
const current = scrollerRef.current;
@@ -1,9 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectPosterOptions = createSelector(
(state: AppState) => state.seriesIndex.posterOptions,
(posterOptions) => posterOptions
);
export default selectPosterOptions;
@@ -1,9 +1,6 @@
import { orderBy } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { useSelect } from 'App/Select/SelectContext';
import AppState from 'App/State/AppState';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
@@ -14,8 +11,11 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import Series from 'Series/Series';
import { bulkDeleteSeries, setDeleteOption } from 'Store/Actions/seriesActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import {
setSeriesDeleteOptions,
useSeriesDeleteOptions,
} from 'Series/seriesOptionsStore';
import useSeries, { useBulkDeleteSeries } from 'Series/useSeries';
import { InputChanged } from 'typings/inputs';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
@@ -25,17 +25,12 @@ export interface DeleteSeriesModalContentProps {
onModalClose(): void;
}
const selectDeleteOptions = createSelector(
(state: AppState) => state.series.deleteOptions,
(deleteOptions) => deleteOptions
);
function DeleteSeriesModalContent({
onModalClose,
}: DeleteSeriesModalContentProps) {
const { addImportListExclusion } = useSelector(selectDeleteOptions);
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const dispatch = useDispatch();
const { addImportListExclusion } = useSeriesDeleteOptions();
const { data: allSeries } = useSeries();
const { bulkDeleteSeries } = useBulkDeleteSeries();
const [deleteFiles, setDeleteFiles] = useState(false);
const { useSelectedIds } = useSelect<Series>();
const seriesIds = useSelectedIds();
@@ -57,25 +52,21 @@ function DeleteSeriesModalContent({
const onDeleteOptionChange = useCallback(
({ name, value }: { name: string; value: boolean }) => {
dispatch(
setDeleteOption({
[name]: value,
})
);
setSeriesDeleteOptions({
[name]: value,
});
},
[dispatch]
[]
);
const onDeleteSeriesConfirmed = useCallback(() => {
setDeleteFiles(false);
dispatch(
bulkDeleteSeries({
seriesIds,
deleteFiles,
addImportListExclusion,
})
);
bulkDeleteSeries({
seriesIds,
deleteFiles,
addImportListExclusion,
});
onModalClose();
}, [
@@ -83,7 +74,7 @@ function DeleteSeriesModalContent({
addImportListExclusion,
setDeleteFiles,
seriesIds,
dispatch,
bulkDeleteSeries,
onModalClose,
]);
@@ -1,6 +1,6 @@
import { orderBy } from 'lodash';
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import { RENAME_SERIES } from 'Commands/commandNames';
import Alert from 'Components/Alert';
@@ -12,8 +12,8 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, kinds } from 'Helpers/Props';
import Series from 'Series/Series';
import useSeries from 'Series/useSeries';
import { executeCommand } from 'Store/Actions/commandActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import translate from 'Utilities/String/translate';
import styles from './OrganizeSeriesModalContent.css';
@@ -24,7 +24,7 @@ export interface OrganizeSeriesModalContentProps {
function OrganizeSeriesModalContent({
onModalClose,
}: OrganizeSeriesModalContentProps) {
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const { data: allSeries } = useSeries();
const dispatch = useDispatch();
const { useSelectedIds } = useSelect<Series>();
const seriesIds = useSelectedIds();
@@ -19,12 +19,7 @@ function SeasonDetails(props: SeasonDetailsProps) {
return (
<div className={styles.seasons}>
{latestSeasons.map((season) => {
const {
seasonNumber,
monitored,
statistics,
isSaving = false,
} = season;
const { seasonNumber, monitored, statistics } = season;
return (
<SeasonPassSeason
@@ -33,7 +28,6 @@ function SeasonDetails(props: SeasonDetailsProps) {
seasonNumber={seasonNumber}
monitored={monitored}
statistics={statistics}
isSaving={isSaving}
/>
);
})}
@@ -1,10 +1,9 @@
import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import formatSeason from 'Season/formatSeason';
import { Statistics } from 'Series/Series';
import { toggleSeasonMonitored } from 'Store/Actions/seriesActions';
import { useToggleSeasonMonitored } from 'Series/useSeries';
import translate from 'Utilities/String/translate';
import styles from './SeasonPassSeason.css';
@@ -13,7 +12,6 @@ interface SeasonPassSeasonProps {
seasonNumber: number;
monitored: boolean;
statistics: Statistics;
isSaving: boolean;
}
function SeasonPassSeason(props: SeasonPassSeasonProps) {
@@ -26,24 +24,22 @@ function SeasonPassSeason(props: SeasonPassSeasonProps) {
totalEpisodeCount: 0,
percentOfEpisodes: 0,
},
isSaving = false,
} = props;
const { episodeFileCount, totalEpisodeCount, percentOfEpisodes } = statistics;
const dispatch = useDispatch();
const { toggleSeasonMonitored, isTogglingSeasonMonitored } =
useToggleSeasonMonitored(seriesId);
const onSeasonMonitoredPress = useCallback(() => {
dispatch(
toggleSeasonMonitored({ seriesId, seasonNumber, monitored: !monitored })
);
}, [seriesId, seasonNumber, monitored, dispatch]);
toggleSeasonMonitored({ seasonNumber, monitored: !monitored });
}, [seasonNumber, monitored, toggleSeasonMonitored]);
return (
<div className={styles.season}>
<div className={styles.info}>
<MonitorToggleButton
monitored={monitored}
isSaving={isSaving}
isSaving={isTogglingSeasonMonitored}
onPress={onSeasonMonitoredPress}
/>
@@ -1,8 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { useSelector } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import AppState from 'App/State/AppState';
import { RENAME_SERIES } from 'Commands/commandNames';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
@@ -10,9 +8,10 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
import { kinds } from 'Helpers/Props';
import Series from 'Series/Series';
import {
saveSeriesEditor,
updateSeriesMonitor,
} from 'Store/Actions/seriesActions';
useBulkDeleteSeries,
useSaveSeriesEditor,
useUpdateSeriesMonitor,
} from 'Series/useSeries';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import translate from 'Utilities/String/translate';
import DeleteSeriesModal from './Delete/DeleteSeriesModal';
@@ -31,28 +30,19 @@ interface SavePayload {
moveFiles?: boolean;
}
const seriesEditorSelector = createSelector(
(state: AppState) => state.series,
(series) => {
const { isSaving, isDeleting, deleteError } = series;
return {
isSaving,
isDeleting,
deleteError,
};
}
);
function SeriesIndexSelectFooter() {
const { isSaving, isDeleting, deleteError } =
useSelector(seriesEditorSelector);
const { saveSeriesEditor, isSavingSeriesEditor } = useSaveSeriesEditor();
const { updateSeriesMonitor, isUpdatingSeriesMonitor } =
useUpdateSeriesMonitor();
const { isBulkDeleting, bulkDeleteError } = useBulkDeleteSeries();
const isOrganizingSeries = useSelector(
createCommandExecutingSelector(RENAME_SERIES)
);
const dispatch = useDispatch();
const isSaving = isSavingSeriesEditor || isUpdatingSeriesMonitor;
const isDeleting = isBulkDeleting;
const deleteError = bulkDeleteError;
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isOrganizeModalOpen, setIsOrganizeModalOpen] = useState(false);
@@ -79,14 +69,12 @@ function SeriesIndexSelectFooter() {
setIsSavingSeries(true);
setIsEditModalOpen(false);
dispatch(
saveSeriesEditor({
...payload,
seriesIds,
})
);
saveSeriesEditor({
...payload,
seriesIds,
});
},
[seriesIds, dispatch]
[seriesIds, saveSeriesEditor]
);
const onOrganizePress = useCallback(() => {
@@ -106,19 +94,16 @@ function SeriesIndexSelectFooter() {
}, [setIsTagsModalOpen]);
const onApplyTagsPress = useCallback(
(tags: number[], applyTags: string) => {
(tags: number[], _applyTags: string) => {
setIsSavingTags(true);
setIsTagsModalOpen(false);
dispatch(
saveSeriesEditor({
seriesIds,
tags,
applyTags,
})
);
saveSeriesEditor({
seriesIds,
tags,
});
},
[seriesIds, dispatch]
[seriesIds, saveSeriesEditor]
);
const onMonitoringPress = useCallback(() => {
@@ -134,14 +119,12 @@ function SeriesIndexSelectFooter() {
setIsSavingMonitoring(true);
setIsMonitoringModalOpen(false);
dispatch(
updateSeriesMonitor({
seriesIds,
monitor,
})
);
updateSeriesMonitor({
series: seriesIds.map((id) => ({ id })),
monitoringOptions: { monitor },
});
},
[seriesIds, dispatch]
[seriesIds, updateSeriesMonitor]
);
const onDeletePress = useCallback(() => {
@@ -1,6 +1,5 @@
import { uniq } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -15,7 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import { useMultipleSeries } from 'Series/useSeries';
import { Tag, useTagList } from 'Tags/useTags';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
@@ -29,27 +28,24 @@ function TagsModalContent({
onModalClose,
onApplyTagsPress,
}: TagsModalContentProps) {
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const tagList: Tag[] = useTagList();
const [tags, setTags] = useState<number[]>([]);
const [applyTags, setApplyTags] = useState('add');
const { useSelectedIds } = useSelect<Series>();
const seriesIds = useSelectedIds();
const selectedSeries = useMultipleSeries(seriesIds);
const seriesTags = useMemo(() => {
const tags = seriesIds.reduce((acc: number[], id) => {
const s = allSeries.find((s) => s.id === id);
if (s) {
acc.push(...s.tags);
const tags = selectedSeries.reduce((acc: number[], series) => {
if (series) {
acc.push(...series.tags);
}
return acc;
}, []);
return uniq(tags);
}, [allSeries, seriesIds]);
}, [selectedSeries]);
const onTagsChange = useCallback(
({ value }: { value: number[] }) => {
+35 -55
View File
@@ -1,15 +1,7 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import QueueDetailsProvider from 'Activity/Queue/Details/QueueDetailsProvider';
import { SelectProvider } from 'App/Select/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import SeriesAppState, { SeriesIndexAppState } from 'App/State/SeriesAppState';
import { RSS_SYNC } from 'Commands/commandNames';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -27,18 +19,17 @@ import { align, icons, kinds } from 'Helpers/Props';
import { DESCENDING } from 'Helpers/Props/sortDirections';
import ParseToolbarButton from 'Parse/ParseToolbarButton';
import NoSeries from 'Series/NoSeries';
import { executeCommand } from 'Store/Actions/commandActions';
import { fetchSeries } from 'Store/Actions/seriesActions';
import {
setSeriesFilter,
setSeriesOption,
setSeriesSort,
setSeriesTableOption,
setSeriesView,
} from 'Store/Actions/seriesIndexActions';
setSeriesTableOptions,
useSeriesOptions,
} from 'Series/seriesOptionsStore';
import { FILTERS, useSeriesIndex } from 'Series/useSeries';
import { executeCommand } from 'Store/Actions/commandActions';
import scrollPositions from 'Store/scrollPositions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSeriesClientSideCollectionItemsSelector from 'Store/Selectors/createSeriesClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
import SeriesIndexFilterMenu from './Menus/SeriesIndexFilterMenu';
import SeriesIndexSortMenu from './Menus/SeriesIndexSortMenu';
@@ -76,19 +67,16 @@ interface SeriesIndexProps {
const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
const {
isFetching,
isPopulated,
error,
isLoading: isFetching,
isFetched,
isError: error,
data,
totalItems,
items,
columns,
selectedFilterKey,
filters,
sortKey,
sortDirection,
view,
}: SeriesAppState & SeriesIndexAppState & ClientSideCollectionAppState =
useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex'));
} = useSeriesIndex();
const { selectedFilterKey, sortKey, sortDirection, view, columns } =
useSeriesOptions();
const filters = FILTERS;
const customFilters = useCustomFiltersList('series');
@@ -104,10 +92,6 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
);
const [isSelectMode, setIsSelectMode] = useState(false);
useEffect(() => {
dispatch(fetchSeries());
}, [dispatch]);
const onRssSyncPress = useCallback(() => {
dispatch(
executeCommand({
@@ -120,37 +104,33 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
setIsSelectMode(!isSelectMode);
}, [isSelectMode, setIsSelectMode]);
const onTableOptionChange = useCallback(
(payload: unknown) => {
dispatch(setSeriesTableOption(payload));
},
[dispatch]
);
const onTableOptionChange = useCallback((payload: unknown) => {
setSeriesTableOptions(
payload as Partial<{ showBanners: boolean; showSearchAction: boolean }>
);
}, []);
const onViewSelect = useCallback(
(value: string) => {
dispatch(setSeriesView({ view: value }));
setSeriesOption('view', value);
if (scrollerRef.current) {
scrollerRef.current.scrollTo(0, 0);
}
},
[scrollerRef, dispatch]
[scrollerRef]
);
const onSortSelect = useCallback(
(value: string) => {
dispatch(setSeriesSort({ sortKey: value }));
setSeriesSort({ sortKey: value, sortDirection });
},
[dispatch]
[sortDirection]
);
const onFilterSelect = useCallback(
(value: string | number) => {
dispatch(setSeriesFilter({ selectedFilterKey: value }));
},
[dispatch]
);
const onFilterSelect = useCallback((value: string | number) => {
setSeriesOption('selectedFilterKey', value);
}, []);
const onOptionsPress = useCallback(() => {
setIsOptionsModalOpen(true);
@@ -184,7 +164,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
};
}
const characters = items.reduce((acc: Record<string, number>, item) => {
const characters = data.reduce((acc: Record<string, number>, item) => {
let char = item.sortTitle.charAt(0);
if (!isNaN(Number(char))) {
@@ -211,15 +191,15 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
characters,
order,
};
}, [items, sortKey, sortDirection]);
}, [data, sortKey, sortDirection]);
const ViewComponent = useMemo(() => getViewComponent(view), [view]);
const isLoaded = !!(!error && isPopulated && items.length);
const isLoaded = !!(!error && isFetched && data.length);
const hasNoSeries = !totalItems;
return (
<QueueDetailsProvider all={true}>
<SelectProvider items={items}>
<SelectProvider items={data}>
<PageContent>
<PageToolbar>
<PageToolbarSection>
@@ -318,7 +298,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
initialScrollTop={props.initialScrollTop}
onScroll={onScroll}
>
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{isFetching && !isFetched ? <LoadingIndicator /> : null}
{!isFetching && !!error ? (
<Alert kind={kinds.DANGER}>
@@ -330,7 +310,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
<div className={styles.contentBodyContainer}>
<ViewComponent
scrollerRef={scrollerRef}
items={items}
items={data}
sortKey={sortKey}
sortDirection={sortDirection}
jumpToCharacter={jumpToCharacter}
@@ -342,7 +322,7 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
</div>
) : null}
{!error && isPopulated && !items.length ? (
{!error && isFetched && !data.length ? (
<NoSeries totalItems={totalItems} />
) : null}
</PageContentBody>
@@ -1,51 +1,28 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal, { FilterModalProps } from 'Components/Filter/FilterModal';
import Series from 'Series/Series';
import { setSeriesFilter } from 'Store/Actions/seriesIndexActions';
function createSeriesSelector() {
return createSelector(
(state: AppState) => state.series.items,
(series) => {
return series;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.seriesIndex.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
import { setSeriesOption } from 'Series/seriesOptionsStore';
import useSeries, { FILTER_BUILDER } from 'Series/useSeries';
type SeriesIndexFilterModalProps = FilterModalProps<Series>;
export default function SeriesIndexFilterModal(
props: SeriesIndexFilterModalProps
) {
const sectionItems = useSelector(createSeriesSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const dispatch = useDispatch();
const { data: sectionItems } = useSeries();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setSeriesFilter(payload));
({ selectedFilterKey }: { selectedFilterKey: string | number }) => {
setSeriesOption('selectedFilterKey', selectedFilterKey);
},
[dispatch]
[]
);
return (
<FilterModal
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
filterBuilderProps={FILTER_BUILDER}
customFilterType="series"
dispatchSetFilter={dispatchSetFilter}
/>
@@ -1,43 +1,15 @@
import classNames from 'classnames';
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import SeriesAppState from 'App/State/SeriesAppState';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
import useSeries from 'Series/useSeries';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './SeriesIndexFooter.css';
function createUnoptimizedSelector() {
return createSelector(
createClientSideCollectionSelector('series', 'seriesIndex'),
(series: SeriesAppState) => {
return series.items.map((s) => {
const { monitored, status, statistics } = s;
return {
monitored,
status,
statistics,
};
});
}
);
}
function createSeriesSelector() {
return createDeepEqualSelector(
createUnoptimizedSelector(),
(series) => series
);
}
export default function SeriesIndexFooter() {
const series = useSelector(createSeriesSelector());
const { data: series } = useSeries();
const count = series.length;
let episodes = 0;
let episodeFiles = 0;
@@ -1,19 +1,17 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import SeriesAppState, { SeriesIndexAppState } from 'App/State/SeriesAppState';
import { REFRESH_SERIES } from 'Commands/commandNames';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import { useSeriesIndex } from 'Series/useSeries';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createSeriesClientSideCollectionItemsSelector from 'Store/Selectors/createSeriesClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
interface SeriesIndexRefreshSeriesButtonProps {
isSelectMode: boolean;
selectedFilterKey: string;
selectedFilterKey: string | number;
}
function SeriesIndexRefreshSeriesButton(
@@ -22,11 +20,7 @@ function SeriesIndexRefreshSeriesButton(
const isRefreshing = useSelector(
createCommandExecutingSelector(REFRESH_SERIES)
);
const {
items,
totalItems,
}: SeriesAppState & SeriesIndexAppState & ClientSideCollectionAppState =
useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex'));
const { data, totalItems } = useSeriesIndex();
const dispatch = useDispatch();
const { isSelectMode, selectedFilterKey } = props;
@@ -42,7 +36,7 @@ function SeriesIndexRefreshSeriesButton(
const onPress = useCallback(() => {
const seriesToRefresh =
isSelectMode && anySelected ? getSelectedIds() : items.map((m) => m.id);
isSelectMode && anySelected ? getSelectedIds() : data.map((m) => m.id);
dispatch(
executeCommand({
@@ -50,7 +44,7 @@ function SeriesIndexRefreshSeriesButton(
seriesIds: seriesToRefresh,
})
);
}, [dispatch, anySelected, isSelectMode, items, getSelectedIds]);
}, [dispatch, anySelected, isSelectMode, data, getSelectedIds]);
return (
<PageToolbarButton
@@ -1,6 +1,6 @@
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import CheckInput from 'Components/Form/CheckInput';
@@ -16,9 +16,9 @@ import Column from 'Components/Table/Column';
import { icons } from 'Helpers/Props';
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModal from 'Series/Edit/EditSeriesModal';
import createSeriesIndexItemSelector from 'Series/Index/createSeriesIndexItemSelector';
import { Statistics } from 'Series/Series';
import SeriesBanner from 'Series/SeriesBanner';
import { useSeriesTableOptions } from 'Series/seriesOptionsStore';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import { executeCommand } from 'Store/Actions/commandActions';
import { SelectStateInputProps } from 'typings/props';
@@ -26,9 +26,9 @@ import formatBytes from 'Utilities/Number/formatBytes';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import SeriesIndexProgressBar from '../ProgressBar/SeriesIndexProgressBar';
import useSeriesIndexItem from '../useSeriesIndexItem';
import hasGrowableColumns from './hasGrowableColumns';
import SeasonsCell from './SeasonsCell';
import selectTableOptions from './selectTableOptions';
import SeriesStatusCell from './SeriesStatusCell';
import styles from './SeriesIndexRow.css';
@@ -48,9 +48,9 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
latestSeason,
isRefreshingSeries,
isSearchingSeries,
} = useSelector(createSeriesIndexItemSelector(props.seriesId));
} = useSeriesIndexItem(seriesId);
const { showBanners, showSearchAction } = useSelector(selectTableOptions);
const { showBanners, showSearchAction } = useSeriesTableOptions();
const {
title,
@@ -75,7 +75,6 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
ratings,
seasons = [],
tags = [],
isSaving = false,
} = series;
const {
@@ -178,7 +177,6 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
monitored={monitored}
status={status}
isSelectMode={isSelectMode}
isSaving={isSaving}
component={VirtualTableRowCell}
/>
);
@@ -1,14 +1,14 @@
import React, { RefObject, useEffect, useMemo, useRef } from 'react';
import { useSelector } from 'react-redux';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import Column from 'Components/Table/Column';
import VirtualTable from 'Components/Table/VirtualTable';
import { SortDirection } from 'Helpers/Props/sortDirections';
import Series from 'Series/Series';
import {
useSeriesOption,
useSeriesTableOptions,
} from 'Series/seriesOptionsStore';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import selectTableOptions from './selectTableOptions';
import SeriesIndexRow from './SeriesIndexRow';
import SeriesIndexTableHeader from './SeriesIndexTableHeader';
import styles from './SeriesIndexTable.css';
@@ -31,11 +31,6 @@ interface SeriesIndexTableProps {
isSmallScreen: boolean;
}
const columnsSelector = createSelector(
(state: AppState) => state.seriesIndex.columns,
(columns) => columns
);
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
const { items, sortKey, columns, isSelectMode } = data;
@@ -64,19 +59,17 @@ function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
);
}
function SeriesIndexTable(props: SeriesIndexTableProps) {
const {
items,
sortKey,
sortDirection,
jumpToCharacter,
isSelectMode,
isSmallScreen,
scrollerRef,
} = props;
const columns = useSelector(columnsSelector);
const { showBanners } = useSelector(selectTableOptions);
function SeriesIndexTable({
items,
sortKey,
sortDirection,
jumpToCharacter,
isSelectMode,
isSmallScreen,
scrollerRef,
}: SeriesIndexTableProps) {
const columns = useSeriesOption('columns');
const { showBanners } = useSeriesTableOptions();
const listRef = useRef<FixedSizeList<RowItemData>>(null);
const rowHeight = useMemo(() => {
@@ -1,6 +1,5 @@
import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useSelect } from 'App/Select/SelectContext';
import IconButton from 'Components/Link/IconButton';
import Column from 'Components/Table/Column';
@@ -12,9 +11,10 @@ import { icons } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import {
setSeriesSort,
setSeriesTableOption,
} from 'Store/Actions/seriesIndexActions';
setSeriesTableOptions,
} from 'Series/seriesOptionsStore';
import { CheckInputChanged } from 'typings/inputs';
import { TableOptionsChangePayload } from 'typings/Table';
import hasGrowableColumns from './hasGrowableColumns';
import SeriesIndexTableOptions from './SeriesIndexTableOptions';
import styles from './SeriesIndexTableHeader.css';
@@ -29,21 +29,29 @@ interface SeriesIndexTableHeaderProps {
function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
const { showBanners, columns, sortKey, sortDirection, isSelectMode } = props;
const dispatch = useDispatch();
const { allSelected, allUnselected, selectAll, unselectAll } = useSelect();
const onSortPress = useCallback(
(value: string) => {
dispatch(setSeriesSort({ sortKey: value }));
(sortKey: string, sortDirection?: SortDirection) => {
setSeriesSort({ sortKey, sortDirection });
},
[dispatch]
[]
);
const onTableOptionChange = useCallback(
(payload: unknown) => {
dispatch(setSeriesTableOption(payload));
(
payload: TableOptionsChangePayload & {
tableOptions?: { showBanners?: boolean; showSearchAction?: boolean };
}
) => {
if (payload.tableOptions) {
setSeriesTableOptions(payload.tableOptions);
} else {
// Handle standard table options like columns - for now just ignore
// as series table only uses the tableOptions property
}
},
[dispatch]
[]
);
const onSelectAllChange = useCallback(
@@ -1,34 +1,25 @@
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import {
setSeriesTableOptions,
useSeriesTableOptions,
} from 'Series/seriesOptionsStore';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import selectTableOptions from './selectTableOptions';
interface SeriesIndexTableOptionsProps {
onTableOptionChange(...args: unknown[]): unknown;
}
function SeriesIndexTableOptions() {
const { showBanners, showSearchAction } = useSeriesTableOptions();
function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
const { onTableOptionChange } = props;
const tableOptions = useSelector(selectTableOptions);
const { showBanners, showSearchAction } = tableOptions;
const onTableOptionChangeWrapper = useCallback(
const handleTableOptionChange = useCallback(
({ name, value }: InputChanged<boolean>) => {
onTableOptionChange({
tableOptions: {
...tableOptions,
[name]: value,
},
setSeriesTableOptions({
[name]: value,
});
},
[tableOptions, onTableOptionChange]
[]
);
return (
@@ -41,7 +32,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
name="showBanners"
value={showBanners}
helpText={translate('ShowBannersHelpText')}
onChange={onTableOptionChangeWrapper}
onChange={handleTableOptionChange}
/>
</FormGroup>
@@ -53,7 +44,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
name="showSearchAction"
value={showSearchAction}
helpText={translate('ShowSearchHelpText')}
onChange={onTableOptionChangeWrapper}
onChange={handleTableOptionChange}
/>
</FormGroup>
</>
@@ -1,12 +1,11 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import { SeriesStatus } from 'Series/Series';
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import { toggleSeriesMonitored } from 'Store/Actions/seriesActions';
import { useToggleSeriesMonitored } from 'Series/useSeries';
import translate from 'Utilities/String/translate';
import styles from './SeriesStatusCell.css';
@@ -16,28 +15,25 @@ interface SeriesStatusCellProps {
monitored: boolean;
status: SeriesStatus;
isSelectMode: boolean;
isSaving: boolean;
component?: React.ElementType;
}
function SeriesStatusCell(props: SeriesStatusCellProps) {
const {
className,
seriesId,
monitored,
status,
isSelectMode,
isSaving,
component: Component = VirtualTableRowCell,
...otherProps
} = props;
function SeriesStatusCell({
className,
seriesId,
monitored,
status,
isSelectMode,
component: Component = VirtualTableRowCell,
...otherProps
}: SeriesStatusCellProps) {
const statusDetails = getSeriesStatusDetails(status);
const dispatch = useDispatch();
const { toggleSeriesMonitored, isTogglingSeriesMonitored } =
useToggleSeriesMonitored(seriesId);
const onMonitoredPress = useCallback(() => {
dispatch(toggleSeriesMonitored({ seriesId, monitored: !monitored }));
}, [seriesId, monitored, dispatch]);
toggleSeriesMonitored({ monitored: !monitored });
}, [monitored, toggleSeriesMonitored]);
return (
<Component className={className} {...otherProps}>
@@ -45,7 +41,7 @@ function SeriesStatusCell(props: SeriesStatusCellProps) {
<MonitorToggleButton
className={styles.statusIcon}
monitored={monitored}
isSaving={isSaving}
isSaving={isTogglingSeriesMonitored}
onPress={onMonitoredPress}
/>
) : (
@@ -1,9 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectTableOptions = createSelector(
(state: AppState) => state.seriesIndex.tableOptions,
(tableOptions) => tableOptions
);
export default selectTableOptions;
@@ -1,45 +0,0 @@
import { maxBy } from 'lodash';
import { createSelector } from 'reselect';
import Command from 'Commands/Command';
import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import Series from 'Series/Series';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import createSeriesQualityProfileSelector from 'Store/Selectors/createSeriesQualityProfileSelector';
import { createSeriesSelectorForHook } from 'Store/Selectors/createSeriesSelector';
function createSeriesIndexItemSelector(seriesId: number) {
return createSelector(
createSeriesSelectorForHook(seriesId),
createSeriesQualityProfileSelector(seriesId),
createExecutingCommandsSelector(),
(series: Series, qualityProfile, executingCommands: Command[]) => {
const isRefreshingSeries = executingCommands.some((command) => {
return (
command.name === REFRESH_SERIES &&
command.body.seriesIds?.includes(series.id)
);
});
const isSearchingSeries = executingCommands.some((command) => {
return (
command.name === SERIES_SEARCH && command.body.seriesId === seriesId
);
});
const latestSeason = maxBy(
series.seasons,
(season) => season.seasonNumber
);
return {
series,
qualityProfile,
latestSeason,
isRefreshingSeries,
isSearchingSeries,
};
}
);
}
export default createSeriesIndexItemSelector;
@@ -0,0 +1,49 @@
import { maxBy } from 'lodash';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import Command from 'Commands/Command';
import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import { useSingleSeries } from 'Series/useSeries';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import createSeriesQualityProfileSelector from 'Store/Selectors/createSeriesQualityProfileSelector';
function useSeriesIndexItem(seriesId: number) {
const series = useSingleSeries(seriesId);
const qualityProfile = useSelector(
createSeriesQualityProfileSelector(series)
);
const executingCommands: Command[] = useSelector(
createExecutingCommandsSelector()
);
return useMemo(() => {
if (!series) {
throw new Error('Series not found');
}
const isRefreshingSeries = executingCommands.some((command) => {
return (
command.name === REFRESH_SERIES &&
command.body.seriesIds?.includes(series.id)
);
});
const isSearchingSeries = executingCommands.some((command) => {
return (
command.name === SERIES_SEARCH && command.body.seriesId === seriesId
);
});
const latestSeason = maxBy(series.seasons, (season) => season.seasonNumber);
return {
series,
qualityProfile,
latestSeason,
isRefreshingSeries,
isSearchingSeries,
};
}, [series, qualityProfile, executingCommands, seriesId]);
}
export default useSeriesIndexItem;