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:
@@ -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;
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user