mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-05 15:47:20 +02:00
New: Blocklist Custom Filters
(cherry picked from commit f81bb3ec1945d343dd0695a2826dac8833cb6346) Closes #9997
This commit is contained in:
parent
0ec18ce4b3
commit
190c4c5893
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
@ -20,6 +21,7 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
|||||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
|
import BlocklistFilterModal from './BlocklistFilterModal';
|
||||||
import BlocklistRowConnector from './BlocklistRowConnector';
|
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||||
|
|
||||||
class Blocklist extends Component {
|
class Blocklist extends Component {
|
||||||
@ -114,9 +116,13 @@ class Blocklist extends Component {
|
|||||||
error,
|
error,
|
||||||
items,
|
items,
|
||||||
columns,
|
columns,
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
totalRecords,
|
totalRecords,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isClearingBlocklistExecuting,
|
isClearingBlocklistExecuting,
|
||||||
|
onFilterSelect,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -161,6 +167,15 @@ class Blocklist extends Component {
|
|||||||
iconName={icons.TABLE}
|
iconName={icons.TABLE}
|
||||||
/>
|
/>
|
||||||
</TableOptionsModalWrapper>
|
</TableOptionsModalWrapper>
|
||||||
|
|
||||||
|
<FilterMenu
|
||||||
|
alignMenu={align.RIGHT}
|
||||||
|
selectedFilterKey={selectedFilterKey}
|
||||||
|
filters={filters}
|
||||||
|
customFilters={customFilters}
|
||||||
|
filterModalConnectorComponent={BlocklistFilterModal}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
</PageToolbar>
|
</PageToolbar>
|
||||||
|
|
||||||
@ -180,7 +195,11 @@ class Blocklist extends Component {
|
|||||||
{
|
{
|
||||||
isPopulated && !error && !items.length &&
|
isPopulated && !error && !items.length &&
|
||||||
<Alert kind={kinds.INFO}>
|
<Alert kind={kinds.INFO}>
|
||||||
{translate('NoHistoryBlocklist')}
|
{
|
||||||
|
selectedFilterKey === 'all' ?
|
||||||
|
translate('NoHistoryBlocklist') :
|
||||||
|
translate('BlocklistFilterHasNoItems')
|
||||||
|
}
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,11 +270,15 @@ Blocklist.propTypes = {
|
|||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: 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,
|
||||||
totalRecords: PropTypes.number,
|
totalRecords: PropTypes.number,
|
||||||
isRemoving: PropTypes.bool.isRequired,
|
isRemoving: PropTypes.bool.isRequired,
|
||||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||||
onRemoveSelected: PropTypes.func.isRequired,
|
onRemoveSelected: PropTypes.func.isRequired,
|
||||||
onClearBlocklistPress: PropTypes.func.isRequired
|
onClearBlocklistPress: PropTypes.func.isRequired,
|
||||||
|
onFilterSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Blocklist;
|
export default Blocklist;
|
||||||
|
@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import withCurrentPage from 'Components/withCurrentPage';
|
import withCurrentPage from 'Components/withCurrentPage';
|
||||||
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||||
import Blocklist from './Blocklist';
|
import Blocklist from './Blocklist';
|
||||||
@ -13,10 +14,12 @@ import Blocklist from './Blocklist';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.blocklist,
|
(state) => state.blocklist,
|
||||||
|
createCustomFiltersSelector('blocklist'),
|
||||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
||||||
(blocklist, isClearingBlocklistExecuting) => {
|
(blocklist, customFilters, isClearingBlocklistExecuting) => {
|
||||||
return {
|
return {
|
||||||
isClearingBlocklistExecuting,
|
isClearingBlocklistExecuting,
|
||||||
|
customFilters,
|
||||||
...blocklist
|
...blocklist
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -97,6 +100,14 @@ class BlocklistConnector extends Component {
|
|||||||
this.props.setBlocklistSort({ sortKey });
|
this.props.setBlocklistSort({ sortKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onFilterSelect = (selectedFilterKey) => {
|
||||||
|
this.props.setBlocklistFilter({ selectedFilterKey });
|
||||||
|
};
|
||||||
|
|
||||||
|
onClearBlocklistPress = () => {
|
||||||
|
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||||
|
};
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
onTableOptionChange = (payload) => {
|
||||||
this.props.setBlocklistTableOption(payload);
|
this.props.setBlocklistTableOption(payload);
|
||||||
|
|
||||||
@ -105,10 +116,6 @@ class BlocklistConnector extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClearBlocklistPress = () => {
|
|
||||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@ -122,6 +129,7 @@ class BlocklistConnector extends Component {
|
|||||||
onPageSelect={this.onPageSelect}
|
onPageSelect={this.onPageSelect}
|
||||||
onRemoveSelected={this.onRemoveSelected}
|
onRemoveSelected={this.onRemoveSelected}
|
||||||
onSortPress={this.onSortPress}
|
onSortPress={this.onSortPress}
|
||||||
|
onFilterSelect={this.onFilterSelect}
|
||||||
onTableOptionChange={this.onTableOptionChange}
|
onTableOptionChange={this.onTableOptionChange}
|
||||||
onClearBlocklistPress={this.onClearBlocklistPress}
|
onClearBlocklistPress={this.onClearBlocklistPress}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
@ -142,6 +150,7 @@ BlocklistConnector.propTypes = {
|
|||||||
gotoBlocklistPage: PropTypes.func.isRequired,
|
gotoBlocklistPage: PropTypes.func.isRequired,
|
||||||
removeBlocklistItems: PropTypes.func.isRequired,
|
removeBlocklistItems: PropTypes.func.isRequired,
|
||||||
setBlocklistSort: PropTypes.func.isRequired,
|
setBlocklistSort: PropTypes.func.isRequired,
|
||||||
|
setBlocklistFilter: PropTypes.func.isRequired,
|
||||||
setBlocklistTableOption: PropTypes.func.isRequired,
|
setBlocklistTableOption: PropTypes.func.isRequired,
|
||||||
clearBlocklist: PropTypes.func.isRequired,
|
clearBlocklist: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
|
54
frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx
Normal file
54
frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
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 { setBlocklistFilter } from 'Store/Actions/blocklistActions';
|
||||||
|
|
||||||
|
function createBlocklistSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.blocklist.items,
|
||||||
|
(blocklistItems) => {
|
||||||
|
return blocklistItems;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilterBuilderPropsSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.blocklist.filterBuilderProps,
|
||||||
|
(filterBuilderProps) => {
|
||||||
|
return filterBuilderProps;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlocklistFilterModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BlocklistFilterModal(props: BlocklistFilterModalProps) {
|
||||||
|
const sectionItems = useSelector(createBlocklistSelector());
|
||||||
|
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||||
|
const customFilterType = 'blocklist';
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const dispatchSetFilter = useCallback(
|
||||||
|
(payload: unknown) => {
|
||||||
|
dispatch(setBlocklistFilter(payload));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterModal
|
||||||
|
// TODO: Don't spread all the props
|
||||||
|
{...props}
|
||||||
|
sectionItems={sectionItems}
|
||||||
|
filterBuilderProps={filterBuilderProps}
|
||||||
|
customFilterType={customFilterType}
|
||||||
|
dispatchSetFilter={dispatchSetFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||||
|
import BlocklistAppState from './BlocklistAppState';
|
||||||
import CalendarAppState from './CalendarAppState';
|
import CalendarAppState from './CalendarAppState';
|
||||||
import CommandAppState from './CommandAppState';
|
import CommandAppState from './CommandAppState';
|
||||||
import HistoryAppState from './HistoryAppState';
|
import HistoryAppState from './HistoryAppState';
|
||||||
@ -54,6 +55,7 @@ export interface AppSectionState {
|
|||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
app: AppSectionState;
|
app: AppSectionState;
|
||||||
|
blocklist: BlocklistAppState;
|
||||||
calendar: CalendarAppState;
|
calendar: CalendarAppState;
|
||||||
commands: CommandAppState;
|
commands: CommandAppState;
|
||||||
history: HistoryAppState;
|
history: HistoryAppState;
|
||||||
|
8
frontend/src/App/State/BlocklistAppState.ts
Normal file
8
frontend/src/App/State/BlocklistAppState.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Blocklist from 'typings/Blocklist';
|
||||||
|
import AppSectionState, { AppSectionFilterState } from './AppSectionState';
|
||||||
|
|
||||||
|
interface BlocklistAppState
|
||||||
|
extends AppSectionState<Blocklist>,
|
||||||
|
AppSectionFilterState<Blocklist> {}
|
||||||
|
|
||||||
|
export default BlocklistAppState;
|
@ -1,6 +1,6 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||||
@ -79,6 +79,31 @@ export const defaultState = {
|
|||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
selectedFilterKey: 'all',
|
||||||
|
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'all',
|
||||||
|
label: () => translate('All'),
|
||||||
|
filters: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
filterBuilderProps: [
|
||||||
|
{
|
||||||
|
name: 'movieIds',
|
||||||
|
label: () => translate('Movie'),
|
||||||
|
type: filterBuilderTypes.EQUAL,
|
||||||
|
valueType: filterBuilderValueTypes.MOVIE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'protocols',
|
||||||
|
label: () => translate('Protocol'),
|
||||||
|
type: filterBuilderTypes.EQUAL,
|
||||||
|
valueType: filterBuilderValueTypes.PROTOCOL
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,6 +111,7 @@ export const persistState = [
|
|||||||
'blocklist.pageSize',
|
'blocklist.pageSize',
|
||||||
'blocklist.sortKey',
|
'blocklist.sortKey',
|
||||||
'blocklist.sortDirection',
|
'blocklist.sortDirection',
|
||||||
|
'blocklist.selectedFilterKey',
|
||||||
'blocklist.columns'
|
'blocklist.columns'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -99,6 +125,7 @@ export const GOTO_NEXT_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistNextPage';
|
|||||||
export const GOTO_LAST_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistLastPage';
|
export const GOTO_LAST_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistLastPage';
|
||||||
export const GOTO_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistPage';
|
export const GOTO_BLOCKLIST_PAGE = 'blocklist/gotoBlocklistPage';
|
||||||
export const SET_BLOCKLIST_SORT = 'blocklist/setBlocklistSort';
|
export const SET_BLOCKLIST_SORT = 'blocklist/setBlocklistSort';
|
||||||
|
export const SET_BLOCKLIST_FILTER = 'blocklist/setBlocklistFilter';
|
||||||
export const SET_BLOCKLIST_TABLE_OPTION = 'blocklist/setBlocklistTableOption';
|
export const SET_BLOCKLIST_TABLE_OPTION = 'blocklist/setBlocklistTableOption';
|
||||||
export const REMOVE_BLOCKLIST_ITEM = 'blocklist/removeBlocklistItem';
|
export const REMOVE_BLOCKLIST_ITEM = 'blocklist/removeBlocklistItem';
|
||||||
export const REMOVE_BLOCKLIST_ITEMS = 'blocklist/removeBlocklistItems';
|
export const REMOVE_BLOCKLIST_ITEMS = 'blocklist/removeBlocklistItems';
|
||||||
@ -114,6 +141,7 @@ export const gotoBlocklistNextPage = createThunk(GOTO_NEXT_BLOCKLIST_PAGE);
|
|||||||
export const gotoBlocklistLastPage = createThunk(GOTO_LAST_BLOCKLIST_PAGE);
|
export const gotoBlocklistLastPage = createThunk(GOTO_LAST_BLOCKLIST_PAGE);
|
||||||
export const gotoBlocklistPage = createThunk(GOTO_BLOCKLIST_PAGE);
|
export const gotoBlocklistPage = createThunk(GOTO_BLOCKLIST_PAGE);
|
||||||
export const setBlocklistSort = createThunk(SET_BLOCKLIST_SORT);
|
export const setBlocklistSort = createThunk(SET_BLOCKLIST_SORT);
|
||||||
|
export const setBlocklistFilter = createThunk(SET_BLOCKLIST_FILTER);
|
||||||
export const setBlocklistTableOption = createAction(SET_BLOCKLIST_TABLE_OPTION);
|
export const setBlocklistTableOption = createAction(SET_BLOCKLIST_TABLE_OPTION);
|
||||||
export const removeBlocklistItem = createThunk(REMOVE_BLOCKLIST_ITEM);
|
export const removeBlocklistItem = createThunk(REMOVE_BLOCKLIST_ITEM);
|
||||||
export const removeBlocklistItems = createThunk(REMOVE_BLOCKLIST_ITEMS);
|
export const removeBlocklistItems = createThunk(REMOVE_BLOCKLIST_ITEMS);
|
||||||
@ -134,7 +162,8 @@ export const actionHandlers = handleThunks({
|
|||||||
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_BLOCKLIST_PAGE,
|
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_BLOCKLIST_PAGE,
|
||||||
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_BLOCKLIST_PAGE,
|
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_BLOCKLIST_PAGE,
|
||||||
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_BLOCKLIST_PAGE,
|
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_BLOCKLIST_PAGE,
|
||||||
[serverSideCollectionHandlers.SORT]: SET_BLOCKLIST_SORT
|
[serverSideCollectionHandlers.SORT]: SET_BLOCKLIST_SORT,
|
||||||
|
[serverSideCollectionHandlers.FILTER]: SET_BLOCKLIST_FILTER
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[REMOVE_BLOCKLIST_ITEM]: createRemoveItemHandler(section, '/blocklist'),
|
[REMOVE_BLOCKLIST_ITEM]: createRemoveItemHandler(section, '/blocklist'),
|
||||||
|
16
frontend/src/typings/Blocklist.ts
Normal file
16
frontend/src/typings/Blocklist.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
import Language from 'Language/Language';
|
||||||
|
import { QualityModel } from 'Quality/Quality';
|
||||||
|
import CustomFormat from 'typings/CustomFormat';
|
||||||
|
|
||||||
|
interface Blocklist extends ModelBase {
|
||||||
|
languages: Language[];
|
||||||
|
quality: QualityModel;
|
||||||
|
customFormats: CustomFormat[];
|
||||||
|
title: string;
|
||||||
|
date?: string;
|
||||||
|
protocol: string;
|
||||||
|
movieId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Blocklist;
|
@ -48,14 +48,30 @@ public void DeleteForMovies(List<int> movieIds)
|
|||||||
Delete(x => movieIds.Contains(x.MovieId));
|
Delete(x => movieIds.Contains(x.MovieId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
|
public override PagingSpec<Blocklist> GetPaged(PagingSpec<Blocklist> pagingSpec)
|
||||||
.Join<Blocklist, Movie>((b, m) => b.MovieId == m.Id)
|
{
|
||||||
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
|
pagingSpec.Records = GetPagedRecords(PagedBuilder(), pagingSpec, PagedQuery);
|
||||||
|
|
||||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder sql) => _database.QueryJoined<Blocklist, Movie>(sql, (bl, movie) =>
|
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(Blocklist))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
|
||||||
{
|
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().Select(typeof(Blocklist)), pagingSpec, countTemplate);
|
||||||
bl.Movie = movie;
|
|
||||||
return bl;
|
return pagingSpec;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
protected override SqlBuilder PagedBuilder()
|
||||||
|
{
|
||||||
|
var builder = Builder()
|
||||||
|
.Join<Blocklist, Movie>((b, m) => b.MovieId == m.Id)
|
||||||
|
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) =>
|
||||||
|
_database.QueryJoined<Blocklist, Movie>(builder, (blocklist, movie) =>
|
||||||
|
{
|
||||||
|
blocklist.Movie = movie;
|
||||||
|
return blocklist;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,7 @@
|
|||||||
"BlocklistAndSearch": "Blocklist and Search",
|
"BlocklistAndSearch": "Blocklist and Search",
|
||||||
"BlocklistAndSearchHint": "Start a search for a replacement after blocklisting",
|
"BlocklistAndSearchHint": "Start a search for a replacement after blocklisting",
|
||||||
"BlocklistAndSearchMultipleHint": "Start searches for replacements after blocklisting",
|
"BlocklistAndSearchMultipleHint": "Start searches for replacements after blocklisting",
|
||||||
|
"BlocklistFilterHasNoItems": "Selected blocklist filter contains no items",
|
||||||
"BlocklistLoadError": "Unable to load blocklist",
|
"BlocklistLoadError": "Unable to load blocklist",
|
||||||
"BlocklistMultipleOnlyHint": "Blocklist without searching for replacements",
|
"BlocklistMultipleOnlyHint": "Blocklist without searching for replacements",
|
||||||
"BlocklistOnly": "Blocklist Only",
|
"BlocklistOnly": "Blocklist Only",
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using NzbDrone.Core.Blocklisting;
|
using NzbDrone.Core.Blocklisting;
|
||||||
using NzbDrone.Core.CustomFormats;
|
using NzbDrone.Core.CustomFormats;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
using Radarr.Http;
|
using Radarr.Http;
|
||||||
using Radarr.Http.Extensions;
|
using Radarr.Http.Extensions;
|
||||||
using Radarr.Http.REST.Attributes;
|
using Radarr.Http.REST.Attributes;
|
||||||
@ -25,12 +26,22 @@ public BlocklistController(IBlocklistService blocklistService,
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
public PagingResource<BlocklistResource> GetBlocklist([FromQuery] PagingRequestResource paging)
|
public PagingResource<BlocklistResource> GetBlocklist([FromQuery] PagingRequestResource paging, [FromQuery] int[] movieIds = null, [FromQuery] DownloadProtocol[] protocols = null)
|
||||||
{
|
{
|
||||||
var pagingResource = new PagingResource<BlocklistResource>(paging);
|
var pagingResource = new PagingResource<BlocklistResource>(paging);
|
||||||
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, NzbDrone.Core.Blocklisting.Blocklist>("date", SortDirection.Descending);
|
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, NzbDrone.Core.Blocklisting.Blocklist>("date", SortDirection.Descending);
|
||||||
|
|
||||||
return pagingSpec.ApplyToPage(_blocklistService.Paged, model => BlocklistResourceMapper.MapToResource(model, _formatCalculator));
|
if (movieIds?.Any() == true)
|
||||||
|
{
|
||||||
|
pagingSpec.FilterExpressions.Add(b => movieIds.Contains(b.MovieId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protocols?.Any() == true)
|
||||||
|
{
|
||||||
|
pagingSpec.FilterExpressions.Add(b => protocols.Contains(b.Protocol));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagingSpec.ApplyToPage(b => _blocklistService.Paged(pagingSpec), b => BlocklistResourceMapper.MapToResource(b, _formatCalculator));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("movie")]
|
[HttpGet("movie")]
|
||||||
|
Loading…
Reference in New Issue
Block a user