mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
New: UI Updates, Tag manager, More custom filters (#437)
* New: UI Updates, Tag manager, More custom filters * fixup! Fix ScanFixture Unit Tests * Fixed: Sentry Errors from UI don't have release, branch, environment * Changed: Bump Mobile Detect for New Device Detection * Fixed: Build on changes to package.json * fixup! Add MetadataProfile filter option * fixup! Tag Note, Blacklist, Manual Import * fixup: Remove connectSection * fixup: root folder comment
This commit is contained in:
parent
afa78b1d20
commit
6581b3a2c5
198 changed files with 3057 additions and 888 deletions
|
@ -14,7 +14,7 @@ function createSetSettingValueReducer(section) {
|
|||
|
||||
let parsedValue = null;
|
||||
|
||||
if (_.isNumber(currentValue)) {
|
||||
if (_.isNumber(currentValue) && value != null) {
|
||||
parsedValue = parseInt(value);
|
||||
} else {
|
||||
parsedValue = value;
|
||||
|
|
|
@ -9,6 +9,7 @@ import createSetClientSideCollectionFilterReducer from './Creators/Reducers/crea
|
|||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { set } from './baseActions';
|
||||
import { fetchAlbums } from './albumActions';
|
||||
import { fetchArtist, filters, filterPredicates } from './artistActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
@ -26,9 +27,9 @@ export const defaultState = {
|
|||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
filters,
|
||||
filterPredicates,
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
|
|
@ -2,7 +2,8 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
|
||||
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
|
@ -16,6 +17,95 @@ import { updateItem } from './baseActions';
|
|||
|
||||
export const section = 'artist';
|
||||
|
||||
export const 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 Tracks',
|
||||
filters: [
|
||||
{
|
||||
key: 'missing',
|
||||
value: true,
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const filterPredicates = {
|
||||
missing: function(item) {
|
||||
const { statistics = {} } = item;
|
||||
|
||||
return statistics.trackCount - statistics.trackFileCount > 0;
|
||||
},
|
||||
|
||||
nextAlbum: function(item, filterValue, type) {
|
||||
return dateFilterPredicate(item.nextAlbum, filterValue, type);
|
||||
},
|
||||
|
||||
lastAlbum: function(item, filterValue, type) {
|
||||
return dateFilterPredicate(item.lastAlbum, filterValue, type);
|
||||
},
|
||||
|
||||
added: function(item, filterValue, type) {
|
||||
return dateFilterPredicate(item.added, filterValue, type);
|
||||
},
|
||||
|
||||
ratings: function(item, filterValue, type) {
|
||||
const predicate = filterTypePredicates[type];
|
||||
|
||||
return predicate(item.ratings.value * 10, filterValue);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
|
@ -28,75 +118,6 @@ 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,12 +1,15 @@
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, 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 createHandleActions from './Creators/createHandleActions';
|
||||
import { set, updateItem } from './baseActions';
|
||||
import { filters, filterPredicates } from './artistActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
@ -26,9 +29,58 @@ export const defaultState = {
|
|||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
filters,
|
||||
filterPredicates,
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: 'Monitored',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.ARTIST_STATUS
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: 'Quality Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'languageProfileId',
|
||||
label: 'Language Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.LANGUAGE_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'metadataProfileId',
|
||||
label: 'Metadata Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: 'Root Folder Path',
|
||||
type: filterBuilderTypes.EXACT
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -45,6 +97,8 @@ export const SET_ARTIST_EDITOR_SORT = 'artistEditor/setArtistEditorSort';
|
|||
export const SET_ARTIST_EDITOR_FILTER = 'artistEditor/setArtistEditorFilter';
|
||||
export const SAVE_ARTIST_EDITOR = 'artistEditor/saveArtistEditor';
|
||||
export const BULK_DELETE_ARTIST = 'artistEditor/bulkDeleteArtist';
|
||||
export const REMOVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/removeArtistEditorCustomFilter';
|
||||
export const SAVE_ARTIST_EDITOR_CUSTOM_FILTER = 'artistEditor/saveArtistEditorCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -53,6 +107,8 @@ export const setArtistEditorSort = createAction(SET_ARTIST_EDITOR_SORT);
|
|||
export const setArtistEditorFilter = createAction(SET_ARTIST_EDITOR_FILTER);
|
||||
export const saveArtistEditor = createThunk(SAVE_ARTIST_EDITOR);
|
||||
export const bulkDeleteArtist = createThunk(BULK_DELETE_ARTIST);
|
||||
export const removeArtistEditorCustomFilter = createAction(REMOVE_ARTIST_EDITOR_CUSTOM_FILTER);
|
||||
export const saveArtistEditorCustomFilter = createAction(SAVE_ARTIST_EDITOR_CUSTOM_FILTER);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
@ -137,6 +193,11 @@ export const actionHandlers = handleThunks({
|
|||
export const reducers = createHandleActions({
|
||||
|
||||
[SET_ARTIST_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
||||
[SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_ARTIST_EDITOR_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_ARTIST_EDITOR_CUSTOM_FILTER
|
||||
})
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import moment from 'moment';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import customFilterHandlers from 'Utilities/customFilterHandlers';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createCustomFilterReducers from './Creators/Reducers/createCustomFilterReducers';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { filters, filterPredicates } from './artistActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
@ -137,6 +141,18 @@ export const defaultState = {
|
|||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: 'Genres',
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'ratings',
|
||||
label: 'Rating',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
|
@ -153,10 +169,12 @@ export const defaultState = {
|
|||
|
||||
sortPredicates: {
|
||||
trackProgress: function(item) {
|
||||
const { statistics = {} } = item;
|
||||
|
||||
const {
|
||||
trackCount = 0,
|
||||
trackFileCount
|
||||
} = item.statistics;
|
||||
} = statistics;
|
||||
|
||||
const progress = trackCount ? trackFileCount / trackCount * 100 : 100;
|
||||
|
||||
|
@ -178,22 +196,136 @@ export const defaultState = {
|
|||
},
|
||||
|
||||
albumCount: function(item) {
|
||||
return item.statistics.albumCount;
|
||||
const { statistics = {} } = item;
|
||||
|
||||
return statistics.albumCount;
|
||||
},
|
||||
|
||||
trackCount: function(item) {
|
||||
return item.statistics.totalTrackCount;
|
||||
const { statistics = {} } = item;
|
||||
|
||||
return statistics.totalTrackCount;
|
||||
},
|
||||
|
||||
sizeOnDisk: function(item) {
|
||||
return item.statistics.sizeOnDisk;
|
||||
const { statistics = {} } = item;
|
||||
|
||||
return statistics.sizeOnDisk;
|
||||
},
|
||||
|
||||
ratings: function(item) {
|
||||
const { ratings = {} } = item;
|
||||
|
||||
return ratings.value;
|
||||
}
|
||||
},
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
// filters come from artistActions
|
||||
|
||||
filters,
|
||||
filterPredicates,
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: 'Monitored',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.ARTIST_STATUS
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: 'Quality Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'languageProfileId',
|
||||
label: 'Language Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.LANGUAGE_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'metadataProfileId',
|
||||
label: 'Metadata Profile',
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'nextAlbum',
|
||||
label: 'Next Album',
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'lastAlbum',
|
||||
label: 'Last Album',
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: 'Added',
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'albumCount',
|
||||
label: 'Album Count',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'trackProgress',
|
||||
label: 'Track Progress',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'sizeOnDisk',
|
||||
label: 'Size on Disk',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: 'Genres',
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const tagList = items.reduce((acc, artist) => {
|
||||
artist.genres.forEach((genre) => {
|
||||
acc.push({
|
||||
id: genre,
|
||||
name: genre
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return tagList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ratings',
|
||||
label: 'Rating',
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
],
|
||||
customFilters: []
|
||||
// filterPredicates come from artistActions
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
@ -218,6 +350,8 @@ export const SET_ARTIST_TABLE_OPTION = 'artistIndex/setArtistTableOption';
|
|||
export const SET_ARTIST_POSTER_OPTION = 'artistIndex/setArtistPosterOption';
|
||||
export const SET_ARTIST_BANNER_OPTION = 'artistIndex/setArtistBannerOption';
|
||||
export const SET_ARTIST_OVERVIEW_OPTION = 'artistIndex/setArtistOverviewOption';
|
||||
export const REMOVE_ARTIST_CUSTOM_FILTER = 'artistIndex/removeArtistCustomFilter';
|
||||
export const SAVE_ARTIST_CUSTOM_FILTER = 'artistIndex/saveArtistCustomFilter';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -229,7 +363,8 @@ export const setArtistTableOption = createAction(SET_ARTIST_TABLE_OPTION);
|
|||
export const setArtistPosterOption = createAction(SET_ARTIST_POSTER_OPTION);
|
||||
export const setArtistBannerOption = createAction(SET_ARTIST_BANNER_OPTION);
|
||||
export const setArtistOverviewOption = createAction(SET_ARTIST_OVERVIEW_OPTION);
|
||||
|
||||
export const removeArtistCustomFilter = createAction(REMOVE_ARTIST_CUSTOM_FILTER);
|
||||
export const saveArtistCustomFilter = createAction(SAVE_ARTIST_CUSTOM_FILTER);
|
||||
//
|
||||
// Reducers
|
||||
|
||||
|
@ -278,6 +413,11 @@ export const reducers = createHandleActions({
|
|||
...payload
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
...createCustomFilterReducers(section, {
|
||||
[customFilterHandlers.REMOVE]: REMOVE_ARTIST_CUSTOM_FILTER,
|
||||
[customFilterHandlers.SAVE]: SAVE_ARTIST_CUSTOM_FILTER
|
||||
})
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createThunk, handleThunks } from 'Store/thunks';
|
|||
import { sortDirections } from 'Helpers/Props';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
|
||||
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
|
||||
|
||||
//
|
||||
|
@ -59,8 +60,8 @@ export const defaultState = {
|
|||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
columnLabel: 'Details',
|
||||
name: 'actions',
|
||||
columnLabel: 'Actions',
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
|
@ -85,6 +86,7 @@ export const GOTO_LAST_BLACKLIST_PAGE = 'blacklist/gotoBlacklistLastPage';
|
|||
export const GOTO_BLACKLIST_PAGE = 'blacklist/gotoBlacklistPage';
|
||||
export const SET_BLACKLIST_SORT = 'blacklist/setBlacklistSort';
|
||||
export const SET_BLACKLIST_TABLE_OPTION = 'blacklist/setBlacklistTableOption';
|
||||
export const REMOVE_FROM_BLACKLIST = 'blacklist/removeFromBlacklist';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -97,6 +99,7 @@ export const gotoBlacklistLastPage = createThunk(GOTO_LAST_BLACKLIST_PAGE);
|
|||
export const gotoBlacklistPage = createThunk(GOTO_BLACKLIST_PAGE);
|
||||
export const setBlacklistSort = createThunk(SET_BLACKLIST_SORT);
|
||||
export const setBlacklistTableOption = createAction(SET_BLACKLIST_TABLE_OPTION);
|
||||
export const removeFromBlacklist = createThunk(REMOVE_FROM_BLACKLIST);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
@ -114,7 +117,9 @@ export const actionHandlers = handleThunks({
|
|||
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_BLACKLIST_PAGE,
|
||||
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_BLACKLIST_PAGE,
|
||||
[serverSideCollectionHandlers.SORT]: SET_BLACKLIST_SORT
|
||||
})
|
||||
}),
|
||||
|
||||
[REMOVE_FROM_BLACKLIST]: createRemoveItemHandler(section, '/blacklist')
|
||||
});
|
||||
|
||||
//
|
||||
|
|
|
@ -287,7 +287,7 @@ export const actionHandlers = handleThunks({
|
|||
[SET_CALENDAR_VIEW]: function(getState, payload, dispatch) {
|
||||
const state = getState();
|
||||
const view = payload.view;
|
||||
const time = view === calendarViews.FORECAST ?
|
||||
const time = view === calendarViews.FORECAST || calendarViews.AGENDA ?
|
||||
moment() :
|
||||
state.calendar.time;
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
import { createAction } from 'redux-actions';
|
||||
|
@ -44,7 +43,7 @@ export const defaultState = {
|
|||
},
|
||||
|
||||
quality: function(item, direction) {
|
||||
return item.quality.qualityWeight;
|
||||
return item.quality ? item.quality.qualityWeight : 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -144,7 +143,7 @@ export const reducers = createHandleActions({
|
|||
const id = payload.id;
|
||||
const newState = Object.assign({}, state);
|
||||
const items = newState.items;
|
||||
const index = _.findIndex(items, { id });
|
||||
const index = items.findIndex((item) => item.id === id);
|
||||
const item = Object.assign({}, items[index], payload);
|
||||
|
||||
newState.items = [...items];
|
||||
|
@ -157,7 +156,7 @@ export const reducers = createHandleActions({
|
|||
const folder = payload.folder;
|
||||
const recentFolder = { folder, lastUsed: moment().toISOString() };
|
||||
const recentFolders = [...state.recentFolders];
|
||||
const index = _.findIndex(recentFolders, { folder });
|
||||
const index = recentFolders.findIndex((r) => r.folder === folder);
|
||||
|
||||
if (index > -1) {
|
||||
recentFolders.splice(index, 1, recentFolder);
|
||||
|
@ -170,7 +169,10 @@ export const reducers = createHandleActions({
|
|||
|
||||
[REMOVE_RECENT_FOLDER]: function(state, { payload }) {
|
||||
const folder = payload.folder;
|
||||
const recentFolders = _.remove([...state.recentFolders], { folder });
|
||||
const recentFolders = [...state.recentFolders];
|
||||
const index = recentFolders.findIndex((r) => r.folder === folder);
|
||||
|
||||
recentFolders.splice(index, 1);
|
||||
|
||||
return Object.assign({}, state, { recentFolders });
|
||||
},
|
||||
|
|
|
@ -16,8 +16,8 @@ export const section = 'oAuth';
|
|||
|
||||
export const defaultState = {
|
||||
authorizing: false,
|
||||
accessToken: null,
|
||||
accessTokenSecret: null
|
||||
result: null,
|
||||
error: null
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -50,9 +50,11 @@ function showOAuthWindow(url) {
|
|||
const splitQuery = query.substring(1).split('&');
|
||||
|
||||
splitQuery.forEach((param) => {
|
||||
const paramSplit = param.split('=');
|
||||
if (param) {
|
||||
const paramSplit = param.split('=');
|
||||
|
||||
queryParams[paramSplit[0]] = paramSplit[1];
|
||||
queryParams[paramSplit[0]] = paramSplit[1];
|
||||
}
|
||||
});
|
||||
|
||||
onComplete();
|
||||
|
@ -70,7 +72,7 @@ export const actionHandlers = handleThunks({
|
|||
[START_OAUTH]: function(getState, payload, dispatch) {
|
||||
const actionPayload = {
|
||||
action: 'startOAuth',
|
||||
queryParams: { callbackUrl: `${window.location.origin}/oauth.html` },
|
||||
queryParams: { callbackUrl: `${window.location.origin}${window.Lidarr.urlBase}/oauth.html` },
|
||||
...payload
|
||||
};
|
||||
|
||||
|
@ -78,33 +80,36 @@ export const actionHandlers = handleThunks({
|
|||
authorizing: true
|
||||
}));
|
||||
|
||||
let startResponse = {};
|
||||
|
||||
const promise = requestAction(actionPayload)
|
||||
.then((response) => {
|
||||
startResponse = response;
|
||||
return showOAuthWindow(response.oauthUrl);
|
||||
})
|
||||
.then((queryParams) => {
|
||||
return requestAction({
|
||||
action: 'getOAuthToken',
|
||||
queryParams,
|
||||
queryParams: {
|
||||
...startResponse,
|
||||
...queryParams
|
||||
},
|
||||
...payload
|
||||
});
|
||||
})
|
||||
.then((response) => {
|
||||
const {
|
||||
accessToken,
|
||||
accessTokenSecret
|
||||
} = response;
|
||||
|
||||
dispatch(setOAuthValue({
|
||||
authorizing: false,
|
||||
accessToken,
|
||||
accessTokenSecret
|
||||
result: response,
|
||||
error: null
|
||||
}));
|
||||
});
|
||||
|
||||
promise.fail(() => {
|
||||
promise.fail((xhr) => {
|
||||
dispatch(setOAuthValue({
|
||||
authorizing: false
|
||||
authorizing: false,
|
||||
result: null,
|
||||
error: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -78,16 +78,19 @@ export const defaultState = {
|
|||
{
|
||||
name: 'protocol',
|
||||
label: 'Protocol',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: 'Indexer',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'downloadClient',
|
||||
label: 'Download Client',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
|
@ -269,7 +272,7 @@ export const actionHandlers = handleThunks({
|
|||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
dispatch(fetchQueue()),
|
||||
fetchQueue(),
|
||||
|
||||
...ids.map((id) => {
|
||||
return updateItem({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { update } from './baseActions';
|
||||
|
||||
|
@ -16,7 +17,14 @@ export const defaultState = {
|
|||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
items: [],
|
||||
|
||||
details: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: []
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -24,12 +32,16 @@ export const defaultState = {
|
|||
|
||||
export const FETCH_TAGS = 'tags/fetchTags';
|
||||
export const ADD_TAG = 'tags/addTag';
|
||||
export const DELETE_TAG = 'tags/deleteTag';
|
||||
export const FETCH_TAG_DETAILS = 'tags/fetchTagDetails';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchTags = createThunk(FETCH_TAGS);
|
||||
export const addTag = createThunk(ADD_TAG);
|
||||
export const deleteTag = createThunk(DELETE_TAG);
|
||||
export const fetchTagDetails = createThunk(FETCH_TAG_DETAILS);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
@ -51,7 +63,11 @@ export const actionHandlers = handleThunks({
|
|||
dispatch(update({ section, data: tags }));
|
||||
payload.onTagCreated(data);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
[DELETE_TAG]: createRemoveItemHandler(section, '/tag'),
|
||||
[FETCH_TAG_DETAILS]: createFetchHandler('tags.details', '/tag/detail')
|
||||
|
||||
});
|
||||
|
||||
//
|
||||
|
|
|
@ -15,7 +15,10 @@ export default function(history) {
|
|||
middlewares.push(routerMiddleware(history));
|
||||
middlewares.push(thunk);
|
||||
|
||||
return compose(
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
return composeEnhancers(
|
||||
applyMiddleware(...middlewares),
|
||||
persistState
|
||||
);
|
||||
|
|
|
@ -32,17 +32,20 @@ export default function sentryMiddleware() {
|
|||
}
|
||||
|
||||
const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290' :
|
||||
'https://c3a5b33e08de4e18b7d0505e942dbc95@sentry.io/216290';
|
||||
'https://baede6f14da54cf48ff431479e400adf@sentry.io/1249427';
|
||||
|
||||
Raven.config(dsn).install();
|
||||
Raven.config(
|
||||
dsn,
|
||||
{
|
||||
environment: isProduction ? 'production' : 'development',
|
||||
release,
|
||||
tags: {
|
||||
branch,
|
||||
version
|
||||
},
|
||||
dataCallback: cleanseData
|
||||
}
|
||||
).install();
|
||||
|
||||
return createRavenMiddleware(Raven, {
|
||||
environment: isProduction ? 'production' : 'development',
|
||||
release,
|
||||
tags: {
|
||||
branch,
|
||||
version
|
||||
},
|
||||
dataCallback: cleanseData
|
||||
});
|
||||
return createRavenMiddleware(Raven);
|
||||
}
|
||||
|
|
|
@ -1,37 +1,7 @@
|
|||
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().contains(filterValue.toLowerCase());
|
||||
},
|
||||
|
||||
[filterTypes.EQUAL]: function(value, filterValue) {
|
||||
return value === filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.GREATER_THAN]: function(value, filterValue) {
|
||||
return value > filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.GREATER_THAN_OR_EQUAL]: function(value, filterValue) {
|
||||
return value >= filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.LESS_THAN]: function(value, filterValue) {
|
||||
return value < filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.LESS_THAN_OR_EQUAL]: function(value, filterValue) {
|
||||
return value <= filterValue;
|
||||
},
|
||||
|
||||
[filterTypes.NOT_EQUAL]: function(value, filterValue) {
|
||||
return value !== filterValue;
|
||||
}
|
||||
};
|
||||
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
|
||||
function getSortClause(sortKey, sortDirection, sortPredicates) {
|
||||
if (sortPredicates && sortPredicates.hasOwnProperty(sortKey)) {
|
||||
|
@ -124,10 +94,10 @@ function sort(items, state) {
|
|||
return _.orderBy(items, clauses, orders);
|
||||
}
|
||||
|
||||
function createClientSideCollectionSelector() {
|
||||
function createClientSideCollectionSelector(section, uiSection) {
|
||||
return createSelector(
|
||||
(state, { section }) => _.get(state, section),
|
||||
(state, { uiSection }) => _.get(state, uiSection),
|
||||
(state) => _.get(state, section),
|
||||
(state) => _.get(state, uiSection),
|
||||
(sectionState, uiSectionState = {}) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState);
|
||||
|
||||
|
@ -137,7 +107,8 @@ function createClientSideCollectionSelector() {
|
|||
return {
|
||||
...sectionState,
|
||||
...uiSectionState,
|
||||
items: sorted
|
||||
items: sorted,
|
||||
totalItems: state.items.length
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,10 +2,10 @@ import _ from 'lodash';
|
|||
import { createSelector } from 'reselect';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
|
||||
function createProviderSettingsSelector() {
|
||||
function createProviderSettingsSelector(sectionName) {
|
||||
return createSelector(
|
||||
(state, { id }) => id,
|
||||
(state, { section }) => state.settings[section],
|
||||
(state) => state.settings[sectionName],
|
||||
(id, section) => {
|
||||
if (!id) {
|
||||
const item = _.isArray(section.schema) ? section.selectedSchema : section.schema;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
|
||||
function createSettingsSectionSelector() {
|
||||
function createSettingsSectionSelector(section) {
|
||||
return createSelector(
|
||||
(state, { section }) => _.get(state, section),
|
||||
(state) => state.settings[section],
|
||||
(sectionSettings) => {
|
||||
const {
|
||||
isFetching,
|
||||
|
|
13
frontend/src/Store/Selectors/createTagDetailsSelector.js
Normal file
13
frontend/src/Store/Selectors/createTagDetailsSelector.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
function createTagDetailsSelector() {
|
||||
return createSelector(
|
||||
(state, { id }) => id,
|
||||
(state) => state.tags.details.items,
|
||||
(id, tagDetails) => {
|
||||
return tagDetails.find((t) => t.id === id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createTagDetailsSelector;
|
|
@ -1,58 +0,0 @@
|
|||
/* eslint max-params: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import getDisplayName from 'Helpers/getDisplayName';
|
||||
|
||||
function connectSection(mapStateToProps, mapDispatchToProps, mergeProps, options = {}, sectionOptions = {}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(WrappedComponent);
|
||||
|
||||
class Section extends Component {
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
getWrappedInstance = () => {
|
||||
if (this._wrappedInstance) {
|
||||
return this._wrappedInstance.getWrappedInstance();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
setWrappedInstanceRef = (ref) => {
|
||||
this._wrappedInstance = ref;
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
if (options.withRef) {
|
||||
return (
|
||||
<ConnectedComponent
|
||||
ref={this.setWrappedInstanceRef}
|
||||
{...sectionOptions}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectedComponent
|
||||
{...sectionOptions}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Section.displayName = `Section(${getDisplayName(WrappedComponent)})`;
|
||||
Section.WrappedComponent = WrappedComponent;
|
||||
|
||||
return Section;
|
||||
};
|
||||
}
|
||||
|
||||
export default connectSection;
|
Loading…
Add table
Add a link
Reference in a new issue