parent
546e9fd1d0
commit
0a0e03dca0
@ -0,0 +1,10 @@
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Release from 'typings/Release';
|
||||
|
||||
interface ReleasesAppState
|
||||
extends AppSectionState<Release>,
|
||||
AppSectionFilterState<Release> {}
|
||||
|
||||
export default ReleasesAppState;
|
@ -1,234 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||
import InteractiveSearchRow from './InteractiveSearchRow';
|
||||
import styles from './InteractiveSearch.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: () => translate('Source'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: () => translate('Age'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: () => translate('Title'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: () => translate('Indexer'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: () => translate('Size'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: () => translate('Peers'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'languageWeight',
|
||||
label: () => translate('Languages'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityWeight',
|
||||
label: () => translate('Quality'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: () => translate('CustomFormatScore')
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexerFlags',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.FLAG,
|
||||
title: () => translate('IndexerFlags')
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.DANGER,
|
||||
title: () => translate('Rejections')
|
||||
}),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
function InteractiveSearch(props) {
|
||||
const {
|
||||
searchPayload,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
totalReleasesCount,
|
||||
items,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
type,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onSortPress,
|
||||
onFilterSelect,
|
||||
onGrabPress
|
||||
} = props;
|
||||
|
||||
const errorMessage = getErrorMessage(error);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.filterMenuContainer}>
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
buttonComponent={PageMenuButton}
|
||||
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
||||
filterModalConnectorComponentProps={{ type }}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
isFetching ? <LoadingIndicator /> : null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && error ?
|
||||
<div>
|
||||
{
|
||||
errorMessage ?
|
||||
<Fragment>
|
||||
{translate('InteractiveSearchResultsSeriesFailedErrorMessage', { message: errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1) })}
|
||||
</Fragment> :
|
||||
translate('EpisodeSearchResultsLoadError')
|
||||
}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && isPopulated && !totalReleasesCount ?
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoResultsFound')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!!totalReleasesCount && isPopulated && !items.length ?
|
||||
<Alert kind={kinds.WARNING}>
|
||||
{translate('AllResultsAreHiddenByTheAppliedFilter')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !!items.length ?
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<InteractiveSearchRow
|
||||
key={`${item.indexerId}-${item.guid}`}
|
||||
{...item}
|
||||
searchPayload={searchPayload}
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onGrabPress={onGrabPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
totalReleasesCount !== items.length && !!items.length ?
|
||||
<div className={styles.filteredMessage}>
|
||||
{translate('SomeResultsAreHiddenByTheAppliedFilter')}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InteractiveSearch.propTypes = {
|
||||
searchPayload: PropTypes.object.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
totalReleasesCount: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default InteractiveSearch;
|
@ -0,0 +1,247 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
|
||||
import ReleasesAppState from 'App/State/ReleasesAppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||
import Column from 'Components/Table/Column';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import InteractiveSearchType from 'InteractiveSearch/InteractiveSearchType';
|
||||
import {
|
||||
fetchReleases,
|
||||
grabRelease,
|
||||
setEpisodeReleasesFilter,
|
||||
setReleasesSort,
|
||||
setSeasonReleasesFilter,
|
||||
} from 'Store/Actions/releaseActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveSearchFilterModal from './InteractiveSearchFilterModal';
|
||||
import InteractiveSearchRow from './InteractiveSearchRow';
|
||||
import styles from './InteractiveSearch.css';
|
||||
|
||||
const columns: Column[] = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: () => translate('Source'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: () => translate('Age'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: () => translate('Title'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: () => translate('Indexer'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: () => translate('Size'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: () => translate('Peers'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'languageWeight',
|
||||
label: () => translate('Languages'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'qualityWeight',
|
||||
label: () => translate('Quality'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: () => translate('CustomFormatScore'),
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'indexerFlags',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.FLAG,
|
||||
title: () => translate('IndexerFlags'),
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.DANGER,
|
||||
title: () => translate('Rejections'),
|
||||
}),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true,
|
||||
},
|
||||
];
|
||||
|
||||
interface InteractiveSearchProps {
|
||||
type: InteractiveSearchType;
|
||||
searchPayload: object;
|
||||
}
|
||||
|
||||
function InteractiveSearch({ type, searchPayload }: InteractiveSearchProps) {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
totalItems,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
}: ReleasesAppState & ClientSideCollectionAppState = useSelector(
|
||||
createClientSideCollectionSelector('releases', `releases.${type}`)
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleFilterSelect = useCallback(
|
||||
(selectedFilterKey: string) => {
|
||||
const action =
|
||||
type === 'episode' ? setEpisodeReleasesFilter : setSeasonReleasesFilter;
|
||||
|
||||
dispatch(action({ selectedFilterKey }));
|
||||
},
|
||||
[type, dispatch]
|
||||
);
|
||||
|
||||
const handleSortPress = useCallback(
|
||||
(sortKey: string, sortDirection: SortDirection) => {
|
||||
dispatch(setReleasesSort({ sortKey, sortDirection }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleGrabPress = useCallback(
|
||||
(payload: object) => {
|
||||
dispatch(grabRelease(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// If search results are not yet isPopulated fetch them,
|
||||
// otherwise re-show the existing props.
|
||||
|
||||
if (!isPopulated) {
|
||||
dispatch(fetchReleases(searchPayload));
|
||||
}
|
||||
}, [isPopulated, searchPayload, dispatch]);
|
||||
|
||||
const errorMessage = getErrorMessage(error);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.filterMenuContainer}>
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
buttonComponent={PageMenuButton}
|
||||
filterModalConnectorComponent={InteractiveSearchFilterModal}
|
||||
filterModalConnectorComponentProps={{ type }}
|
||||
onFilterSelect={handleFilterSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isFetching ? <LoadingIndicator /> : null}
|
||||
|
||||
{!isFetching && error ? (
|
||||
<div>
|
||||
{errorMessage ? (
|
||||
<>
|
||||
{translate('InteractiveSearchResultsSeriesFailedErrorMessage', {
|
||||
message:
|
||||
errorMessage.charAt(0).toLowerCase() + errorMessage.slice(1),
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
translate('EpisodeSearchResultsLoadError')
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isFetching && isPopulated && !totalItems ? (
|
||||
<Alert kind={kinds.INFO}>{translate('NoResultsFound')}</Alert>
|
||||
) : null}
|
||||
|
||||
{!!totalItems && isPopulated && !items.length ? (
|
||||
<Alert kind={kinds.WARNING}>
|
||||
{translate('AllResultsAreHiddenByTheAppliedFilter')}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{isPopulated && !!items.length ? (
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={handleSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<InteractiveSearchRow
|
||||
key={`${item.indexerId}-${item.guid}`}
|
||||
{...item}
|
||||
searchPayload={searchPayload}
|
||||
onGrabPress={handleGrabPress}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : null}
|
||||
|
||||
{totalItems !== items.length && !!items.length ? (
|
||||
<div className={styles.filteredMessage}>
|
||||
{translate('SomeResultsAreHiddenByTheAppliedFilter')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InteractiveSearch;
|
@ -1,95 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import InteractiveSearch from './InteractiveSearch';
|
||||
|
||||
function createMapStateToProps(appState, { type }) {
|
||||
return createSelector(
|
||||
(state) => state.releases.items.length,
|
||||
createClientSideCollectionSelector('releases', `releases.${type}`),
|
||||
createUISettingsSelector(),
|
||||
(totalReleasesCount, releases, uiSettings) => {
|
||||
return {
|
||||
totalReleasesCount,
|
||||
longDateFormat: uiSettings.longDateFormat,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
...releases
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchReleases(payload) {
|
||||
dispatch(releaseActions.fetchReleases(payload));
|
||||
},
|
||||
|
||||
onSortPress(sortKey, sortDirection) {
|
||||
dispatch(releaseActions.setReleasesSort({ sortKey, sortDirection }));
|
||||
},
|
||||
|
||||
onFilterSelect(selectedFilterKey) {
|
||||
const action = props.type === 'episode' ?
|
||||
releaseActions.setEpisodeReleasesFilter :
|
||||
releaseActions.setSeasonReleasesFilter;
|
||||
|
||||
dispatch(action({ selectedFilterKey }));
|
||||
},
|
||||
|
||||
onGrabPress(payload) {
|
||||
dispatch(releaseActions.grabRelease(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class InteractiveSearchConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
searchPayload,
|
||||
isPopulated,
|
||||
dispatchFetchReleases
|
||||
} = this.props;
|
||||
|
||||
// If search results are not yet isPopulated fetch them,
|
||||
// otherwise re-show the existing props.
|
||||
|
||||
if (!isPopulated) {
|
||||
dispatchFetchReleases(searchPayload);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchFetchReleases,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
||||
<InteractiveSearch
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveSearchConnector.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
searchPayload: PropTypes.object.isRequired,
|
||||
isPopulated: PropTypes.bool,
|
||||
dispatchFetchReleases: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);
|
@ -0,0 +1,65 @@
|
||||
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 InteractiveSearchType from 'InteractiveSearch/InteractiveSearchType';
|
||||
import {
|
||||
setEpisodeReleasesFilter,
|
||||
setSeasonReleasesFilter,
|
||||
} from 'Store/Actions/releaseActions';
|
||||
|
||||
function createReleasesSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.releases.items,
|
||||
(releases) => {
|
||||
return releases;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.releases.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface InteractiveSearchFilterModalProps {
|
||||
isOpen: boolean;
|
||||
type: InteractiveSearchType;
|
||||
}
|
||||
|
||||
export default function InteractiveSearchFilterModal({
|
||||
type,
|
||||
...otherProps
|
||||
}: InteractiveSearchFilterModalProps) {
|
||||
const sectionItems = useSelector(createReleasesSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'releases';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
const action =
|
||||
type === 'episode' ? setEpisodeReleasesFilter : setSeasonReleasesFilter;
|
||||
|
||||
dispatch(action(payload));
|
||||
},
|
||||
[type, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...otherProps}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setEpisodeReleasesFilter, setSeasonReleasesFilter } from 'Store/Actions/releaseActions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.releases.items,
|
||||
(state) => state.releases.filterBuilderProps,
|
||||
(sectionItems, filterBuilderProps) => {
|
||||
return {
|
||||
sectionItems,
|
||||
filterBuilderProps,
|
||||
customFilterType: 'releases'
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchSetFilter(payload) {
|
||||
const action = props.type === 'episode' ?
|
||||
setEpisodeReleasesFilter:
|
||||
setSeasonReleasesFilter;
|
||||
|
||||
dispatch(action(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal);
|
@ -0,0 +1,3 @@
|
||||
type InteractiveSearchType = 'episode' | 'season';
|
||||
|
||||
export default InteractiveSearchType;
|
@ -1,10 +0,0 @@
|
||||
interface ReleaseEpisode {
|
||||
id: number;
|
||||
episodeFileId: number;
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
absoluteEpisodeNumber?: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default ReleaseEpisode;
|
@ -0,0 +1,53 @@
|
||||
import type DownloadProtocol from 'DownloadClient/DownloadProtocol';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
|
||||
export interface ReleaseEpisode {
|
||||
id: number;
|
||||
episodeFileId: number;
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
absoluteEpisodeNumber?: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Release {
|
||||
guid: string;
|
||||
protocol: DownloadProtocol;
|
||||
age: number;
|
||||
ageHours: number;
|
||||
ageMinutes: number;
|
||||
publishDate: string;
|
||||
title: string;
|
||||
infoUrl: string;
|
||||
indexerId: number;
|
||||
indexer: string;
|
||||
size: number;
|
||||
seeders?: number;
|
||||
leechers?: number;
|
||||
quality: QualityModel;
|
||||
languages: Language[];
|
||||
customFormats: CustomFormat[];
|
||||
customFormatScore: number;
|
||||
sceneMapping?: object;
|
||||
seasonNumber?: number;
|
||||
episodeNumbers?: number[];
|
||||
absoluteEpisodeNumbers?: number[];
|
||||
mappedSeriesId?: number;
|
||||
mappedSeasonNumber?: number;
|
||||
mappedEpisodeNumbers?: number[];
|
||||
mappedAbsoluteEpisodeNumbers?: number[];
|
||||
mappedEpisodeInfo: ReleaseEpisode[];
|
||||
indexerFlags: number;
|
||||
rejections: string[];
|
||||
episodeRequested: boolean;
|
||||
downloadAllowed: boolean;
|
||||
isDaily: boolean;
|
||||
|
||||
isGrabbing?: boolean;
|
||||
isGrabbed?: boolean;
|
||||
grabError?: string;
|
||||
}
|
||||
|
||||
export default Release;
|
Loading…
Reference in new issue