mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-23 22:25:56 -04:00
Typings cleanup and improvements
This commit is contained in:
@@ -7,8 +7,8 @@ import SelectEpisodeModalContent, {
|
||||
interface SelectEpisodeModalProps {
|
||||
isOpen: boolean;
|
||||
selectedIds: number[] | string[];
|
||||
seriesId: number;
|
||||
seasonNumber: number;
|
||||
seriesId?: number;
|
||||
seasonNumber?: number;
|
||||
selectedDetails?: string;
|
||||
isAnime: boolean;
|
||||
modalTitle: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import EpisodesAppState from 'App/State/EpisodesAppState';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -14,12 +15,15 @@ import TableBody from 'Components/Table/TableBody';
|
||||
import Episode from 'Episode/Episode';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { kinds, scrollDirections } from 'Helpers/Props';
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import {
|
||||
clearEpisodes,
|
||||
fetchEpisodes,
|
||||
setEpisodesSort,
|
||||
} from 'Store/Actions/episodeSelectionActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import SelectEpisodeRow from './SelectEpisodeRow';
|
||||
@@ -47,7 +51,7 @@ const columns = [
|
||||
function episodesSelector() {
|
||||
return createSelector(
|
||||
createClientSideCollectionSelector('episodeSelection'),
|
||||
(episodes) => {
|
||||
(episodes: EpisodesAppState) => {
|
||||
return episodes;
|
||||
}
|
||||
);
|
||||
@@ -60,8 +64,8 @@ export interface SelectedEpisode {
|
||||
|
||||
interface SelectEpisodeModalContentProps {
|
||||
selectedIds: number[] | string[];
|
||||
seriesId: number;
|
||||
seasonNumber: number;
|
||||
seriesId?: number;
|
||||
seasonNumber?: number;
|
||||
selectedDetails?: string;
|
||||
isAnime: boolean;
|
||||
sortKey?: string;
|
||||
@@ -100,26 +104,26 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
||||
const filterEpisodeNumber = parseInt(filter);
|
||||
const errorMessage = getErrorMessage(error, 'Unable to load episodes');
|
||||
const selectedCount = selectedIds.length;
|
||||
const selectedEpisodesCount = getSelectedIds(selectState).length;
|
||||
const selectedEpisodesCount = getSelectedIds(selectedState).length;
|
||||
const selectionIsValid =
|
||||
selectedEpisodesCount > 0 && selectedEpisodesCount % selectedCount === 0;
|
||||
|
||||
const onFilterChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setFilter(value.toLowerCase());
|
||||
},
|
||||
[setFilter]
|
||||
);
|
||||
|
||||
const onSelectAllChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: CheckInputChanged) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
},
|
||||
[items, setSelectState]
|
||||
);
|
||||
|
||||
const onSelectedChange = useCallback(
|
||||
({ id, value, shiftKey = false }) => {
|
||||
({ id, value, shiftKey = false }: SelectStateInputProps) => {
|
||||
setSelectState({
|
||||
type: 'toggleSelected',
|
||||
items,
|
||||
@@ -132,7 +136,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
||||
);
|
||||
|
||||
const onSortPress = useCallback(
|
||||
(newSortKey, newSortDirection) => {
|
||||
(newSortKey: string, newSortDirection: SortDirection) => {
|
||||
dispatch(
|
||||
setEpisodesSort({
|
||||
sortKey: newSortKey,
|
||||
@@ -144,9 +148,9 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
||||
);
|
||||
|
||||
const onEpisodesSelectWrapper = useCallback(() => {
|
||||
const episodeIds = getSelectedIds(selectedState);
|
||||
const episodeIds: number[] = getSelectedIds(selectedState);
|
||||
|
||||
const selectedEpisodes = items.reduce((acc, item) => {
|
||||
const selectedEpisodes = items.reduce((acc: Episode[], item) => {
|
||||
if (episodeIds.indexOf(item.id) > -1) {
|
||||
acc.push(item);
|
||||
}
|
||||
@@ -167,7 +171,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
||||
);
|
||||
|
||||
return {
|
||||
fileId,
|
||||
fileId: fileId as number,
|
||||
episodes,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import PathInputConnector from 'Components/Form/PathInputConnector';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
removeRecentFolder,
|
||||
} from 'Store/Actions/interactiveImportActions';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import RecentFolder from './RecentFolder';
|
||||
import RecentFolderRow from './RecentFolderRow';
|
||||
import styles from './InteractiveImportSelectFolderModalContent.css';
|
||||
|
||||
@@ -49,9 +49,9 @@ function InteractiveImportSelectFolderModalContent(
|
||||
const { modalTitle, onFolderSelect, onModalClose } = props;
|
||||
const [folder, setFolder] = useState('');
|
||||
const dispatch = useDispatch();
|
||||
const recentFolders: RecentFolder[] = useSelector(
|
||||
const recentFolders = useSelector(
|
||||
createSelector(
|
||||
(state) => state.interactiveImport.recentFolders,
|
||||
(state: AppState) => state.interactiveImport.recentFolders,
|
||||
(recentFolders) => {
|
||||
return recentFolders;
|
||||
}
|
||||
@@ -59,14 +59,14 @@ function InteractiveImportSelectFolderModalContent(
|
||||
);
|
||||
|
||||
const onPathChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setFolder(value);
|
||||
},
|
||||
[setFolder]
|
||||
);
|
||||
|
||||
const onRecentPathPress = useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
setFolder(value);
|
||||
},
|
||||
[setFolder]
|
||||
@@ -91,8 +91,8 @@ function InteractiveImportSelectFolderModalContent(
|
||||
}, [folder, onFolderSelect, dispatch]);
|
||||
|
||||
const onRemoveRecentFolderPress = useCallback(
|
||||
(f) => {
|
||||
dispatch(removeRecentFolder({ folder: f }));
|
||||
(folderToRemove: string) => {
|
||||
dispatch(removeRecentFolder({ folder: folderToRemove }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
enum ImportMode {
|
||||
Auto = 'auto',
|
||||
Move = 'move',
|
||||
Copy = 'copy',
|
||||
}
|
||||
type ImportMode = 'auto' | 'move' | 'copy' | 'chooseImportMode';
|
||||
|
||||
export default ImportMode;
|
||||
|
||||
@@ -2,6 +2,8 @@ import { cloneDeep, without } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -20,16 +22,24 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import Column from 'Components/Table/Column';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { EpisodeFile } from 'EpisodeFile/EpisodeFile';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
|
||||
import { SelectedEpisode } from 'InteractiveImport/Episode/SelectEpisodeModalContent';
|
||||
import ImportMode from 'InteractiveImport/ImportMode';
|
||||
import InteractiveImport, {
|
||||
InteractiveImportCommandOptions,
|
||||
} from 'InteractiveImport/InteractiveImport';
|
||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
|
||||
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import Series from 'Series/Series';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import {
|
||||
deleteEpisodeFiles,
|
||||
@@ -44,6 +54,8 @@ import {
|
||||
updateInteractiveImportItems,
|
||||
} from 'Store/Actions/interactiveImportActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { SortCallback } from 'typings/callbacks';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
@@ -59,6 +71,13 @@ type SelectType =
|
||||
| 'quality'
|
||||
| 'language';
|
||||
|
||||
type FilterExistingFiles = 'all' | 'new';
|
||||
|
||||
// TODO: This feels janky to do, but not sure of a better way currently
|
||||
type OnSelectedChangeCallback = React.ComponentProps<
|
||||
typeof InteractiveImportRow
|
||||
>['onSelectedChange'];
|
||||
|
||||
const COLUMNS = [
|
||||
{
|
||||
name: 'relativePath',
|
||||
@@ -125,25 +144,23 @@ const COLUMNS = [
|
||||
},
|
||||
];
|
||||
|
||||
const filterExistingFilesOptions = {
|
||||
ALL: 'all',
|
||||
NEW: 'new',
|
||||
};
|
||||
|
||||
const importModeOptions = [
|
||||
{ key: 'chooseImportMode', value: 'Choose Import Mode', disabled: true },
|
||||
{ key: 'move', value: 'Move Files' },
|
||||
{ key: 'copy', value: 'Hardlink/Copy Files' },
|
||||
];
|
||||
|
||||
function isSameEpisodeFile(file, originalFile) {
|
||||
function isSameEpisodeFile(
|
||||
file: InteractiveImport,
|
||||
originalFile?: InteractiveImport
|
||||
) {
|
||||
const { series, seasonNumber, episodes } = file;
|
||||
|
||||
if (!originalFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!originalFile.series || series.id !== originalFile.series.id) {
|
||||
if (!originalFile.series || series?.id !== originalFile.series.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,8 +172,8 @@ function isSameEpisodeFile(file, originalFile) {
|
||||
}
|
||||
|
||||
const episodeFilesInfoSelector = createSelector(
|
||||
(state) => state.episodeFiles.isDeleting,
|
||||
(state) => state.episodeFiles.deleteError,
|
||||
(state: AppState) => state.episodeFiles.isDeleting,
|
||||
(state: AppState) => state.episodeFiles.deleteError,
|
||||
(isDeleting, deleteError) => {
|
||||
return {
|
||||
isDeleting,
|
||||
@@ -166,7 +183,7 @@ const episodeFilesInfoSelector = createSelector(
|
||||
);
|
||||
|
||||
const importModeSelector = createSelector(
|
||||
(state) => state.interactiveImport.importMode,
|
||||
(state: AppState) => state.interactiveImport.importMode,
|
||||
(importMode) => {
|
||||
return importMode;
|
||||
}
|
||||
@@ -178,7 +195,6 @@ interface InteractiveImportModalContentProps {
|
||||
seasonNumber?: number;
|
||||
showSeries?: boolean;
|
||||
allowSeriesChange?: boolean;
|
||||
autoSelectRow?: boolean;
|
||||
showDelete?: boolean;
|
||||
showImportMode?: boolean;
|
||||
showFilterExistingFiles?: boolean;
|
||||
@@ -200,7 +216,6 @@ function InteractiveImportModalContent(
|
||||
seriesId,
|
||||
seasonNumber,
|
||||
allowSeriesChange = true,
|
||||
autoSelectRow = true,
|
||||
showSeries = true,
|
||||
showFilterExistingFiles = false,
|
||||
showDelete = false,
|
||||
@@ -221,16 +236,18 @@ function InteractiveImportModalContent(
|
||||
originalItems,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
} = useSelector(createClientSideCollectionSelector('interactiveImport'));
|
||||
}: InteractiveImportAppState = useSelector(
|
||||
createClientSideCollectionSelector('interactiveImport')
|
||||
);
|
||||
|
||||
const { isDeleting, deleteError } = useSelector(episodeFilesInfoSelector);
|
||||
const importMode = useSelector(importModeSelector);
|
||||
|
||||
const [invalidRowsSelected, setInvalidRowsSelected] = useState([]);
|
||||
const [invalidRowsSelected, setInvalidRowsSelected] = useState<number[]>([]);
|
||||
const [
|
||||
withoutEpisodeFileIdRowsSelected,
|
||||
setWithoutEpisodeFileIdRowsSelected,
|
||||
] = useState([]);
|
||||
] = useState<number[]>([]);
|
||||
const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>(
|
||||
null
|
||||
);
|
||||
@@ -253,16 +270,20 @@ function InteractiveImportModalContent(
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const columns: Column[] = useMemo(() => {
|
||||
const result = cloneDeep(COLUMNS);
|
||||
const result: Column[] = cloneDeep(COLUMNS);
|
||||
|
||||
if (!showSeries) {
|
||||
result.find((c) => c.name === 'series').isVisible = false;
|
||||
const seriesColumn = result.find((c) => c.name === 'series');
|
||||
|
||||
if (seriesColumn) {
|
||||
seriesColumn.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [showSeries]);
|
||||
|
||||
const selectedIds = useMemo(() => {
|
||||
const selectedIds: number[] = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
}, [selectedState]);
|
||||
|
||||
@@ -317,13 +338,13 @@ function InteractiveImportModalContent(
|
||||
}, [previousIsDeleting, isDeleting, deleteError, onModalClose]);
|
||||
|
||||
const onSelectAllChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: SelectStateInputProps) => {
|
||||
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||
},
|
||||
[items, setSelectState]
|
||||
);
|
||||
|
||||
const onSelectedChange = useCallback(
|
||||
const onSelectedChange = useCallback<OnSelectedChangeCallback>(
|
||||
({ id, value, hasEpisodeFileId, shiftKey = false }) => {
|
||||
setSelectState({
|
||||
type: 'toggleSelected',
|
||||
@@ -365,7 +386,7 @@ function InteractiveImportModalContent(
|
||||
const onConfirmDelete = useCallback(() => {
|
||||
setIsConfirmDeleteModalOpen(false);
|
||||
|
||||
const episodeFileIds = items.reduce((acc, item) => {
|
||||
const episodeFileIds = items.reduce((acc: number[], item) => {
|
||||
if (selectedIds.indexOf(item.id) > -1 && item.episodeFileId) {
|
||||
acc.push(item.episodeFileId);
|
||||
}
|
||||
@@ -381,11 +402,10 @@ function InteractiveImportModalContent(
|
||||
}, [setIsConfirmDeleteModalOpen]);
|
||||
|
||||
const onImportSelectedPress = useCallback(() => {
|
||||
const finalImportMode =
|
||||
downloadId || !showImportMode ? ImportMode.Auto : importMode;
|
||||
const finalImportMode = downloadId || !showImportMode ? 'auto' : importMode;
|
||||
|
||||
const existingFiles = [];
|
||||
const files = [];
|
||||
const existingFiles: Partial<EpisodeFile>[] = [];
|
||||
const files: InteractiveImportCommandOptions[] = [];
|
||||
|
||||
if (finalImportMode === 'chooseImportMode') {
|
||||
setInteractiveImportErrorMessage('An import mode must be selected');
|
||||
@@ -511,16 +531,18 @@ function InteractiveImportModalContent(
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const onSortPress = useCallback(
|
||||
const onSortPress = useCallback<SortCallback>(
|
||||
(sortKey, sortDirection) => {
|
||||
dispatch(setInteractiveImportSort({ sortKey, sortDirection }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onFilterExistingFilesChange = useCallback(
|
||||
const onFilterExistingFilesChange = useCallback<
|
||||
(value: FilterExistingFiles) => void
|
||||
>(
|
||||
(value) => {
|
||||
const filter = value !== filterExistingFilesOptions.ALL;
|
||||
const filter = value !== 'all';
|
||||
|
||||
setFilterExistingFiles(filter);
|
||||
|
||||
@@ -536,14 +558,18 @@ function InteractiveImportModalContent(
|
||||
[downloadId, seriesId, folder, setFilterExistingFiles, dispatch]
|
||||
);
|
||||
|
||||
const onImportModeChange = useCallback(
|
||||
const onImportModeChange = useCallback<
|
||||
({ value }: { value: ImportMode }) => void
|
||||
>(
|
||||
({ value }) => {
|
||||
dispatch(setInteractiveImportMode({ importMode: value }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onSelectModalSelect = useCallback(
|
||||
const onSelectModalSelect = useCallback<
|
||||
({ value }: { value: SelectType }) => void
|
||||
>(
|
||||
({ value }) => {
|
||||
setSelectModalOpen(value);
|
||||
},
|
||||
@@ -555,7 +581,7 @@ function InteractiveImportModalContent(
|
||||
}, [setSelectModalOpen]);
|
||||
|
||||
const onSeriesSelect = useCallback(
|
||||
(series) => {
|
||||
(series: Series) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -573,7 +599,7 @@ function InteractiveImportModalContent(
|
||||
);
|
||||
|
||||
const onSeasonSelect = useCallback(
|
||||
(seasonNumber) => {
|
||||
(seasonNumber: number) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -590,7 +616,7 @@ function InteractiveImportModalContent(
|
||||
);
|
||||
|
||||
const onEpisodesSelect = useCallback(
|
||||
(episodes) => {
|
||||
(episodes: SelectedEpisode[]) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -606,7 +632,7 @@ function InteractiveImportModalContent(
|
||||
);
|
||||
|
||||
const onReleaseGroupSelect = useCallback(
|
||||
(releaseGroup) => {
|
||||
(releaseGroup: string) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -622,7 +648,7 @@ function InteractiveImportModalContent(
|
||||
);
|
||||
|
||||
const onLanguagesSelect = useCallback(
|
||||
(newLanguages) => {
|
||||
(newLanguages: Language[]) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -638,7 +664,7 @@ function InteractiveImportModalContent(
|
||||
);
|
||||
|
||||
const onQualitySelect = useCallback(
|
||||
(quality) => {
|
||||
(quality: QualityModel) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
@@ -653,7 +679,7 @@ function InteractiveImportModalContent(
|
||||
[selectedIds, dispatch]
|
||||
);
|
||||
|
||||
const orderedSelectedIds = items.reduce((acc, file) => {
|
||||
const orderedSelectedIds = items.reduce((acc: number[], file) => {
|
||||
if (selectedIds.includes(file.id)) {
|
||||
acc.push(file.id);
|
||||
}
|
||||
@@ -690,7 +716,7 @@ function InteractiveImportModalContent(
|
||||
|
||||
<MenuContent>
|
||||
<SelectedMenuItem
|
||||
name={filterExistingFilesOptions.ALL}
|
||||
name={'all'}
|
||||
isSelected={!filterExistingFiles}
|
||||
onPress={onFilterExistingFilesChange}
|
||||
>
|
||||
@@ -698,7 +724,7 @@ function InteractiveImportModalContent(
|
||||
</SelectedMenuItem>
|
||||
|
||||
<SelectedMenuItem
|
||||
name={filterExistingFilesOptions.NEW}
|
||||
name={'new'}
|
||||
isSelected={filterExistingFiles}
|
||||
onPress={onFilterExistingFilesChange}
|
||||
>
|
||||
@@ -733,7 +759,6 @@ function InteractiveImportModalContent(
|
||||
isSelected={selectedState[item.id]}
|
||||
{...item}
|
||||
allowSeriesChange={allowSeriesChange}
|
||||
autoSelectRow={autoSelectRow}
|
||||
columns={columns}
|
||||
modalTitle={modalTitle}
|
||||
onSelectedChange={onSelectedChange}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
reprocessInteractiveImportItems,
|
||||
updateInteractiveImportItem,
|
||||
} from 'Store/Actions/interactiveImportActions';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import Rejection from 'typings/Rejection';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
@@ -40,6 +41,10 @@ type SelectType =
|
||||
| 'quality'
|
||||
| 'language';
|
||||
|
||||
type SelectedChangeProps = SelectStateInputProps & {
|
||||
hasEpisodeFileId: boolean;
|
||||
};
|
||||
|
||||
interface InteractiveImportRowProps {
|
||||
id: number;
|
||||
allowSeriesChange: boolean;
|
||||
@@ -58,7 +63,7 @@ interface InteractiveImportRowProps {
|
||||
isReprocessing?: boolean;
|
||||
isSelected?: boolean;
|
||||
modalTitle: string;
|
||||
onSelectedChange(...args: unknown[]): void;
|
||||
onSelectedChange(result: SelectedChangeProps): void;
|
||||
onValidRowChange(id: number, isValid: boolean): void;
|
||||
}
|
||||
|
||||
@@ -88,7 +93,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isSeriesColumnVisible = useMemo(
|
||||
() => columns.find((c) => c.name === 'series').isVisible,
|
||||
() => columns.find((c) => c.name === 'series')?.isVisible ?? false,
|
||||
[columns]
|
||||
);
|
||||
|
||||
@@ -110,6 +115,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
id,
|
||||
hasEpisodeFileId: !!episodeFileId,
|
||||
value: true,
|
||||
shiftKey: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -143,7 +149,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
]);
|
||||
|
||||
const onSelectedChangeWrapper = useCallback(
|
||||
(result) => {
|
||||
(result: SelectedChangeProps) => {
|
||||
onSelectedChange({
|
||||
...result,
|
||||
hasEpisodeFileId: !!episodeFileId,
|
||||
@@ -158,6 +164,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
id,
|
||||
hasEpisodeFileId: !!episodeFileId,
|
||||
value: true,
|
||||
shiftKey: false,
|
||||
});
|
||||
}
|
||||
}, [id, episodeFileId, isSelected, onSelectedChange]);
|
||||
@@ -312,9 +319,10 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
);
|
||||
});
|
||||
|
||||
const requiresSeasonNumber = isNaN(Number(seasonNumber));
|
||||
const showSeriesPlaceholder = isSelected && !series;
|
||||
const showSeasonNumberPlaceholder =
|
||||
isSelected && !!series && isNaN(seasonNumber) && !isReprocessing;
|
||||
isSelected && !!series && requiresSeasonNumber && !isReprocessing;
|
||||
const showEpisodeNumbersPlaceholder =
|
||||
isSelected && Number.isInteger(seasonNumber) && !episodes.length;
|
||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||
@@ -364,9 +372,11 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
isDisabled={!series || isNaN(seasonNumber)}
|
||||
isDisabled={!series || requiresSeasonNumber}
|
||||
title={
|
||||
series && !isNaN(seasonNumber) ? 'Click to change episode' : undefined
|
||||
series && !requiresSeasonNumber
|
||||
? 'Click to change episode'
|
||||
: undefined
|
||||
}
|
||||
onPress={onSelectEpisodePress}
|
||||
>
|
||||
@@ -456,7 +466,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
|
||||
<SelectSeasonModal
|
||||
isOpen={selectModalOpen === 'season'}
|
||||
seriesId={series && series.id}
|
||||
seriesId={series?.id}
|
||||
modalTitle={modalTitle}
|
||||
onSeasonSelect={onSeasonSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
@@ -465,7 +475,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
<SelectEpisodeModal
|
||||
isOpen={selectModalOpen === 'episode'}
|
||||
selectedIds={[id]}
|
||||
seriesId={series && series.id}
|
||||
seriesId={series?.id}
|
||||
isAnime={isAnime}
|
||||
seasonNumber={seasonNumber}
|
||||
selectedDetails={relativePath}
|
||||
|
||||
@@ -3,6 +3,19 @@ import Episode from 'Episode/Episode';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import Series from 'Series/Series';
|
||||
import Rejection from 'typings/Rejection';
|
||||
|
||||
export interface InteractiveImportCommandOptions {
|
||||
path: string;
|
||||
folderName: string;
|
||||
seriesId: number;
|
||||
episodeIds: number[];
|
||||
releaseGroup?: string;
|
||||
quality: QualityModel;
|
||||
languages: Language[];
|
||||
downloadId?: string;
|
||||
episodeFileId?: number;
|
||||
}
|
||||
|
||||
interface InteractiveImport extends ModelBase {
|
||||
path: string;
|
||||
@@ -18,7 +31,7 @@ interface InteractiveImport extends ModelBase {
|
||||
episodes: Episode[];
|
||||
qualityWeight: number;
|
||||
customFormats: object[];
|
||||
rejections: string[];
|
||||
rejections: Rejection[];
|
||||
episodeFileId?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ function InteractiveImportModal(props: InteractiveImportModalProps) {
|
||||
const previousIsOpen = usePrevious(isOpen);
|
||||
|
||||
const onFolderSelect = useCallback(
|
||||
(f) => {
|
||||
setFolderPath(f);
|
||||
(path: string) => {
|
||||
setFolderPath(path);
|
||||
},
|
||||
[setFolderPath]
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { LanguageSettingsAppState } from 'App/State/SettingsAppState';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -25,11 +26,12 @@ interface SelectLanguageModalContentProps {
|
||||
|
||||
function createFilteredLanguagesSelector() {
|
||||
return createSelector(createLanguagesSelector(), (languages) => {
|
||||
const { isFetching, isPopulated, error, items } = languages;
|
||||
const { isFetching, isPopulated, error, items } =
|
||||
languages as LanguageSettingsAppState;
|
||||
|
||||
const filterItems = ['Any', 'Original'];
|
||||
const filteredLanguages = items.filter(
|
||||
(lang) => !filterItems.includes(lang.name)
|
||||
(lang: Language) => !filterItems.includes(lang.name)
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -51,7 +53,7 @@ function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
|
||||
const [languageIds, setLanguageIds] = useState(props.languageIds);
|
||||
|
||||
const onLanguageChange = useCallback(
|
||||
({ value, name }) => {
|
||||
({ name, value }: { name: string; value: boolean }) => {
|
||||
const changedId = parseInt(name);
|
||||
|
||||
let newLanguages = [...languageIds];
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Error } from 'App/State/AppSectionState';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -12,22 +14,32 @@ 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 { QualityModel } from 'Quality/Quality';
|
||||
import Quality, { QualityModel } from 'Quality/Quality';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
|
||||
function createQualitySchemeSelctor() {
|
||||
interface QualitySchemaState {
|
||||
isFetching: boolean;
|
||||
isPopulated: boolean;
|
||||
error: Error;
|
||||
items: Quality[];
|
||||
}
|
||||
|
||||
function createQualitySchemaSelector() {
|
||||
return createSelector(
|
||||
(state) => state.settings.qualityProfiles,
|
||||
(qualityProfiles) => {
|
||||
(state: AppState) => state.settings.qualityProfiles,
|
||||
(qualityProfiles): QualitySchemaState => {
|
||||
const { isSchemaFetching, isSchemaPopulated, schemaError, schema } =
|
||||
qualityProfiles;
|
||||
|
||||
const items = getQualities(schema.items) as Quality[];
|
||||
|
||||
return {
|
||||
isFetching: isSchemaFetching,
|
||||
isPopulated: isSchemaPopulated,
|
||||
error: schemaError,
|
||||
items: getQualities(schema.items),
|
||||
items,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -50,7 +62,7 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
|
||||
const [real, setReal] = useState(props.real);
|
||||
|
||||
const { isFetching, isPopulated, error, items } = useSelector(
|
||||
createQualitySchemeSelctor()
|
||||
createQualitySchemaSelector()
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -72,28 +84,28 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
|
||||
}, [items]);
|
||||
|
||||
const onQualityChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setQualityId(parseInt(value));
|
||||
},
|
||||
[setQualityId]
|
||||
);
|
||||
|
||||
const onProperChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: CheckInputChanged) => {
|
||||
setProper(value);
|
||||
},
|
||||
[setProper]
|
||||
);
|
||||
|
||||
const onRealChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: CheckInputChanged) => {
|
||||
setReal(value);
|
||||
},
|
||||
[setReal]
|
||||
);
|
||||
|
||||
const onQualitySelectWrapper = useCallback(() => {
|
||||
const quality = items.find((item) => item.id === qualityId);
|
||||
const quality = items.find((item) => item.id === qualityId) as Quality;
|
||||
|
||||
const revision = {
|
||||
version: proper ? 2 : 1,
|
||||
|
||||
@@ -25,7 +25,7 @@ function SelectReleaseGroupModalContent(
|
||||
const [releaseGroup, setReleaseGroup] = useState(props.releaseGroup);
|
||||
|
||||
const onReleaseGroupChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setReleaseGroup(value);
|
||||
},
|
||||
[setReleaseGroup]
|
||||
|
||||
@@ -5,8 +5,8 @@ import SelectSeasonModalContent from './SelectSeasonModalContent';
|
||||
interface SelectSeasonModalProps {
|
||||
isOpen: boolean;
|
||||
modalTitle: string;
|
||||
seriesId: number;
|
||||
onSeasonSelect(seasonNumber): void;
|
||||
seriesId?: number;
|
||||
onSeasonSelect(seasonNumber: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,21 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { Season } from 'Series/Series';
|
||||
import { createSeriesSelectorForHook } from 'Store/Selectors/createSeriesSelector';
|
||||
import SelectSeasonRow from './SelectSeasonRow';
|
||||
|
||||
interface SelectSeasonModalContentProps {
|
||||
seriesId: number;
|
||||
seriesId?: number;
|
||||
modalTitle: string;
|
||||
onSeasonSelect(seasonNumber): void;
|
||||
onSeasonSelect(seasonNumber: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function SelectSeasonModalContent(props: SelectSeasonModalContentProps) {
|
||||
const { seriesId, modalTitle, onSeasonSelect, onModalClose } = props;
|
||||
const series = useSelector(createSeriesSelectorForHook(seriesId));
|
||||
const seasons = useMemo(() => {
|
||||
const seasons = useMemo<Season[]>(() => {
|
||||
return series.seasons.slice(0).reverse();
|
||||
}, [series]);
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ interface SelectSeriesModalContentProps {
|
||||
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||
const { modalTitle, onSeriesSelect, onModalClose } = props;
|
||||
|
||||
const allSeries = useSelector(createAllSeriesSelector());
|
||||
const allSeries: Series[] = useSelector(createAllSeriesSelector());
|
||||
const [filter, setFilter] = useState('');
|
||||
|
||||
const onFilterChange = useCallback(
|
||||
({ value }) => {
|
||||
({ value }: { value: string }) => {
|
||||
setFilter(value);
|
||||
},
|
||||
[setFilter]
|
||||
@@ -34,7 +34,7 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||
|
||||
const onSeriesSelectWrapper = useCallback(
|
||||
(seriesId: number) => {
|
||||
const series = allSeries.find((s) => s.id === seriesId);
|
||||
const series = allSeries.find((s) => s.id === seriesId) as Series;
|
||||
|
||||
onSeriesSelect(series);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user