mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-29 19:28:27 -07:00
New: Custom Filtering for UI (#234)
This commit is contained in:
parent
c6873014c7
commit
7354e02bff
154 changed files with 3498 additions and 1370 deletions
|
@ -0,0 +1,65 @@
|
|||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import generateUUIDv4 from 'Utilities/String/generateUUIDv4';
|
||||
|
||||
function createRemoveCustomFilterReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
const newState = getSectionState(state, section);
|
||||
const index = newState.customFilters.findIndex((c) => c.key === payload.key);
|
||||
|
||||
newState.customFilters = [...newState.customFilters];
|
||||
newState.customFilters.splice(index, 1);
|
||||
|
||||
// Reset the selected filter to the first filter if the selected filter
|
||||
// is being deleted.
|
||||
// TODO: Server side collections need to have their collections refetched
|
||||
|
||||
if (newState.selectedFilterKey === payload.key) {
|
||||
newState.selectedFilterKey = newState.filters[0].key;
|
||||
}
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
};
|
||||
}
|
||||
|
||||
function createSaveCustomFilterReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
const newState = getSectionState(state, section);
|
||||
|
||||
const {
|
||||
label,
|
||||
filters
|
||||
} = payload;
|
||||
|
||||
let key = payload.key;
|
||||
|
||||
newState.customFilters = [...newState.customFilters];
|
||||
|
||||
if (key) {
|
||||
const index = newState.customFilters.findIndex((c) => c.key === key);
|
||||
|
||||
newState.customFilters.splice(index, 1, { key, label, filters });
|
||||
} else {
|
||||
key = generateUUIDv4();
|
||||
|
||||
newState.customFilters.push({
|
||||
key,
|
||||
label,
|
||||
filters
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Server side collections need to have their collections refetched
|
||||
newState.selectedFilterKey = key;
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
};
|
||||
}
|
||||
|
||||
export default function createCustomFilterReducers(section, handlers) {
|
||||
return {
|
||||
[handlers[customFilterHandlers.REMOVE]]: createRemoveCustomFilterReducer(section),
|
||||
[handlers[customFilterHandlers.SAVE]]: createSaveCustomFilterReducer(section)
|
||||
};
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import { filterTypes } from 'Helpers/Props';
|
||||
|
||||
function createSetClientSideCollectionFilterReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
const newState = getSectionState(state, section);
|
||||
|
||||
newState.filterKey = payload.filterKey;
|
||||
newState.filterValue = payload.filterValue;
|
||||
newState.filterType = payload.filterType || filterTypes.EQUAL;
|
||||
newState.selectedFilterKey = payload.selectedFilterKey;
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
};
|
||||
|
|
|
@ -2,40 +2,40 @@ import $ from 'jquery';
|
|||
import updateAlbums from 'Utilities/Album/updateAlbums';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
|
||||
function createBatchToggleAlbumMonitoredHandler(section) {
|
||||
return function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
const {
|
||||
albumIds,
|
||||
function createBatchToggleAlbumMonitoredHandler(section, fetchHandler) {
|
||||
return function(getState, payload, dispatch) {
|
||||
const {
|
||||
albumIds,
|
||||
monitored
|
||||
} = payload;
|
||||
|
||||
const state = getSectionState(getState(), section, true);
|
||||
|
||||
dispatch(updateAlbums(section, state.items, albumIds, {
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = $.ajax({
|
||||
url: '/album/monitor',
|
||||
method: 'PUT',
|
||||
data: JSON.stringify({ albumIds, monitored }),
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
promise.done(() => {
|
||||
dispatch(updateAlbums(section, state.items, albumIds, {
|
||||
isSaving: false,
|
||||
monitored
|
||||
} = payload;
|
||||
}));
|
||||
|
||||
const state = getSectionState(getState(), section, true);
|
||||
dispatch(fetchHandler());
|
||||
});
|
||||
|
||||
updateAlbums(dispatch, section, state.items, albumIds, {
|
||||
isSaving: true
|
||||
});
|
||||
|
||||
const promise = $.ajax({
|
||||
url: '/album/monitor',
|
||||
method: 'PUT',
|
||||
data: JSON.stringify({ albumIds, monitored }),
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
promise.done(() => {
|
||||
updateAlbums(dispatch, section, state.items, albumIds, {
|
||||
isSaving: false,
|
||||
monitored
|
||||
});
|
||||
});
|
||||
|
||||
promise.fail(() => {
|
||||
updateAlbums(dispatch, section, state.items, albumIds, {
|
||||
isSaving: false
|
||||
});
|
||||
});
|
||||
};
|
||||
promise.fail(() => {
|
||||
dispatch(updateAlbums(section, state.items, albumIds, {
|
||||
isSaving: false
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import { set, updateServerSideCollection } from '../baseActions';
|
||||
|
||||
|
@ -15,11 +16,21 @@ function createFetchServerSideCollectionHandler(section, url) {
|
|||
_.pick(sectionState, [
|
||||
'pageSize',
|
||||
'sortDirection',
|
||||
'sortKey',
|
||||
'filterKey',
|
||||
'filterValue'
|
||||
'sortKey'
|
||||
]));
|
||||
|
||||
const {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters
|
||||
} = sectionState;
|
||||
|
||||
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
||||
|
||||
selectedFilters.forEach((filter) => {
|
||||
data[filter.key] = filter.value;
|
||||
});
|
||||
|
||||
const promise = $.ajax({
|
||||
url,
|
||||
data
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import updateAlbums from 'Utilities/Album/updateAlbums';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
|
||||
function createToggleAlbumMonitoredHandler(section) {
|
||||
return function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
const {
|
||||
albumId,
|
||||
monitored
|
||||
} = payload;
|
||||
|
||||
const state = getSectionState(getState(), section, true);
|
||||
|
||||
updateAlbums(dispatch, section, state.items, [albumId], {
|
||||
isSaving: true
|
||||
});
|
||||
|
||||
const promise = $.ajax({
|
||||
url: `/album/${albumId}`,
|
||||
method: 'PUT',
|
||||
data: JSON.stringify({ monitored }),
|
||||
dataType: 'json'
|
||||
});
|
||||
|
||||
promise.done(() => {
|
||||
updateAlbums(dispatch, section, state.items, [albumId], {
|
||||
isSaving: false,
|
||||
monitored
|
||||
});
|
||||
});
|
||||
|
||||
promise.fail(() => {
|
||||
updateAlbums(dispatch, section, state.items, [albumId], {
|
||||
isSaving: false
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default createToggleAlbumMonitoredHandler;
|
|
@ -46,10 +46,9 @@ export const actionHandlers = handleThunks({
|
|||
const queryParams = {
|
||||
pageSize: 1000,
|
||||
page: 1,
|
||||
filterKey: 'albumId',
|
||||
filterValue: payload.albumId,
|
||||
sortKey: 'date',
|
||||
sortDirection: sortDirections.DESCENDING
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
albumId: payload.albumId
|
||||
};
|
||||
|
||||
const promise = $.ajax({
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import getMonitoringOptions from 'Utilities/Artist/getMonitoringOptions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
|
@ -25,17 +25,17 @@ export const defaultState = {
|
|||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
filterKey: null,
|
||||
filterValue: null,
|
||||
filterType: filterTypes.EQUAL
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'albumStudio.sortKey',
|
||||
'albumStudio.sortDirection',
|
||||
'albumStudio.filterKey',
|
||||
'albumStudio.filterValue',
|
||||
'albumStudio.filterType'
|
||||
'albumStudio.selectedFilterKey',
|
||||
'albumStudio.customFilters'
|
||||
];
|
||||
|
||||
//
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
|
@ -28,6 +28,75 @@ export const defaultState = {
|
|||
items: [],
|
||||
sortKey: 'sortName',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: 'All',
|
||||
filters: []
|
||||
},
|
||||
{
|
||||
key: 'monitored',
|
||||
label: 'Monitored Only',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
label: 'Unmonitored Only',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: false,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'continuing',
|
||||
label: 'Continuing Only',
|
||||
filters: [
|
||||
{
|
||||
key: 'status',
|
||||
value: 'continuing',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'ended',
|
||||
label: 'Ended Only',
|
||||
filters: [
|
||||
{
|
||||
key: 'status',
|
||||
value: 'ended',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'missing',
|
||||
label: 'Missing Albums',
|
||||
filters: [
|
||||
{
|
||||
key: 'missing',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
filterPredicates: {
|
||||
missing: function(item) {
|
||||
return item.statistics.trackCount - item.statistics.trackFileCount > 0;
|
||||
}
|
||||
},
|
||||
|
||||
pendingChanges: {}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
|
@ -25,17 +25,17 @@ export const defaultState = {
|
|||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
filterKey: null,
|
||||
filterValue: null,
|
||||
filterType: filterTypes.EQUAL
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'artistEditor.sortKey',
|
||||
'artistEditor.sortDirection',
|
||||
'artistEditor.filterKey',
|
||||
'artistEditor.filterValue',
|
||||
'artistEditor.filterType'
|
||||
'artistEditor.selectedFilterKey',
|
||||
'artistEditor.customFilters'
|
||||
];
|
||||
|
||||
//
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import moment from 'moment';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
|
@ -19,9 +19,6 @@ export const defaultState = {
|
|||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
filterKey: null,
|
||||
filterValue: null,
|
||||
filterType: filterTypes.EQUAL,
|
||||
view: 'posters',
|
||||
|
||||
posterOptions: {
|
||||
|
@ -155,45 +152,41 @@ export const defaultState = {
|
|||
],
|
||||
|
||||
sortPredicates: {
|
||||
nextAiring: function(item, direction) {
|
||||
const nextAiring = item.nextAiring;
|
||||
|
||||
if (nextAiring) {
|
||||
return moment(nextAiring).unix();
|
||||
}
|
||||
|
||||
if (direction === sortDirections.DESCENDING) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
trackProgress: function(item) {
|
||||
const {
|
||||
trackCount = 0,
|
||||
trackFileCount
|
||||
} = item;
|
||||
} = item.statistics;
|
||||
|
||||
const progress = trackCount ? trackFileCount / trackCount * 100 : 100;
|
||||
|
||||
return progress + trackCount / 1000000;
|
||||
},
|
||||
|
||||
albumCount: function(item) {
|
||||
return item.statistics.albumCount;
|
||||
},
|
||||
|
||||
trackCount: function(item) {
|
||||
return item.statistics.totalTrackCount;
|
||||
},
|
||||
|
||||
sizeOnDisk: function(item) {
|
||||
return item.statistics.sizeOnDisk;
|
||||
}
|
||||
},
|
||||
|
||||
filterPredicates: {
|
||||
missing: function(item) {
|
||||
return item.trackCount - item.trackFileCount > 0;
|
||||
}
|
||||
}
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'artistIndex.sortKey',
|
||||
'artistIndex.sortDirection',
|
||||
'artistIndex.filterKey',
|
||||
'artistIndex.filterValue',
|
||||
'artistIndex.filterType',
|
||||
'artistIndex.selectedFilterKey',
|
||||
'artistIndex.customFilters',
|
||||
'artistIndex.view',
|
||||
'artistIndex.columns',
|
||||
'artistIndex.posterOptions',
|
||||
|
|
|
@ -3,6 +3,7 @@ import $ from 'jquery';
|
|||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import moment from 'moment';
|
||||
import { filterTypes } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import * as calendarViews from 'Calendar/calendarViews';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
@ -31,16 +32,42 @@ export const defaultState = {
|
|||
dates: [],
|
||||
dayCount: 7,
|
||||
view: window.innerWidth > 768 ? 'week' : 'day',
|
||||
unmonitored: false,
|
||||
showUpcoming: true,
|
||||
error: null,
|
||||
items: []
|
||||
items: [],
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: 'All',
|
||||
filters: [
|
||||
{
|
||||
key: 'unmonitored',
|
||||
value: false,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
label: 'Unmonitored',
|
||||
filters: [
|
||||
{
|
||||
key: 'unmonitored',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'calendar.view',
|
||||
'calendar.unmonitored',
|
||||
'calendar.showUpcoming'
|
||||
'calendar.showUpcoming',
|
||||
'calendar.selectedFilterKey'
|
||||
];
|
||||
|
||||
//
|
||||
|
@ -48,8 +75,8 @@ export const persistState = [
|
|||
|
||||
export const FETCH_CALENDAR = 'calendar/fetchCalendar';
|
||||
export const SET_CALENDAR_DAYS_COUNT = 'calendar/setCalendarDaysCount';
|
||||
export const SET_CALENDAR_INCLUDE_UNMONITORED = 'calendar/setCalendarIncludeUnmonitored';
|
||||
export const SET_CALENDAR_VIEW = 'calendar/setCalendarView';
|
||||
export const SET_CALENDAR_FILTER = 'calendar/setCalendarFilter';
|
||||
export const GOTO_CALENDAR_TODAY = 'calendar/gotoCalendarToday';
|
||||
export const GOTO_CALENDAR_PREVIOUS_RANGE = 'calendar/gotoCalendarPreviousRange';
|
||||
export const GOTO_CALENDAR_NEXT_RANGE = 'calendar/gotoCalendarNextRange';
|
||||
|
@ -155,8 +182,8 @@ function isRangePopulated(start, end, state) {
|
|||
|
||||
export const fetchCalendar = createThunk(FETCH_CALENDAR);
|
||||
export const setCalendarDaysCount = createThunk(SET_CALENDAR_DAYS_COUNT);
|
||||
export const setCalendarIncludeUnmonitored = createThunk(SET_CALENDAR_INCLUDE_UNMONITORED);
|
||||
export const setCalendarView = createThunk(SET_CALENDAR_VIEW);
|
||||
export const setCalendarFilter = createThunk(SET_CALENDAR_FILTER);
|
||||
export const gotoCalendarToday = createThunk(GOTO_CALENDAR_TODAY);
|
||||
export const gotoCalendarPreviousRange = createThunk(GOTO_CALENDAR_PREVIOUS_RANGE);
|
||||
export const gotoCalendarNextRange = createThunk(GOTO_CALENDAR_NEXT_RANGE);
|
||||
|
@ -166,9 +193,11 @@ export const clearCalendar = createAction(CLEAR_CALENDAR);
|
|||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
|
||||
const state = getState();
|
||||
const unmonitored = state.calendar.unmonitored;
|
||||
const selectedFilter = state.calendar.selectedFilterKey;
|
||||
const unmonitored = state.calendar.filters.find((f) => f.key === selectedFilter).filters[0].value;
|
||||
|
||||
const {
|
||||
time,
|
||||
|
@ -245,18 +274,6 @@ export const actionHandlers = handleThunks({
|
|||
dispatch(fetchCalendar({ time, view }));
|
||||
},
|
||||
|
||||
[SET_CALENDAR_INCLUDE_UNMONITORED]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
unmonitored: payload.unmonitored
|
||||
}));
|
||||
|
||||
const state = getState();
|
||||
const { time, view } = state.calendar;
|
||||
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
},
|
||||
|
||||
[SET_CALENDAR_VIEW]: function(getState, payload, dispatch) {
|
||||
const state = getState();
|
||||
const view = payload.view;
|
||||
|
@ -300,6 +317,18 @@ export const actionHandlers = handleThunks({
|
|||
const amount = view === calendarViews.FORECAST ? dayCount : 1;
|
||||
const time = moment(state.calendar.time).add(amount, viewRanges[view]);
|
||||
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
},
|
||||
|
||||
[SET_CALENDAR_FILTER]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
selectedFilterKey: payload.selectedFilterKey
|
||||
}));
|
||||
|
||||
const state = getState();
|
||||
const { time, view } = state.calendar;
|
||||
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
|
@ -24,8 +24,6 @@ export const defaultState = {
|
|||
pageSize: 20,
|
||||
sortKey: 'date',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
filterKey: null,
|
||||
filterValue: null,
|
||||
items: [],
|
||||
|
||||
columns: [
|
||||
|
@ -89,15 +87,80 @@ export const defaultState = {
|
|||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: 'All',
|
||||
filters: []
|
||||
},
|
||||
{
|
||||
key: 'grabbed',
|
||||
label: 'Grabbed',
|
||||
filters: [
|
||||
{
|
||||
key: 'eventType',
|
||||
value: '1',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'imported',
|
||||
label: 'Imported',
|
||||
filters: [
|
||||
{
|
||||
key: 'eventType',
|
||||
value: '3',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'failed',
|
||||
label: 'Failed',
|
||||
filters: [
|
||||
{
|
||||
key: 'eventType',
|
||||
value: '4',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'deleted',
|
||||
label: 'Deleted',
|
||||
filters: [
|
||||
{
|
||||
key: 'eventType',
|
||||
value: '5',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'renamed',
|
||||
label: 'Renamed',
|
||||
filters: [
|
||||
{
|
||||
key: 'eventType',
|
||||
value: '6',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'history.pageSize',
|
||||
'history.sortKey',
|
||||
'history.sortDirection',
|
||||
'history.filterKey',
|
||||
'history.filterValue'
|
||||
'history.selectedFilterKey'
|
||||
];
|
||||
|
||||
//
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
||||
|
@ -40,9 +43,116 @@ export const defaultState = {
|
|||
|
||||
return releaseWeight;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: 'All',
|
||||
filters: []
|
||||
},
|
||||
{
|
||||
key: 'discography-pack',
|
||||
label: 'Discography',
|
||||
filters: [
|
||||
{
|
||||
key: 'discography',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'not-discography-pack',
|
||||
label: 'Not Discography',
|
||||
filters: [
|
||||
{
|
||||
key: 'discography',
|
||||
value: false,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
filterPredicates: {
|
||||
quality: function(item, value, type) {
|
||||
const qualityId = item.quality.quality.id;
|
||||
|
||||
if (type === filterTypes.EQUAL) {
|
||||
return qualityId === value;
|
||||
}
|
||||
|
||||
if (type === filterTypes.NOT_EQUAL) {
|
||||
return qualityId !== value;
|
||||
}
|
||||
|
||||
// Default to false
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: 'Age',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Protocol',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.PROTOCOL
|
||||
},
|
||||
{
|
||||
name: 'indexerId',
|
||||
label: 'Indexer',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.INDEXER
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'seeders',
|
||||
label: 'Seeders',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'leechers',
|
||||
label: 'Peers',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: 'Rejections',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
}
|
||||
],
|
||||
|
||||
customFilters: []
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'releases.selectedFilterKey',
|
||||
'releases.customFilters'
|
||||
];
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
|
@ -52,6 +162,10 @@ export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
|||
export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
||||
export const ADD_RELEASES_CUSTOM_FILTER = 'releases/addReleasesCustomFilter';
|
||||
export const REMOVE_RELEASES_CUSTOM_FILTER = 'releases/removeReleasesCustomFilter';
|
||||
export const SAVE_RELEASES_CUSTOM_FILTER = 'releases/saveReleasesCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -62,6 +176,10 @@ export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
|||
export const clearReleases = createAction(CLEAR_RELEASES);
|
||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||
export const addReleasesCustomFilter = createAction(ADD_RELEASES_CUSTOM_FILTER);
|
||||
export const removeReleasesCustomFilter = createAction(REMOVE_RELEASES_CUSTOM_FILTER);
|
||||
export const saveReleasesCustomFilter = createAction(SAVE_RELEASES_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Helpers
|
||||
|
@ -147,6 +265,12 @@ export const reducers = createHandleActions({
|
|||
return newState;
|
||||
},
|
||||
|
||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section)
|
||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_RELEASES_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_RELEASES_CUSTOM_FILTER
|
||||
})
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import { setAppValue } from 'Store/Actions/appActions';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
|
@ -73,8 +73,6 @@ export const defaultState = {
|
|||
pageSize: 50,
|
||||
sortKey: 'time',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
filterKey: null,
|
||||
filterValue: null,
|
||||
error: null,
|
||||
items: [],
|
||||
|
||||
|
@ -108,6 +106,49 @@ export const defaultState = {
|
|||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'all',
|
||||
label: 'All',
|
||||
filters: []
|
||||
},
|
||||
{
|
||||
key: 'info',
|
||||
label: 'Info',
|
||||
filters: [
|
||||
{
|
||||
key: 'level',
|
||||
value: 'info',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'warn',
|
||||
label: 'Warn',
|
||||
filters: [
|
||||
{
|
||||
key: 'level',
|
||||
value: 'warn',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'error',
|
||||
label: 'Error',
|
||||
filters: [
|
||||
{
|
||||
key: 'level',
|
||||
value: 'error',
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -130,8 +171,7 @@ export const persistState = [
|
|||
'system.logs.pageSize',
|
||||
'system.logs.sortKey',
|
||||
'system.logs.sortDirection',
|
||||
'system.logs.filterKey',
|
||||
'system.logs.filterValue'
|
||||
'system.logs.selectedFilterKey'
|
||||
];
|
||||
|
||||
//
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createAction } from 'redux-actions';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
|
@ -23,8 +23,6 @@ export const defaultState = {
|
|||
pageSize: 20,
|
||||
sortKey: 'releaseDate',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
filterKey: 'monitored',
|
||||
filterValue: 'true',
|
||||
error: null,
|
||||
items: [],
|
||||
|
||||
|
@ -68,6 +66,33 @@ export const defaultState = {
|
|||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
selectedFilterKey: 'monitored',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
label: 'Monitored',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
label: 'Unmonitored',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: false,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -77,9 +102,6 @@ export const defaultState = {
|
|||
pageSize: 20,
|
||||
sortKey: 'releaseDate',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
filterKey: 'monitored',
|
||||
filterValue: true,
|
||||
error: null,
|
||||
items: [],
|
||||
|
||||
columns: [
|
||||
|
@ -127,6 +149,33 @@ export const defaultState = {
|
|||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
selectedFilterKey: 'monitored',
|
||||
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
label: 'Monitored',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
label: 'Unmonitored',
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: false,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -135,14 +184,12 @@ export const persistState = [
|
|||
'wanted.missing.pageSize',
|
||||
'wanted.missing.sortKey',
|
||||
'wanted.missing.sortDirection',
|
||||
'wanted.missing.filterKey',
|
||||
'wanted.missing.filterValue',
|
||||
'wanted.missing.selectedFilterKey',
|
||||
'wanted.missing.columns',
|
||||
'wanted.cutoffUnmet.pageSize',
|
||||
'wanted.cutoffUnmet.sortKey',
|
||||
'wanted.cutoffUnmet.sortDirection',
|
||||
'wanted.cutoffUnmet.filterKey',
|
||||
'wanted.cutoffUnmet.filterValue',
|
||||
'wanted.cutoffUnmet.selectedFilterKey',
|
||||
'wanted.cutoffUnmet.columns'
|
||||
];
|
||||
|
||||
|
@ -225,7 +272,7 @@ export const actionHandlers = handleThunks({
|
|||
}
|
||||
),
|
||||
|
||||
[BATCH_TOGGLE_MISSING_ALBUMS]: createBatchToggleAlbumMonitoredHandler('wanted.missing'),
|
||||
[BATCH_TOGGLE_MISSING_ALBUMS]: createBatchToggleAlbumMonitoredHandler('wanted.missing', fetchMissing),
|
||||
|
||||
...createServerSideCollectionHandlers(
|
||||
'wanted.cutoffUnmet',
|
||||
|
@ -243,7 +290,7 @@ export const actionHandlers = handleThunks({
|
|||
}
|
||||
),
|
||||
|
||||
[BATCH_TOGGLE_CUTOFF_UNMET_ALBUMS]: createBatchToggleAlbumMonitoredHandler('wanted.cutoffUnmet')
|
||||
[BATCH_TOGGLE_CUTOFF_UNMET_ALBUMS]: createBatchToggleAlbumMonitoredHandler('wanted.cutoffUnmet', fetchCutoffUnmet)
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
|
||||
const filterTypePredicates = {
|
||||
[filterTypes.CONTAINS]: function(value, filterValue) {
|
||||
return value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1;
|
||||
return value.toLowerCase().contains(filterValue.toLowerCase());
|
||||
},
|
||||
|
||||
[filterTypes.EQUAL]: function(value, filterValue) {
|
||||
|
@ -46,26 +47,54 @@ function getSortClause(sortKey, sortDirection, sortPredicates) {
|
|||
|
||||
function filter(items, state) {
|
||||
const {
|
||||
filterKey,
|
||||
filterValue,
|
||||
filterType,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
filterPredicates
|
||||
} = state;
|
||||
|
||||
if (!filterKey || !filterValue) {
|
||||
if (!selectedFilterKey) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
||||
|
||||
return _.filter(items, (item) => {
|
||||
if (filterPredicates && filterPredicates.hasOwnProperty(filterKey)) {
|
||||
return filterPredicates[filterKey](item);
|
||||
let i = 0;
|
||||
let accepted = true;
|
||||
|
||||
while (accepted && i < selectedFilters.length) {
|
||||
const {
|
||||
key,
|
||||
value,
|
||||
type = filterTypes.EQUAL
|
||||
} = selectedFilters[i];
|
||||
|
||||
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
|
||||
const predicate = filterPredicates[key];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
accepted = value.some((v) => predicate(item, v, type));
|
||||
} else {
|
||||
accepted = predicate(item, value, type);
|
||||
}
|
||||
} else if (item.hasOwnProperty(key)) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
accepted = value.some((v) => predicate(item[key], v));
|
||||
} else {
|
||||
accepted = predicate(item[key], value);
|
||||
}
|
||||
} else {
|
||||
// Default to false if the filter can't be tested
|
||||
accepted = false;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (item.hasOwnProperty(filterKey)) {
|
||||
return filterTypePredicates[filterType](item[filterKey], filterValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
return accepted;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue