import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import Icon from 'Components/Icon'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import Column from 'Components/Table/Column'; import TableRow from 'Components/Table/TableRow'; import Popover from 'Components/Tooltip/Popover'; import Episode from 'Episode/Episode'; import EpisodeFormats from 'Episode/EpisodeFormats'; import EpisodeLanguages from 'Episode/EpisodeLanguages'; import EpisodeQuality from 'Episode/EpisodeQuality'; import getReleaseTypeName from 'Episode/getReleaseTypeName'; import IndexerFlags from 'Episode/IndexerFlags'; import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal'; import { SelectedEpisode } from 'InteractiveImport/Episode/SelectEpisodeModalContent'; import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import ReleaseType from 'InteractiveImport/ReleaseType'; import SelectReleaseTypeModal from 'InteractiveImport/ReleaseType/SelectReleaseTypeModal'; 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 { reprocessInteractiveImportItems, updateInteractiveImportItem, } from 'Store/Actions/interactiveImportActions'; import { SelectStateInputProps } from 'typings/props'; import Rejection from 'typings/Rejection'; import formatBytes from 'Utilities/Number/formatBytes'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; import translate from 'Utilities/String/translate'; import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder'; import styles from './InteractiveImportRow.css'; type SelectType = | 'series' | 'season' | 'episode' | 'releaseGroup' | 'quality' | 'language' | 'indexerFlags' | 'releaseType'; type SelectedChangeProps = SelectStateInputProps & { hasEpisodeFileId: boolean; }; interface InteractiveImportRowProps { id: number; allowSeriesChange: boolean; relativePath: string; series?: Series; seasonNumber?: number; episodes?: Episode[]; releaseGroup?: string; quality?: QualityModel; languages?: Language[]; size: number; releaseType: ReleaseType; customFormats?: object[]; customFormatScore?: number; indexerFlags: number; rejections: Rejection[]; columns: Column[]; episodeFileId?: number; isReprocessing?: boolean; isSelected?: boolean; modalTitle: string; onSelectedChange(result: SelectedChangeProps): void; onValidRowChange(id: number, isValid: boolean): void; } function InteractiveImportRow(props: InteractiveImportRowProps) { const { id, allowSeriesChange, relativePath, series, seasonNumber, episodes = [], quality, languages, releaseGroup, size, releaseType, customFormats, customFormatScore, indexerFlags, rejections, isReprocessing, isSelected, modalTitle, episodeFileId, columns, onSelectedChange, onValidRowChange, } = props; const dispatch = useDispatch(); const isSeriesColumnVisible = useMemo( () => columns.find((c) => c.name === 'series')?.isVisible ?? false, [columns] ); const isIndexerFlagsColumnVisible = useMemo( () => columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false, [columns] ); const [selectModalOpen, setSelectModalOpen] = useState( null ); useEffect( () => { if ( allowSeriesChange && series && seasonNumber != null && episodes.length && quality && languages && size > 0 ) { onSelectedChange({ id, hasEpisodeFileId: !!episodeFileId, value: true, shiftKey: false, }); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [] ); useEffect(() => { const isValid = !!( series && seasonNumber != null && episodes.length && quality && languages ); if (isSelected && !isValid) { onValidRowChange(id, false); } else { onValidRowChange(id, true); } }, [ id, series, seasonNumber, episodes, quality, languages, isSelected, onValidRowChange, ]); const onSelectedChangeWrapper = useCallback( (result: SelectedChangeProps) => { onSelectedChange({ ...result, hasEpisodeFileId: !!episodeFileId, }); }, [episodeFileId, onSelectedChange] ); const selectRowAfterChange = useCallback(() => { if (!isSelected) { onSelectedChange({ id, hasEpisodeFileId: !!episodeFileId, value: true, shiftKey: false, }); } }, [id, episodeFileId, isSelected, onSelectedChange]); const onSelectModalClose = useCallback(() => { setSelectModalOpen(null); }, [setSelectModalOpen]); const onSelectSeriesPress = useCallback(() => { setSelectModalOpen('series'); }, [setSelectModalOpen]); const onSeriesSelect = useCallback( (series: Series) => { dispatch( updateInteractiveImportItem({ id, series, seasonNumber: undefined, episodes: [], }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectSeasonPress = useCallback(() => { setSelectModalOpen('season'); }, [setSelectModalOpen]); const onSeasonSelect = useCallback( (seasonNumber: number) => { dispatch( updateInteractiveImportItem({ id, seasonNumber, episodes: [], }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectEpisodePress = useCallback(() => { setSelectModalOpen('episode'); }, [setSelectModalOpen]); const onEpisodesSelect = useCallback( (selectedEpisodes: SelectedEpisode[]) => { dispatch( updateInteractiveImportItem({ id, episodes: selectedEpisodes[0].episodes, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectReleaseGroupPress = useCallback(() => { setSelectModalOpen('releaseGroup'); }, [setSelectModalOpen]); const onReleaseGroupSelect = useCallback( (releaseGroup: string) => { dispatch( updateInteractiveImportItem({ id, releaseGroup, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectQualityPress = useCallback(() => { setSelectModalOpen('quality'); }, [setSelectModalOpen]); const onQualitySelect = useCallback( (quality: QualityModel) => { dispatch( updateInteractiveImportItem({ id, quality, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectLanguagePress = useCallback(() => { setSelectModalOpen('language'); }, [setSelectModalOpen]); const onLanguagesSelect = useCallback( (languages: Language[]) => { dispatch( updateInteractiveImportItem({ id, languages, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectReleaseTypePress = useCallback(() => { setSelectModalOpen('releaseType'); }, [setSelectModalOpen]); const onReleaseTypeSelect = useCallback( (releaseType: ReleaseType) => { dispatch( updateInteractiveImportItem({ id, releaseType, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const onSelectIndexerFlagsPress = useCallback(() => { setSelectModalOpen('indexerFlags'); }, [setSelectModalOpen]); const onIndexerFlagsSelect = useCallback( (indexerFlags: number) => { dispatch( updateInteractiveImportItem({ id, indexerFlags, }) ); dispatch(reprocessInteractiveImportItems({ ids: [id] })); setSelectModalOpen(null); selectRowAfterChange(); }, [id, dispatch, setSelectModalOpen, selectRowAfterChange] ); const seriesTitle = series ? series.title : ''; const isAnime = series?.seriesType === 'anime'; const episodeInfo = episodes.map((episode) => { return (
{episode.episodeNumber} {isAnime && episode.absoluteEpisodeNumber != null ? ` (${episode.absoluteEpisodeNumber})` : ''} {` - ${episode.title}`}
); }); const requiresSeasonNumber = isNaN(Number(seasonNumber)); const showSeriesPlaceholder = isSelected && !series; const showSeasonNumberPlaceholder = isSelected && !!series && requiresSeasonNumber && !isReprocessing; const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length; const showReleaseGroupPlaceholder = isSelected && !releaseGroup; const showQualityPlaceholder = isSelected && !quality; const showLanguagePlaceholder = isSelected && !languages; const showIndexerFlagsPlaceholder = isSelected && !indexerFlags; return ( {relativePath} {isSeriesColumnVisible ? ( {showSeriesPlaceholder ? ( ) : ( seriesTitle )} ) : null} {showSeasonNumberPlaceholder ? ( ) : ( seasonNumber )} {isReprocessing && seasonNumber == null ? ( ) : null} {showEpisodeNumbersPlaceholder ? ( ) : ( episodeInfo )} {showReleaseGroupPlaceholder ? ( ) : ( releaseGroup )} {showQualityPlaceholder && } {!showQualityPlaceholder && !!quality && ( )} {showLanguagePlaceholder && } {!showLanguagePlaceholder && !!languages && ( )} {formatBytes(size)} {getReleaseTypeName(releaseType)} {customFormats?.length ? ( } position={tooltipPositions.LEFT} /> ) : null} {isIndexerFlagsColumnVisible ? ( {showIndexerFlagsPlaceholder ? ( ) : ( <> {indexerFlags ? ( } title={translate('IndexerFlags')} body={} position={tooltipPositions.LEFT} /> ) : null} )} ) : null} {rejections.length ? ( } title={translate('ReleaseRejected')} body={
    {rejections.map((rejection, index) => { return
  • {rejection.reason}
  • ; })}
} position={tooltipPositions.LEFT} canFlip={false} /> ) : null}
1 : false} real={quality ? quality.revision.real > 0 : false} modalTitle={modalTitle} onQualitySelect={onQualitySelect} onModalClose={onSelectModalClose} /> l.id) : []} modalTitle={modalTitle} onLanguagesSelect={onLanguagesSelect} onModalClose={onSelectModalClose} />
); } export default InteractiveImportRow;