1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-25 22:46:31 -04:00

Typings cleanup and improvements

This commit is contained in:
Mark McDowall
2023-04-04 09:21:34 -07:00
parent 5326a102e2
commit b2c43fb2a6
92 changed files with 1019 additions and 346 deletions
@@ -1,10 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';
import { CustomFilter } from 'App/State/AppState';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
import SeriesIndexFilterModal from 'Series/Index/SeriesIndexFilterModal';
function SeriesIndexFilterMenu(props) {
interface SeriesIndexFilterMenuProps {
selectedFilterKey: string | number;
filters: object[];
customFilters: CustomFilter[];
isDisabled: boolean;
onFilterSelect(filterName: string): unknown;
}
function SeriesIndexFilterMenu(props: SeriesIndexFilterMenuProps) {
const {
selectedFilterKey,
filters,
@@ -26,15 +34,6 @@ function SeriesIndexFilterMenu(props) {
);
}
SeriesIndexFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
onFilterSelect: PropTypes.func.isRequired,
};
SeriesIndexFilterMenu.defaultProps = {
showCustomFilters: false,
};
@@ -1,11 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';
import MenuContent from 'Components/Menu/MenuContent';
import SortMenu from 'Components/Menu/SortMenu';
import SortMenuItem from 'Components/Menu/SortMenuItem';
import { align, sortDirections } from 'Helpers/Props';
import { align } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
function SeriesIndexSortMenu(props) {
interface SeriesIndexSortMenuProps {
sortKey?: string;
sortDirection?: SortDirection;
isDisabled: boolean;
onSortSelect(sortKey: string): unknown;
}
function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
const { sortKey, sortDirection, isDisabled, onSortSelect } = props;
return (
@@ -150,11 +157,4 @@ function SeriesIndexSortMenu(props) {
);
}
SeriesIndexSortMenu.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
isDisabled: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired,
};
export default SeriesIndexSortMenu;
@@ -1,11 +1,16 @@
import PropTypes from 'prop-types';
import React from 'react';
import MenuContent from 'Components/Menu/MenuContent';
import ViewMenu from 'Components/Menu/ViewMenu';
import ViewMenuItem from 'Components/Menu/ViewMenuItem';
import { align } from 'Helpers/Props';
function SeriesIndexViewMenu(props) {
interface SeriesIndexViewMenuProps {
view: string;
isDisabled: boolean;
onViewSelect(value: string): unknown;
}
function SeriesIndexViewMenu(props: SeriesIndexViewMenuProps) {
const { view, isDisabled, onViewSelect } = props;
return (
@@ -31,10 +36,4 @@ function SeriesIndexViewMenu(props) {
);
}
SeriesIndexViewMenu.propTypes = {
view: PropTypes.string.isRequired,
isDisabled: PropTypes.bool.isRequired,
onViewSelect: PropTypes.func.isRequired,
};
export default SeriesIndexViewMenu;
@@ -45,7 +45,7 @@ function SeriesIndexOverviewOptionsModalContent(
const dispatch = useDispatch();
const onOverviewOptionChange = useCallback(
({ name, value }) => {
({ name, value }: { name: string; value: unknown }) => {
dispatch(setSeriesOverviewOption({ [name]: value }));
},
[dispatch]
@@ -10,6 +10,7 @@ import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
import { Statistics } from 'Series/Series';
import SeriesPoster from 'Series/SeriesPoster';
import { executeCommand } from 'Store/Actions/commandActions';
import dimensions from 'Styles/Variables/dimensions';
@@ -66,7 +67,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
previousAiring,
added,
overview,
statistics = {},
statistics = {} as Statistics,
images,
network,
} = series;
@@ -1,14 +1,50 @@
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { icons } from 'Helpers/Props';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import dimensions from 'Styles/Variables/dimensions';
import { UiSettings } from 'typings/UiSettings';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import SeriesIndexOverviewInfoRow from './SeriesIndexOverviewInfoRow';
import styles from './SeriesIndexOverviewInfo.css';
interface RowProps {
name: string;
showProp: string;
valueProp: string;
}
interface RowInfoProps {
title: string;
iconName: IconDefinition;
label: string;
}
interface SeriesIndexOverviewInfoProps {
height: number;
showNetwork: boolean;
showMonitored: boolean;
showQualityProfile: boolean;
showPreviousAiring: boolean;
showAdded: boolean;
showSeasonCount: boolean;
showPath: boolean;
showSizeOnDisk: boolean;
monitored: boolean;
nextAiring?: string;
network?: string;
qualityProfile: object;
previousAiring?: string;
added?: string;
seasonCount: number;
path: string;
sizeOnDisk?: number;
sortKey: string;
}
const infoRowHeight = parseInt(dimensions.seriesIndexOverviewInfoRowHeight);
const rows = [
@@ -54,7 +90,11 @@ const rows = [
},
];
function getInfoRowProps(row, props, uiSettings) {
function getInfoRowProps(
row: RowProps,
props: SeriesIndexOverviewInfoProps,
uiSettings: UiSettings
): RowInfoProps | null {
const { name } = row;
if (name === 'monitored') {
@@ -71,7 +111,7 @@ function getInfoRowProps(row, props, uiSettings) {
return {
title: 'Network',
iconName: icons.NETWORK,
label: props.network,
label: props.network ?? '',
};
}
@@ -79,6 +119,9 @@ function getInfoRowProps(row, props, uiSettings) {
return {
title: 'Quality Profile',
iconName: icons.PROFILE,
// TODO: Type QualityProfile
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2339)
label: props.qualityProfile.name,
};
}
@@ -95,15 +138,11 @@ function getInfoRowProps(row, props, uiSettings) {
timeFormat
)}`,
iconName: icons.CALENDAR,
label: getRelativeDate(
previousAiring,
shortDateFormat,
showRelativeDates,
{
label:
getRelativeDate(previousAiring, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: true,
}
),
}) ?? '',
};
}
@@ -115,10 +154,11 @@ function getInfoRowProps(row, props, uiSettings) {
return {
title: `Added: ${formatDateTime(added, longDateFormat, timeFormat)}`,
iconName: icons.ADD,
label: getRelativeDate(added, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: true,
}),
label:
getRelativeDate(added, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: true,
}) ?? '',
};
}
@@ -154,28 +194,8 @@ function getInfoRowProps(row, props, uiSettings) {
label: formatBytes(props.sizeOnDisk),
};
}
}
interface SeriesIndexOverviewInfoProps {
height: number;
showNetwork: boolean;
showMonitored: boolean;
showQualityProfile: boolean;
showPreviousAiring: boolean;
showAdded: boolean;
showSeasonCount: boolean;
showPath: boolean;
showSizeOnDisk: boolean;
monitored: boolean;
nextAiring?: string;
network?: string;
qualityProfile: object;
previousAiring?: string;
added?: string;
seasonCount: number;
path: string;
sizeOnDisk?: number;
sortKey: string;
return null;
}
function SeriesIndexOverviewInfo(props: SeriesIndexOverviewInfoProps) {
@@ -194,6 +214,8 @@ function SeriesIndexOverviewInfo(props: SeriesIndexOverviewInfoProps) {
const { name, showProp, valueProp } = row;
const isVisible =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(7053)
props[valueProp] != null && (props[showProp] || props.sortKey === name);
return {
@@ -234,6 +256,10 @@ function SeriesIndexOverviewInfo(props: SeriesIndexOverviewInfoProps) {
const infoRowProps = getInfoRowProps(row, props, uiSettings);
if (infoRowProps == null) {
return null;
}
return <SeriesIndexOverviewInfoRow key={row.name} {...infoRowProps} />;
})}
</div>
@@ -1,11 +1,12 @@
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
import React from 'react';
import Icon from 'Components/Icon';
import styles from './SeriesIndexOverviewInfoRow.css';
interface SeriesIndexOverviewInfoRowProps {
title?: string;
iconName: object;
label: string;
iconName?: IconDefinition;
label: string | null;
}
function SeriesIndexOverviewInfoRow(props: SeriesIndexOverviewInfoRowProps) {
@@ -1,5 +1,5 @@
import { throttle } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
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';
@@ -33,11 +33,11 @@ interface RowItemData {
interface SeriesIndexOverviewsProps {
items: Series[];
sortKey?: string;
sortKey: string;
sortDirection?: string;
jumpToCharacter?: string;
scrollTop?: number;
scrollerRef: React.MutableRefObject<HTMLElement>;
scrollerRef: RefObject<HTMLElement>;
isSelectMode: boolean;
isSmallScreen: boolean;
}
@@ -79,7 +79,7 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
const { size: posterSize, detailedProgressBar } = useSelector(
selectOverviewOptions
);
const listRef: React.MutableRefObject<List> = useRef();
const listRef = useRef<List>(null);
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
@@ -136,8 +136,8 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
}, [isSmallScreen, scrollerRef, bounds]);
useEffect(() => {
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
const currentScrollerRef = scrollerRef.current;
const currentScrollerRef = scrollerRef.current as HTMLElement;
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
const handleScroll = throttle(() => {
const { offsetTop = 0 } = currentScrollerRef;
@@ -146,7 +146,7 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
? getWindowScrollTopPosition()
: currentScrollerRef.scrollTop) - offsetTop;
listRef.current.scrollTo(scrollTop);
listRef.current?.scrollTo(scrollTop);
}, 10);
currentScrollListener.addEventListener('scroll', handleScroll);
@@ -175,8 +175,8 @@ function SeriesIndexOverviews(props: SeriesIndexOverviewsProps) {
scrollTop += offset;
}
listRef.current.scrollTo(scrollTop);
scrollerRef.current.scrollTo(0, scrollTop);
listRef.current?.scrollTo(scrollTop);
scrollerRef.current?.scrollTo(0, scrollTop);
}
}
}, [jumpToCharacter, rowHeight, items, scrollerRef, listRef]);
@@ -1,7 +1,8 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectOverviewOptions = createSelector(
(state) => state.seriesIndex.overviewOptions,
(state: AppState) => state.seriesIndex.overviewOptions,
(overviewOptions) => overviewOptions
);
@@ -42,7 +42,7 @@ function SeriesIndexPosterOptionsModalContent(
const dispatch = useDispatch();
const onPosterOptionChange = useCallback(
({ name, value }) => {
({ name, value }: { name: string; value: unknown }) => {
dispatch(setSeriesPosterOption({ [name]: value }));
},
[dispatch]
@@ -10,6 +10,7 @@ import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
import SeriesIndexProgressBar from 'Series/Index/ProgressBar/SeriesIndexProgressBar';
import SeriesIndexPosterSelect from 'Series/Index/Select/SeriesIndexPosterSelect';
import { Statistics } from 'Series/Series';
import SeriesPoster from 'Series/SeriesPoster';
import { executeCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@@ -52,7 +53,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
path,
titleSlug,
nextAiring,
statistics = {},
statistics = {} as Statistics,
images,
} = series;
@@ -1,8 +1,9 @@
import { throttle } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
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/SortDirection';
import SeriesIndexPoster from 'Series/Index/Posters/SeriesIndexPoster';
@@ -21,7 +22,7 @@ const columnPaddingSmallScreen = parseInt(
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
const ADDITIONAL_COLUMN_COUNT = {
const ADDITIONAL_COLUMN_COUNT: Record<string, number> = {
small: 3,
medium: 2,
large: 1,
@@ -41,17 +42,17 @@ interface CellItemData {
interface SeriesIndexPostersProps {
items: Series[];
sortKey?: string;
sortKey: string;
sortDirection?: SortDirection;
jumpToCharacter?: string;
scrollTop?: number;
scrollerRef: React.MutableRefObject<HTMLElement>;
scrollerRef: RefObject<HTMLElement>;
isSelectMode: boolean;
isSmallScreen: boolean;
}
const seriesIndexSelector = createSelector(
(state) => state.seriesIndex.posterOptions,
(state: AppState) => state.seriesIndex.posterOptions,
(posterOptions) => {
return {
posterOptions,
@@ -108,7 +109,7 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
} = props;
const { posterOptions } = useSelector(seriesIndexSelector);
const ref: React.MutableRefObject<Grid> = useRef();
const ref = useRef<Grid>(null);
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
@@ -210,8 +211,8 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
}, [isSmallScreen, scrollerRef, bounds]);
useEffect(() => {
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
const currentScrollerRef = scrollerRef.current;
const currentScrollerRef = scrollerRef.current as HTMLElement;
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
const handleScroll = throttle(() => {
const { offsetTop = 0 } = currentScrollerRef;
@@ -220,7 +221,7 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
? getWindowScrollTopPosition()
: currentScrollerRef.scrollTop) - offsetTop;
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
}, 10);
currentScrollListener.addEventListener('scroll', handleScroll);
@@ -243,8 +244,8 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
const scrollTop = rowIndex * rowHeight + padding;
ref.current.scrollTo({ scrollLeft: 0, scrollTop });
scrollerRef.current.scrollTo(0, scrollTop);
ref.current?.scrollTo({ scrollLeft: 0, scrollTop });
scrollerRef.current?.scrollTo(0, scrollTop);
}
}
}, [
@@ -1,7 +1,8 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectPosterOptions = createSelector(
(state) => state.seriesIndex.posterOptions,
(state: AppState) => state.seriesIndex.posterOptions,
(posterOptions) => posterOptions
);
@@ -2,6 +2,7 @@ import { orderBy } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
@@ -11,8 +12,10 @@ import ModalContent from 'Components/Modal/ModalContent';
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 { CheckInputChanged } from 'typings/inputs';
import styles from './DeleteSeriesModalContent.css';
interface DeleteSeriesModalContentProps {
@@ -21,7 +24,7 @@ interface DeleteSeriesModalContentProps {
}
const selectDeleteOptions = createSelector(
(state) => state.series.deleteOptions,
(state: AppState) => state.series.deleteOptions,
(deleteOptions) => deleteOptions
);
@@ -29,28 +32,28 @@ function DeleteSeriesModalContent(props: DeleteSeriesModalContentProps) {
const { seriesIds, onModalClose } = props;
const { addImportListExclusion } = useSelector(selectDeleteOptions);
const allSeries = useSelector(createAllSeriesSelector());
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const dispatch = useDispatch();
const [deleteFiles, setDeleteFiles] = useState(false);
const series = useMemo(() => {
const series = seriesIds.map((id) => {
const series = useMemo((): Series[] => {
const seriesList = seriesIds.map((id) => {
return allSeries.find((s) => s.id === id);
});
}) as Series[];
return orderBy(series, ['sortTitle']);
return orderBy(seriesList, ['sortTitle']);
}, [seriesIds, allSeries]);
const onDeleteFilesChange = useCallback(
({ value }) => {
({ value }: CheckInputChanged) => {
setDeleteFiles(value);
},
[setDeleteFiles]
);
const onDeleteOptionChange = useCallback(
({ name, value }) => {
({ name, value }: { name: string; value: boolean }) => {
dispatch(
setDeleteOption({
[name]: value,
@@ -54,7 +54,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) {
const [isConfirmMoveModalOpen, setIsConfirmMoveModalOpen] = useState(false);
const save = useCallback(
(moveFiles) => {
(moveFiles: boolean) => {
let hasChanges = false;
const payload: SavePayload = {};
@@ -102,7 +102,7 @@ function EditSeriesModalContent(props: EditSeriesModalContentProps) {
);
const onInputChange = useCallback(
({ name, value }) => {
({ name, value }: { name: string; value: string }) => {
switch (name) {
case 'monitored':
setMonitored(value);
@@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, kinds } from 'Helpers/Props';
import Series from 'Series/Series';
import { executeCommand } from 'Store/Actions/commandActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import styles from './OrganizeSeriesModalContent.css';
@@ -22,13 +23,19 @@ interface OrganizeSeriesModalContentProps {
function OrganizeSeriesModalContent(props: OrganizeSeriesModalContentProps) {
const { seriesIds, onModalClose } = props;
const allSeries = useSelector(createAllSeriesSelector());
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const dispatch = useDispatch();
const seriesTitles = useMemo(() => {
const series = seriesIds.map((id) => {
return allSeries.find((s) => s.id === id);
});
const series = seriesIds.reduce((acc: Series[], id) => {
const s = allSeries.find((s) => s.id === id);
if (s) {
acc.push(s);
}
return acc;
}, []);
const sorted = orderBy(series, ['sortTitle']);
@@ -32,7 +32,7 @@ function ChangeMonitoringModalContent(
const [monitor, setMonitor] = useState(NO_CHANGE);
const onInputChange = useCallback(
({ value }) => {
({ value }: { value: string }) => {
setMonitor(value);
},
[setMonitor]
@@ -18,7 +18,12 @@ function SeasonDetails(props: SeasonDetailsProps) {
return (
<div className={styles.seasons}>
{latestSeasons.map((season) => {
const { seasonNumber, monitored, statistics, isSaving } = season;
const {
seasonNumber,
monitored,
statistics,
isSaving = false,
} = season;
return (
<SeasonPassSeason
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { SyntheticEvent, useCallback } from 'react';
import { useSelect } from 'App/SelectContext';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
@@ -15,8 +15,9 @@ function SeriesIndexPosterSelect(props: SeriesIndexPosterSelectProps) {
const isSelected = selectState.selectedState[seriesId];
const onSelectPress = useCallback(
(event) => {
const shiftKey = event.nativeEvent.shiftKey;
(event: SyntheticEvent) => {
const nativeEvent = event.nativeEvent as PointerEvent;
const shiftKey = nativeEvent.shiftKey;
selectDispatch({
type: 'toggleSelected',
@@ -6,7 +6,7 @@ import { icons } from 'Helpers/Props';
interface SeriesIndexSelectAllButtonProps {
label: string;
isSelectMode: boolean;
overflowComponent: React.FunctionComponent;
overflowComponent: React.FunctionComponent<never>;
}
function SeriesIndexSelectAllButton(props: SeriesIndexSelectAllButtonProps) {
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { useSelect } from 'App/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';
@@ -22,7 +23,7 @@ import TagsModal from './Tags/TagsModal';
import styles from './SeriesIndexSelectFooter.css';
const seriesEditorSelector = createSelector(
(state) => state.series,
(state: AppState) => state.series,
(series) => {
const { isSaving, isDeleting, deleteError } = series;
@@ -71,7 +72,7 @@ function SeriesIndexSelectFooter() {
}, [setIsEditModalOpen]);
const onSavePress = useCallback(
(payload) => {
(payload: any) => {
setIsSavingSeries(true);
setIsEditModalOpen(false);
@@ -102,7 +103,7 @@ function SeriesIndexSelectFooter() {
}, [setIsTagsModalOpen]);
const onApplyTagsPress = useCallback(
(tags, applyTags) => {
(tags: number[], applyTags: string) => {
setIsSavingTags(true);
setIsTagsModalOpen(false);
@@ -126,7 +127,7 @@ function SeriesIndexSelectFooter() {
}, [setIsMonitoringModalOpen]);
const onMonitoringSavePress = useCallback(
(monitor) => {
(monitor: string) => {
setIsSavingMonitoring(true);
setIsMonitoringModalOpen(false);
@@ -7,7 +7,7 @@ interface SeriesIndexSelectModeButtonProps {
label: string;
iconName: IconDefinition;
isSelectMode: boolean;
overflowComponent: React.FunctionComponent;
overflowComponent: React.FunctionComponent<never>;
onPress: () => void;
}
@@ -1,6 +1,7 @@
import { concat, uniq } from 'lodash';
import { uniq } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Tag } from 'App/State/TagsAppState';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -12,6 +13,7 @@ import ModalContent from 'Components/Modal/ModalContent';
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 createTagsSelector from 'Store/Selectors/createTagsSelector';
import styles from './TagsModalContent.css';
@@ -25,29 +27,35 @@ interface TagsModalContentProps {
function TagsModalContent(props: TagsModalContentProps) {
const { seriesIds, onModalClose, onApplyTagsPress } = props;
const allSeries = useSelector(createAllSeriesSelector());
const tagList = useSelector(createTagsSelector());
const allSeries: Series[] = useSelector(createAllSeriesSelector());
const tagList: Tag[] = useSelector(createTagsSelector());
const [tags, setTags] = useState<number[]>([]);
const [applyTags, setApplyTags] = useState('add');
const seriesTags = useMemo(() => {
const series = seriesIds.map((id) => {
return allSeries.find((s) => s.id === id);
});
const tags = seriesIds.reduce((acc: number[], id) => {
const s = allSeries.find((s) => s.id === id);
return uniq(concat(...series.map((s) => s.tags)));
if (s) {
acc.push(...s.tags);
}
return acc;
}, []);
return uniq(tags);
}, [seriesIds, allSeries]);
const onTagsChange = useCallback(
({ value }) => {
({ value }: { value: number[] }) => {
setTags(value);
},
[setTags]
);
const onApplyTagsChange = useCallback(
({ value }) => {
({ value }: { value: string }) => {
setApplyTags(value);
},
[setApplyTags]
+20 -13
View File
@@ -7,6 +7,8 @@ import React, {
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import SeriesAppState, { SeriesIndexAppState } from 'App/State/SeriesAppState';
import { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
@@ -51,7 +53,7 @@ import SeriesIndexTable from './Table/SeriesIndexTable';
import SeriesIndexTableOptions from './Table/SeriesIndexTableOptions';
import styles from './SeriesIndex.css';
function getViewComponent(view) {
function getViewComponent(view: string) {
if (view === 'posters') {
return SeriesIndexPosters;
}
@@ -81,7 +83,8 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
sortKey,
sortDirection,
view,
} = useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex'));
}: SeriesAppState & SeriesIndexAppState & ClientSideCollectionAppState =
useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex'));
const isRefreshingSeries = useSelector(
createCommandExecutingSelector(REFRESH_SERIES)
@@ -91,9 +94,11 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
);
const { isSmallScreen } = useSelector(createDimensionsSelector());
const dispatch = useDispatch();
const scrollerRef = useRef<HTMLDivElement>();
const scrollerRef = useRef<HTMLDivElement>(null);
const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false);
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
const [jumpToCharacter, setJumpToCharacter] = useState<string | undefined>(
undefined
);
const [isSelectMode, setIsSelectMode] = useState(false);
useEffect(() => {
@@ -122,14 +127,14 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
}, [isSelectMode, setIsSelectMode]);
const onTableOptionChange = useCallback(
(payload) => {
(payload: unknown) => {
dispatch(setSeriesTableOption(payload));
},
[dispatch]
);
const onViewSelect = useCallback(
(value) => {
(value: string) => {
dispatch(setSeriesView({ view: value }));
if (scrollerRef.current) {
@@ -140,14 +145,14 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
);
const onSortSelect = useCallback(
(value) => {
(value: string) => {
dispatch(setSeriesSort({ sortKey: value }));
},
[dispatch]
);
const onFilterSelect = useCallback(
(value) => {
(value: string) => {
dispatch(setSeriesFilter({ selectedFilterKey: value }));
},
[dispatch]
@@ -162,15 +167,15 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
}, [setIsOptionsModalOpen]);
const onJumpBarItemPress = useCallback(
(character) => {
(character: string) => {
setJumpToCharacter(character);
},
[setJumpToCharacter]
);
const onScroll = useCallback(
({ scrollTop }) => {
setJumpToCharacter(null);
({ scrollTop }: { scrollTop: number }) => {
setJumpToCharacter(undefined);
scrollPositions.seriesIndex = scrollTop;
},
[setJumpToCharacter]
@@ -184,10 +189,10 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
};
}
const characters = items.reduce((acc, item) => {
const characters = items.reduce((acc: Record<string, number>, item) => {
let char = item.sortTitle.charAt(0);
if (!isNaN(char)) {
if (!isNaN(Number(char))) {
char = '#';
}
@@ -305,6 +310,8 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
<PageContentBody
ref={scrollerRef}
className={styles.contentBody}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
innerClassName={styles[`${view}InnerContentBody`]}
initialScrollTop={props.initialScrollTop}
onScroll={onScroll}
@@ -1,12 +1,13 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setSeriesFilter } from 'Store/Actions/seriesIndexActions';
function createSeriesSelector() {
return createSelector(
(state) => state.series.items,
(state: AppState) => state.series.items,
(series) => {
return series;
}
@@ -15,14 +16,20 @@ function createSeriesSelector() {
function createFilterBuilderPropsSelector() {
return createSelector(
(state) => state.seriesIndex.filterBuilderProps,
(state: AppState) => state.seriesIndex.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
export default function SeriesIndexFilterModal(props) {
interface SeriesIndexFilterModalProps {
isOpen: boolean;
}
export default function SeriesIndexFilterModal(
props: SeriesIndexFilterModalProps
) {
const sectionItems = useSelector(createSeriesSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'series';
@@ -30,7 +37,7 @@ export default function SeriesIndexFilterModal(props) {
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload) => {
(payload: unknown) => {
dispatch(setSeriesFilter(payload));
},
[dispatch]
@@ -38,6 +45,7 @@ export default function SeriesIndexFilterModal(props) {
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
@@ -3,6 +3,7 @@ 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';
@@ -13,7 +14,7 @@ import styles from './SeriesIndexFooter.css';
function createUnoptimizedSelector() {
return createSelector(
createClientSideCollectionSelector('series', 'seriesIndex'),
(series) => {
(series: SeriesAppState) => {
return series.items.map((s) => {
const { monitored, status, statistics } = s;
@@ -45,7 +46,9 @@ export default function SeriesIndexFooter() {
let totalFileSize = 0;
series.forEach((s) => {
const { statistics = {} } = s;
const {
statistics = { episodeCount: 0, episodeFileCount: 0, sizeOnDisk: 0 },
} = s;
const {
episodeCount = 0,
@@ -17,9 +17,11 @@ import { icons } from 'Helpers/Props';
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
import createSeriesIndexItemSelector from 'Series/Index/createSeriesIndexItemSelector';
import { Statistics } from 'Series/Series';
import SeriesBanner from 'Series/SeriesBanner';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import { executeCommand } from 'Store/Actions/commandActions';
import { SelectStateInputProps } from 'typings/props';
import formatBytes from 'Utilities/Number/formatBytes';
import titleCase from 'Utilities/String/titleCase';
import SeriesIndexProgressBar from '../ProgressBar/SeriesIndexProgressBar';
@@ -58,7 +60,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
nextAiring,
previousAiring,
added,
statistics = {},
statistics = {} as Statistics,
seasonFolder,
images,
seriesType,
@@ -137,7 +139,7 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
}, []);
const onSelectedChange = useCallback(
({ id, value, shiftKey }) => {
({ id, value, shiftKey }: SelectStateInputProps) => {
selectDispatch({
type: 'toggleSelected',
id,
@@ -247,6 +249,8 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
if (name === 'nextAiring') {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2739)
<RelativeDateCellConnector
key={name}
className={styles[name]}
@@ -258,6 +262,8 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
if (name === 'previousAiring') {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2739)
<RelativeDateCellConnector
key={name}
className={styles[name]}
@@ -269,6 +275,8 @@ function SeriesIndexRow(props: SeriesIndexRowProps) {
if (name === 'added') {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2739)
<RelativeDateCellConnector
key={name}
className={styles[name]}
@@ -1,8 +1,9 @@
import { throttle } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import Scroller from 'Components/Scroller/Scroller';
import Column from 'Components/Table/Column';
import useMeasure from 'Helpers/Hooks/useMeasure';
@@ -30,17 +31,17 @@ interface RowItemData {
interface SeriesIndexTableProps {
items: Series[];
sortKey?: string;
sortKey: string;
sortDirection?: SortDirection;
jumpToCharacter?: string;
scrollTop?: number;
scrollerRef: React.MutableRefObject<HTMLElement>;
scrollerRef: RefObject<HTMLElement>;
isSelectMode: boolean;
isSmallScreen: boolean;
}
const columnsSelector = createSelector(
(state) => state.seriesIndex.columns,
(state: AppState) => state.seriesIndex.columns,
(columns) => columns
);
@@ -92,7 +93,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
const columns = useSelector(columnsSelector);
const { showBanners } = useSelector(selectTableOptions);
const listRef: React.MutableRefObject<List> = useRef();
const listRef = useRef<List<RowItemData>>(null);
const [measureRef, bounds] = useMeasure();
const [size, setSize] = useState({ width: 0, height: 0 });
const windowWidth = window.innerWidth;
@@ -103,7 +104,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
}, [showBanners]);
useEffect(() => {
const current = scrollerRef.current as HTMLElement;
const current = scrollerRef?.current as HTMLElement;
if (isSmallScreen) {
setSize({
@@ -127,8 +128,8 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
}, [isSmallScreen, windowWidth, windowHeight, scrollerRef, bounds]);
useEffect(() => {
const currentScrollListener = isSmallScreen ? window : scrollerRef.current;
const currentScrollerRef = scrollerRef.current;
const currentScrollerRef = scrollerRef.current as HTMLElement;
const currentScrollListener = isSmallScreen ? window : currentScrollerRef;
const handleScroll = throttle(() => {
const { offsetTop = 0 } = currentScrollerRef;
@@ -137,7 +138,7 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
? getWindowScrollTopPosition()
: currentScrollerRef.scrollTop) - offsetTop;
listRef.current.scrollTo(scrollTop);
listRef.current?.scrollTo(scrollTop);
}, 10);
currentScrollListener.addEventListener('scroll', handleScroll);
@@ -166,8 +167,8 @@ function SeriesIndexTable(props: SeriesIndexTableProps) {
scrollTop += offset;
}
listRef.current.scrollTo(scrollTop);
scrollerRef.current.scrollTo(0, scrollTop);
listRef.current?.scrollTo(scrollTop);
scrollerRef?.current?.scrollTo(0, scrollTop);
}
}
}, [jumpToCharacter, rowHeight, items, scrollerRef, listRef]);
@@ -14,6 +14,7 @@ import {
setSeriesSort,
setSeriesTableOption,
} from 'Store/Actions/seriesIndexActions';
import { CheckInputChanged } from 'typings/inputs';
import hasGrowableColumns from './hasGrowableColumns';
import SeriesIndexTableOptions from './SeriesIndexTableOptions';
import styles from './SeriesIndexTableHeader.css';
@@ -32,21 +33,21 @@ function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
const [selectState, selectDispatch] = useSelect();
const onSortPress = useCallback(
(value) => {
(value: string) => {
dispatch(setSeriesSort({ sortKey: value }));
},
[dispatch]
);
const onTableOptionChange = useCallback(
(payload) => {
(payload: unknown) => {
dispatch(setSeriesTableOption(payload));
},
[dispatch]
);
const onSelectAllChange = useCallback(
({ value }) => {
({ value }: CheckInputChanged) => {
selectDispatch({
type: value ? 'selectAll' : 'unselectAll',
});
@@ -94,6 +95,8 @@ function SeriesIndexTableHeader(props: SeriesIndexTableHeaderProps) {
<VirtualTableHeaderCell
key={name}
className={classNames(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
styles[name],
name === 'sortTitle' && showBanners && styles.banner,
name === 'sortTitle' &&
@@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import { CheckInputChanged } from 'typings/inputs';
import selectTableOptions from './selectTableOptions';
interface SeriesIndexTableOptionsProps {
@@ -18,7 +19,7 @@ function SeriesIndexTableOptions(props: SeriesIndexTableOptionsProps) {
const { showBanners, showSearchAction } = tableOptions;
const onTableOptionChangeWrapper = useCallback(
({ name, value }) => {
({ name, value }: CheckInputChanged) => {
onTableOptionChange({
tableOptions: {
...tableOptions,
@@ -1,6 +1,8 @@
import Column from 'Components/Table/Column';
const growableColumns = ['network', 'qualityProfileId', 'path', 'tags'];
export default function hasGrowableColumns(columns) {
export default function hasGrowableColumns(columns: Column[]) {
return columns.some((column) => {
const { name, isVisible } = column;
@@ -1,7 +1,8 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const selectTableOptions = createSelector(
(state) => state.seriesIndex.tableOptions,
(state: AppState) => state.seriesIndex.tableOptions,
(tableOptions) => tableOptions
);
@@ -1,6 +1,8 @@
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';
@@ -10,25 +12,16 @@ function createSeriesIndexItemSelector(seriesId: number) {
createSeriesSelectorForHook(seriesId),
createSeriesQualityProfileSelector(seriesId),
createExecutingCommandsSelector(),
(series, qualityProfile, executingCommands) => {
// If a series is deleted this selector may fire before the parent
// selectors, which will result in an undefined series, if that happens
// we want to return early here and again in the render function to avoid
// trying to show a series that has no information available.
if (!series) {
return {};
}
(series: Series, qualityProfile, executingCommands: Command[]) => {
const isRefreshingSeries = executingCommands.some((command) => {
return (
command.name === REFRESH_SERIES && command.body.seriesId === series.id
command.name === REFRESH_SERIES && command.body.seriesId === seriesId
);
});
const isSearchingSeries = executingCommands.some((command) => {
return (
command.name === SERIES_SEARCH && command.body.seriesId === series.id
command.name === SERIES_SEARCH && command.body.seriesId === seriesId
);
});
@@ -1,4 +1,5 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
export interface SeriesQueueDetails {
count: number;
@@ -10,7 +11,7 @@ function createSeriesQueueDetailsSelector(
seasonNumber?: number
) {
return createSelector(
(state) => state.queue.details.items,
(state: AppState) => state.queue.details.items,
(queueItems) => {
return queueItems.reduce(
(acc: SeriesQueueDetails, item) => {