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:
+10
-13
@@ -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;
|
||||
+10
-15
@@ -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[] }) => {
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user