New: Release Profiles, Frontend updates (#580)

* New: Release Profiles - UI Updates

* New: Release Profiles - API Changes

* New: Release Profiles - Test Updates

* New: Release Profiles - Backend Updates

* New: Interactive Artist Search

* New: Change Montiored on Album Details Page

* New: Show Duration on Album Details Page

* Fixed: Manual Import not working if no albums are Missing

* Fixed: Sort search input by sortTitle

* Fixed: Queue columnLabel throwing JS error
This commit is contained in:
Qstick 2019-02-23 17:39:11 -05:00 committed by GitHub
parent f126eafd26
commit 3f064c94b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
409 changed files with 6882 additions and 3176 deletions

View file

@ -3,7 +3,7 @@ import { set } from '../baseActions';
function createFetchSchemaHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetchingSchema: true }));
dispatch(set({ section, isSchemaFetching: true }));
const promise = $.ajax({
url
@ -12,7 +12,7 @@ function createFetchSchemaHandler(section, url) {
promise.done((data) => {
dispatch(set({
section,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: true,
schemaError: null,
schema: data
@ -22,7 +22,7 @@ function createFetchSchemaHandler(section, url) {
promise.fail((xhr) => {
dispatch(set({
section,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: true,
schemaError: xhr
}));

View file

@ -5,7 +5,7 @@ import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import getSectionState from 'Utilities/State/getSectionState';
import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url) {
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
@ -19,6 +19,10 @@ function createFetchServerSideCollectionHandler(section, url) {
'sortKey'
]));
if (fetchDataAugmenter) {
fetchDataAugmenter(getState, payload, data);
}
const {
selectedFilterKey,
filters,

View file

@ -5,10 +5,10 @@ import createSetServerSideCollectionPageHandler from './createSetServerSideColle
import createSetServerSideCollectionSortHandler from './createSetServerSideCollectionSortHandler';
import createSetServerSideCollectionFilterHandler from './createSetServerSideCollectionFilterHandler';
function createServerSideCollectionHandlers(section, url, fetchThunk, handlers) {
function createServerSideCollectionHandlers(section, url, fetchThunk, handlers, fetchDataAugmenter) {
const actionHandlers = {};
const fetchHandlerType = handlers[serverSideCollectionHandlers.FETCH];
const fetchHandler = createFetchServerSideCollectionHandler(section, url);
const fetchHandler = createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter);
actionHandlers[fetchHandlerType] = fetchHandler;
if (handlers.hasOwnProperty(serverSideCollectionHandlers.FIRST_PAGE)) {

View file

@ -70,7 +70,7 @@ export default {
isFetching: false,
isPopulated: false,
error: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],

View file

@ -70,7 +70,7 @@ export default {
isFetching: false,
isPopulated: false,
error: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],

View file

@ -70,7 +70,7 @@ export default {
isFetching: false,
isPopulated: false,
error: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],

View file

@ -54,7 +54,7 @@ export default {
error: null,
isDeleting: false,
deleteError: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: {},

View file

@ -54,7 +54,7 @@ export default {
error: null,
isDeleting: false,
deleteError: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: {},

View file

@ -67,7 +67,7 @@ export default {
isFetching: false,
isPopulated: false,
error: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],

View file

@ -54,7 +54,7 @@ export default {
error: null,
isDeleting: false,
deleteError: null,
isFetchingSchema: false,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: {},

View file

@ -0,0 +1,71 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.releaseProfiles';
//
// Actions Types
export const FETCH_RELEASE_PROFILES = 'settings/releaseProfiles/fetchReleaseProfiles';
export const SAVE_RELEASE_PROFILE = 'settings/releaseProfiles/saveReleaseProfile';
export const DELETE_RELEASE_PROFILE = 'settings/releaseProfiles/deleteReleaseProfile';
export const SET_RELEASE_PROFILE_VALUE = 'settings/releaseProfiles/setReleaseProfileValue';
//
// Action Creators
export const fetchReleaseProfiles = createThunk(FETCH_RELEASE_PROFILES);
export const saveReleaseProfile = createThunk(SAVE_RELEASE_PROFILE);
export const deleteReleaseProfile = createThunk(DELETE_RELEASE_PROFILE);
export const setReleaseProfileValue = createAction(SET_RELEASE_PROFILE_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_RELEASE_PROFILES]: createFetchHandler(section, '/releaseprofile'),
[SAVE_RELEASE_PROFILE]: createSaveProviderHandler(section, '/releaseprofile'),
[DELETE_RELEASE_PROFILE]: createRemoveItemHandler(section, '/releaseprofile')
},
//
// Reducers
reducers: {
[SET_RELEASE_PROFILE_VALUE]: createSetSettingValueReducer(section)
}
};

View file

@ -1,71 +0,0 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.restrictions';
//
// Actions Types
export const FETCH_RESTRICTIONS = 'settings/restrictions/fetchRestrictions';
export const SAVE_RESTRICTION = 'settings/restrictions/saveRestriction';
export const DELETE_RESTRICTION = 'settings/restrictions/deleteRestriction';
export const SET_RESTRICTION_VALUE = 'settings/restrictions/setRestrictionValue';
//
// Action Creators
export const fetchRestrictions = createThunk(FETCH_RESTRICTIONS);
export const saveRestriction = createThunk(SAVE_RESTRICTION);
export const deleteRestriction = createThunk(DELETE_RESTRICTION);
export const setRestrictionValue = createAction(SET_RESTRICTION_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_RESTRICTIONS]: createFetchHandler(section, '/restriction'),
[SAVE_RESTRICTION]: createSaveProviderHandler(section, '/restriction'),
[DELETE_RESTRICTION]: createRemoveItemHandler(section, '/restriction')
},
//
// Reducers
reducers: {
[SET_RESTRICTION_VALUE]: createSetSettingValueReducer(section)
}
};

View file

@ -2,11 +2,12 @@ import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import monitorOptions from 'Utilities/Artist/monitorOptions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewArtist from 'Utilities/Artist/getNewArtist';
import { createThunk, handleThunks } from 'Store/thunks';
import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
import createHandleActions from './Creators/createHandleActions';
import { set, update, updateItem } from './baseActions';
@ -31,7 +32,7 @@ export const defaultState = {
defaults: {
rootFolderPath: '',
monitor: 'allAlbums',
monitor: monitorOptions[0].key,
qualityProfileId: 0,
languageProfileId: 0,
metadataProfileId: 0,

View file

@ -189,13 +189,11 @@ export const actionHandlers = handleThunks({
monitored
} = payload;
const albumSection = _.last(albumEntity.split('.'));
dispatch(batchActions(
albumIds.map((albumId) => {
return updateItem({
id: albumId,
section: albumSection,
section: albumEntity,
isSaving: true
});
})
@ -213,7 +211,7 @@ export const actionHandlers = handleThunks({
albumIds.map((albumId) => {
return updateItem({
id: albumId,
section: albumSection,
section: albumEntity,
isSaving: false,
monitored
});
@ -226,7 +224,7 @@ export const actionHandlers = handleThunks({
albumIds.map((albumId) => {
return updateItem({
id: albumId,
section: albumSection,
section: albumEntity,
isSaving: false
});
})

View file

@ -1,7 +1,5 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import getMonitoringOptions from 'Utilities/Artist/getMonitoringOptions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
@ -9,7 +7,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';
import { filters, filterPredicates } from './artistActions';
//
// Variables
@ -113,31 +111,15 @@ export const actionHandlers = handleThunks({
monitor
} = payload;
let monitoringOptions = null;
const artist = [];
const allArtists = getState().artist.items;
artistIds.forEach((id) => {
const s = _.find(allArtists, { id });
const artistToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
artistToUpdate.monitored = monitored;
}
if (monitor) {
const {
albums,
options: artistMonitoringOptions
} = getMonitoringOptions(monitor);
if (!monitoringOptions) {
monitoringOptions = artistMonitoringOptions;
}
artistToUpdate.albums = albums;
}
artist.push(artistToUpdate);
});
@ -151,7 +133,7 @@ export const actionHandlers = handleThunks({
method: 'POST',
data: JSON.stringify({
artist,
monitoringOptions
monitoringOptions: { monitor }
}),
dataType: 'json'
});

View file

@ -54,6 +54,7 @@ export const defaultState = {
},
tableOptions: {
showBanners: false,
showSearchAction: false
},

View file

@ -2,6 +2,7 @@ import { createAction } from 'redux-actions';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import { createThunk, handleThunks } from 'Store/thunks';
import { sortDirections } from 'Helpers/Props';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
@ -87,6 +88,7 @@ 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';
export const CLEAR_BLACKLIST = 'blacklist/clearBlacklist';
//
// Action Creators
@ -100,6 +102,7 @@ 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);
export const clearBlacklist = createAction(CLEAR_BLACKLIST);
//
// Action Handlers
@ -127,6 +130,15 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
[SET_BLACKLIST_TABLE_OPTION]: createSetTableOptionReducer(section)
[SET_BLACKLIST_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_BLACKLIST]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);

View file

@ -6,8 +6,11 @@ import moment from 'moment';
import { filterTypes } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import * as calendarViews from 'Calendar/calendarViews';
import * as commandNames from 'Commands/commandNames';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createHandleActions from './Creators/createHandleActions';
import { set, update } from './baseActions';
import { executeCommandHelper } from './commandActions';
//
// Variables
@ -35,6 +38,12 @@ export const defaultState = {
showUpcoming: true,
error: null,
items: [],
searchMissingCommandId: null,
options: {
collapseMultipleAlbums: false,
showCutoffUnmetIcon: false
},
selectedFilterKey: 'monitored',
@ -67,7 +76,7 @@ export const defaultState = {
export const persistState = [
'calendar.view',
'calendar.selectedFilterKey',
'calendar.showUpcoming'
'calendar.options'
];
//
@ -78,9 +87,11 @@ export const SET_CALENDAR_DAYS_COUNT = 'calendar/setCalendarDaysCount';
export const SET_CALENDAR_FILTER = 'calendar/setCalendarFilter';
export const SET_CALENDAR_VIEW = 'calendar/setCalendarView';
export const GOTO_CALENDAR_TODAY = 'calendar/gotoCalendarToday';
export const GOTO_CALENDAR_PREVIOUS_RANGE = 'calendar/gotoCalendarPreviousRange';
export const GOTO_CALENDAR_NEXT_RANGE = 'calendar/gotoCalendarNextRange';
export const CLEAR_CALENDAR = 'calendar/clearCalendar';
export const SET_CALENDAR_OPTION = 'calendar/setCalendarOption';
export const SEARCH_MISSING = 'calendar/searchMissing';
export const GOTO_CALENDAR_PREVIOUS_RANGE = 'calendar/gotoCalendarPreviousRange';
//
// Helpers
@ -188,6 +199,8 @@ export const gotoCalendarToday = createThunk(GOTO_CALENDAR_TODAY);
export const gotoCalendarPreviousRange = createThunk(GOTO_CALENDAR_PREVIOUS_RANGE);
export const gotoCalendarNextRange = createThunk(GOTO_CALENDAR_NEXT_RANGE);
export const clearCalendar = createAction(CLEAR_CALENDAR);
export const setCalendarOption = createAction(SET_CALENDAR_OPTION);
export const searchMissing = createThunk(SEARCH_MISSING);
//
// Action Handlers
@ -195,11 +208,12 @@ export const clearCalendar = createAction(CLEAR_CALENDAR);
export const actionHandlers = handleThunks({
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
const state = getState();
const unmonitored = state.calendar.selectedFilterKey === 'all';
const calendar = state.calendar;
const unmonitored = calendar.selectedFilterKey === 'all';
const {
time,
view
time = calendar.time,
view = calendar.view
} = payload;
const dayCount = state.calendar.dayCount;
@ -328,6 +342,22 @@ export const actionHandlers = handleThunks({
const time = moment(state.calendar.time).add(amount, viewRanges[view]);
dispatch(fetchCalendar({ time, view }));
},
[SEARCH_MISSING]: function(getState, payload, dispatch) {
const { albumIds } = payload;
const commandPayload = {
name: commandNames.ALBUM_SEARCH,
albumIds
};
executeCommandHelper(commandPayload, dispatch).then((data) => {
dispatch(set({
section,
searchMissingCommandId: data.id
}));
});
}
});
@ -336,15 +366,23 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
[CLEAR_CALENDAR]: (state) => {
const {
view,
selectedFilterKey,
showUpcoming,
...otherDefaultState
} = defaultState;
[CLEAR_CALENDAR]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: []
}),
return Object.assign({}, state, otherDefaultState);
[SET_CALENDAR_OPTION]: function(state, { payload }) {
const options = state.options;
return {
...state,
options: {
...options,
...payload
}
};
}
}, defaultState, section);

View file

@ -121,6 +121,34 @@ function scheduleRemoveCommand(command, dispatch) {
}, 60000 * 5);
}
export function executeCommandHelper( payload, dispatch) {
// TODO: show a message for the user
if (lastCommand && isSameCommand(lastCommand, payload)) {
console.warn('Please wait at least 5 seconds before running this command again');
}
lastCommand = payload;
// clear last command after 5 seconds.
if (lastCommandTimeout) {
clearTimeout(lastCommandTimeout);
}
lastCommandTimeout = setTimeout(() => {
lastCommand = null;
}, 5000);
const promise = $.ajax({
url: '/command',
method: 'POST',
data: JSON.stringify(payload)
});
return promise.then((data) => {
dispatch(addCommand(data));
});
}
//
// Action Handlers
@ -128,31 +156,7 @@ export const actionHandlers = handleThunks({
[FETCH_COMMANDS]: createFetchHandler('commands', '/command'),
[EXECUTE_COMMAND]: function(getState, payload, dispatch) {
// TODO: show a message for the user
if (lastCommand && isSameCommand(lastCommand, payload)) {
console.warn('Please wait at least 5 seconds before running this command again');
}
lastCommand = payload;
// clear last command after 5 seconds.
if (lastCommandTimeout) {
clearTimeout(lastCommandTimeout);
}
lastCommandTimeout = setTimeout(() => {
lastCommand = null;
}, 5000);
const promise = $.ajax({
url: '/command',
method: 'POST',
data: JSON.stringify(payload)
});
promise.done((data) => {
dispatch(addCommand(data));
});
executeCommandHelper(payload, dispatch);
},
[CANCEL_COMMAND]: createRemoveItemHandler(section, '/command'),

View file

@ -278,11 +278,13 @@ export const reducers = createHandleActions({
[SET_HISTORY_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_HISTORY]: createClearReducer('history', {
[CLEAR_HISTORY]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: []
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);

View file

@ -36,6 +36,7 @@ export const defaultState = {
export const QUEUE_LOOKUP_ARTIST = 'importArtist/queueLookupArtist';
export const START_LOOKUP_ARTIST = 'importArtist/startLookupArtist';
export const CANCEL_LOOKUP_ARTIST = 'importArtist/cancelLookupArtist';
export const LOOKUP_UNSEARCHED_ARTIST = 'importArtist/lookupUnsearchedArtist';
export const CLEAR_IMPORT_ARTIST = 'importArtist/clearImportArtist';
export const SET_IMPORT_ARTIST_VALUE = 'importArtist/setImportArtistValue';
export const IMPORT_ARTIST = 'importArtist/importArtist';
@ -46,6 +47,7 @@ export const IMPORT_ARTIST = 'importArtist/importArtist';
export const queueLookupArtist = createThunk(QUEUE_LOOKUP_ARTIST);
export const startLookupArtist = createThunk(START_LOOKUP_ARTIST);
export const importArtist = createThunk(IMPORT_ARTIST);
export const lookupUnsearchedArtist = createThunk(LOOKUP_UNSEARCHED_ARTIST);
export const clearImportArtist = createAction(CLEAR_IMPORT_ARTIST);
export const cancelLookupArtist = createAction(CANCEL_LOOKUP_ARTIST);
@ -83,7 +85,7 @@ export const actionHandlers = handleThunks({
section,
...item,
term,
queued: true,
isQueued: true,
items: []
}));
@ -155,7 +157,7 @@ export const actionHandlers = handleThunks({
isPopulated: true,
error: null,
items: data,
queued: false,
isQueued: false,
selectedArtist: queued.selectedArtist || data[0],
updateOnly: true
}));
@ -168,7 +170,7 @@ export const actionHandlers = handleThunks({
isFetching: false,
isPopulated: false,
error: xhr,
queued: false,
isQueued: false,
updateOnly: true
}));
});
@ -180,6 +182,29 @@ export const actionHandlers = handleThunks({
});
},
[LOOKUP_UNSEARCHED_ARTIST]: function(getState, payload, dispatch) {
const state = getState().importArtist;
if (state.isLookingUpArtist) {
return;
}
state.items.forEach((item) => {
const id = item.id;
if (
!item.isPopulated &&
!queue.includes(id)
) {
queue.push(item.id);
}
});
if (queue.length) {
dispatch(startLookupArtist({ start: true }));
}
},
[IMPORT_ARTIST]: function(getState, payload, dispatch) {
dispatch(set({ section, isImporting: true }));
@ -251,7 +276,23 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
[CANCEL_LOOKUP_ARTIST]: function(state) {
return Object.assign({}, state, { isLookingUpArtist: false });
queue.splice(0, queue.length);
const items = state.items.map((item) => {
if (item.isQueued) {
return {
...item,
isQueued: false
};
}
return item;
});
return Object.assign({}, state, {
isLookingUpArtist: false,
items
});
},
[CLEAR_IMPORT_ARTIST]: function(state) {

View file

@ -24,6 +24,10 @@ const paged = `${section}.paged`;
// State
export const defaultState = {
options: {
includeUnknownArtistItems: false
},
status: {
isFetching: false,
isPopulated: false,
@ -54,6 +58,7 @@ export const defaultState = {
{
name: 'status',
columnLabel: 'Status',
isSortable: true,
isVisible: true,
isModifiable: false
},
@ -75,6 +80,12 @@ export const defaultState = {
isSortable: true,
isVisible: false
},
{
name: 'language',
label: 'Language',
isSortable: true,
isVisible: false
},
{
name: 'quality',
label: 'Quality',
@ -122,12 +133,20 @@ export const defaultState = {
};
export const persistState = [
'queue.options',
'queue.paged.pageSize',
'queue.paged.sortKey',
'queue.paged.sortDirection',
'queue.paged.columns'
];
//
// Helpers
function fetchDataAugmenter(getState, payload, data) {
data.includeUnknownArtistItems = getState().queue.options.includeUnknownArtistItems;
}
//
// Actions Types
@ -144,6 +163,7 @@ export const GOTO_LAST_QUEUE_PAGE = 'queue/gotoQueueLastPage';
export const GOTO_QUEUE_PAGE = 'queue/gotoQueuePage';
export const SET_QUEUE_SORT = 'queue/setQueueSort';
export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption';
export const SET_QUEUE_OPTION = 'queue/setQueueOption';
export const CLEAR_QUEUE = 'queue/clearQueue';
export const GRAB_QUEUE_ITEM = 'queue/grabQueueItem';
@ -167,6 +187,7 @@ export const gotoQueueLastPage = createThunk(GOTO_LAST_QUEUE_PAGE);
export const gotoQueuePage = createThunk(GOTO_QUEUE_PAGE);
export const setQueueSort = createThunk(SET_QUEUE_SORT);
export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION);
export const setQueueOption = createAction(SET_QUEUE_OPTION);
export const clearQueue = createAction(CLEAR_QUEUE);
export const grabQueueItem = createThunk(GRAB_QUEUE_ITEM);
@ -217,7 +238,9 @@ export const actionHandlers = handleThunks({
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_QUEUE_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_QUEUE_PAGE,
[serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT
}),
},
fetchDataAugmenter
),
[GRAB_QUEUE_ITEM]: function(getState, payload, dispatch) {
const id = payload.id;
@ -392,11 +415,25 @@ export const reducers = createHandleActions({
[SET_QUEUE_TABLE_OPTION]: createSetTableOptionReducer(paged),
[SET_QUEUE_OPTION]: function(state, { payload }) {
const queueOptions = state.options;
return {
...state,
options: {
...queueOptions,
...payload
}
};
},
[CLEAR_QUEUE]: createClearReducer(paged, {
isFetching: false,
isPopulated: false,
error: null,
items: []
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);

View file

@ -11,6 +11,9 @@ import createHandleActions from './Creators/createHandleActions';
// Variables
export const section = 'releases';
export const albumSection = 'releases.album';
export const artistSection = 'releases.artist';
let abortCurrentRequest = null;
//
@ -139,12 +142,21 @@ export const defaultState = {
label: 'Rejections',
type: filterBuilderTypes.NUMBER
}
]
],
album: {
selectedFilterKey: 'all'
},
artist: {
selectedFilterKey: 'discography-pack'
}
};
export const persistState = [
'releases.selectedFilterKey',
'releases.customFilters'
'releases.album.customFilters',
'releases.artist.customFilters'
];
//
@ -156,7 +168,8 @@ 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 SET_ALBUM_RELEASES_FILTER = 'releases/setAlbumReleasesFilter';
export const SET_ARTIST_RELEASES_FILTER = 'releases/setArtistReleasesFilter';
//
// Action Creators
@ -167,7 +180,8 @@ 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 setAlbumReleasesFilter = createAction(SET_ALBUM_RELEASES_FILTER);
export const setArtistReleasesFilter = createAction(SET_ARTIST_RELEASES_FILTER);
//
// Helpers
@ -231,7 +245,13 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
[CLEAR_RELEASES]: (state) => {
return Object.assign({}, state, defaultState);
const {
album,
artist,
...otherDefaultState
} = defaultState;
return Object.assign({}, state, otherDefaultState);
},
[UPDATE_RELEASE]: (state, { payload }) => {
@ -254,6 +274,7 @@ export const reducers = createHandleActions({
},
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section)
[SET_ALBUM_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(albumSection),
[SET_ARTIST_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(artistSection)
}, defaultState, section);

View file

@ -18,8 +18,8 @@ import namingExamples from './Settings/namingExamples';
import notifications from './Settings/notifications';
import qualityDefinitions from './Settings/qualityDefinitions';
import qualityProfiles from './Settings/qualityProfiles';
import releaseProfiles from './Settings/releaseProfiles';
import remotePathMappings from './Settings/remotePathMappings';
import restrictions from './Settings/restrictions';
import ui from './Settings/ui';
export * from './Settings/delayProfiles';
@ -39,8 +39,8 @@ export * from './Settings/namingExamples';
export * from './Settings/notifications';
export * from './Settings/qualityDefinitions';
export * from './Settings/qualityProfiles';
export * from './Settings/releaseProfiles';
export * from './Settings/remotePathMappings';
export * from './Settings/restrictions';
export * from './Settings/ui';
//
@ -71,8 +71,8 @@ export const defaultState = {
notifications: notifications.defaultState,
qualityDefinitions: qualityDefinitions.defaultState,
qualityProfiles: qualityProfiles.defaultState,
releaseProfiles: releaseProfiles.defaultState,
remotePathMappings: remotePathMappings.defaultState,
restrictions: restrictions.defaultState,
ui: ui.defaultState
};
@ -111,8 +111,8 @@ export const actionHandlers = handleThunks({
...notifications.actionHandlers,
...qualityDefinitions.actionHandlers,
...qualityProfiles.actionHandlers,
...releaseProfiles.actionHandlers,
...remotePathMappings.actionHandlers,
...restrictions.actionHandlers,
...ui.actionHandlers
});
@ -142,8 +142,8 @@ export const reducers = createHandleActions({
...notifications.reducers,
...qualityDefinitions.reducers,
...qualityProfiles.reducers,
...releaseProfiles.reducers,
...remotePathMappings.reducers,
...restrictions.reducers,
...ui.reducers
}, defaultState, section);

View file

@ -5,6 +5,7 @@ import { filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import { setAppValue } from 'Store/Actions/appActions';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createFetchHandler from './Creators/createFetchHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createHandleActions from './Creators/createHandleActions';
@ -79,25 +80,30 @@ export const defaultState = {
columns: [
{
name: 'level',
isSortable: true,
isVisible: true
columnLabel: 'Level',
isSortable: false,
isVisible: true,
isModifiable: false
},
{
name: 'logger',
label: 'Component',
isSortable: true,
isVisible: true
isSortable: false,
isVisible: true,
isModifiable: false
},
{
name: 'message',
label: 'Message',
isVisible: true
isVisible: true,
isModifiable: false
},
{
name: 'time',
label: 'Time',
isSortable: true,
isVisible: true
isVisible: true,
isModifiable: false
},
{
name: 'actions',
@ -199,7 +205,8 @@ export const GOTO_LAST_LOGS_PAGE = 'system/logs/gotoLogsLastPage';
export const GOTO_LOGS_PAGE = 'system/logs/gotoLogsPage';
export const SET_LOGS_SORT = 'system/logs/setLogsSort';
export const SET_LOGS_FILTER = 'system/logs/setLogsFilter';
export const SET_LOGS_TABLE_OPTION = 'system/logs/ssetLogsTableOption';
export const SET_LOGS_TABLE_OPTION = 'system/logs/setLogsTableOption';
export const CLEAR_LOGS_TABLE = 'system/logs/clearLogsTable';
export const FETCH_LOG_FILES = 'system/logFiles/fetchLogFiles';
export const FETCH_UPDATE_LOG_FILES = 'system/updateLogFiles/fetchUpdateLogFiles';
@ -233,6 +240,7 @@ export const gotoLogsPage = createThunk(GOTO_LOGS_PAGE);
export const setLogsSort = createThunk(SET_LOGS_SORT);
export const setLogsFilter = createThunk(SET_LOGS_FILTER);
export const setLogsTableOption = createAction(SET_LOGS_TABLE_OPTION);
export const clearLogsTable = createAction(CLEAR_LOGS_TABLE);
export const fetchLogFiles = createThunk(FETCH_LOG_FILES);
export const fetchUpdateLogFiles = createThunk(FETCH_UPDATE_LOG_FILES);
@ -370,6 +378,15 @@ export const reducers = createHandleActions({
};
},
[SET_LOGS_TABLE_OPTION]: createSetTableOptionReducer('logs')
[SET_LOGS_TABLE_OPTION]: createSetTableOptionReducer('logs'),
[CLEAR_LOGS_TABLE]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);

View file

@ -33,11 +33,6 @@ export const defaultState = {
isSortable: true,
isVisible: true
},
// {
// name: 'episode',
// label: 'Episode',
// isVisible: true
// },
{
name: 'albumTitle',
label: 'Album Title',
@ -112,11 +107,6 @@ export const defaultState = {
isSortable: true,
isVisible: true
},
// {
// name: 'episode',
// label: 'Episode',
// isVisible: true
// },
{
name: 'albumTitle',
label: 'Album Title',
@ -310,7 +300,9 @@ export const reducers = createHandleActions({
isFetching: false,
isPopulated: false,
error: null,
items: []
items: [],
totalPages: 0,
totalRecords: 0
}
),
@ -320,7 +312,9 @@ export const reducers = createHandleActions({
isFetching: false,
isPopulated: false,
error: null,
items: []
items: [],
totalPages: 0,
totalRecords: 0
}
)

View file

@ -1,6 +1,7 @@
import _ from 'lodash';
import persistState from 'redux-localstorage';
import actions from 'Store/Actions';
import migrate from 'Store/Migrators/migrate';
const columnPaths = [];
@ -90,4 +91,11 @@ const config = {
key: 'lidarr'
};
export default persistState(paths, config);
export default function createPersistState() {
// Migrate existing local storage before proceeding
const persistedState = JSON.parse(localStorage.getItem(config.key));
migrate(persistedState);
localStorage.setItem(config.key, serialize(persistedState));
return persistState(paths, config);
}

View file

@ -2,7 +2,7 @@ import { applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import createSentryMiddleware from './createSentryMiddleware';
import persistState from './persistState';
import createPersistState from './createPersistState';
export default function(history) {
const middlewares = [];
@ -20,6 +20,6 @@ export default function(history) {
return composeEnhancers(
applyMiddleware(...middlewares),
persistState
createPersistState()
);
}

View file

@ -0,0 +1,5 @@
import migrateAddArtistDefaults from './migrateAddArtistDefaults';
export default function migrate(persistedState) {
migrateAddArtistDefaults(persistedState);
}

View file

@ -0,0 +1,14 @@
import { get } from 'lodash';
import monitorOptions from 'Utilities/Artist/monitorOptions';
export default function migrateAddArtistDefaults(persistedState) {
const monitor = get(persistedState, 'addArtist.defaults.monitor');
if (!monitor) {
return;
}
if (!monitorOptions.find((option) => option.key === monitor)) {
persistedState.addArtist.defaults.monitor = monitorOptions[0].key;
}
}

View file

@ -4,8 +4,8 @@ import createAllArtistSelector from './createAllArtistSelector';
function createArtistCountSelector() {
return createSelector(
createAllArtistSelector(),
(series) => {
return series.length;
(artists) => {
return artists.length;
}
);
}

View file

@ -52,7 +52,14 @@ function filter(items, state) {
const predicate = filterTypePredicates[type];
if (Array.isArray(value)) {
accepted = value.some((v) => predicate(item[key], v));
if (
type === filterTypes.NOT_CONTAINS ||
type === filterTypes.NOT_EQUAL
) {
accepted = value.every((v) => predicate(item[key], v));
} else {
accepted = value.some((v) => predicate(item[key], v));
}
} else {
accepted = predicate(item[key], value);
}

View file

@ -12,7 +12,7 @@ function createProviderSettingsSelector(sectionName) {
const settings = selectSettings(Object.assign({ name: '' }, item), section.pendingChanges, section.saveError);
const {
isFetchingSchema: isFetching,
isSchemaFetching: isFetching,
isSchemaPopulated: isPopulated,
schemaError: error,
isSaving,

View file

@ -3,14 +3,18 @@ import { createSelector } from 'reselect';
function createQueueItemSelector() {
return createSelector(
(state, { albumId }) => albumId,
(state) => state.queue.details,
(state) => state.queue.details.items,
(albumId, details) => {
if (!albumId) {
return null;
}
return details.items.find((item) => {
return item.album.id === albumId;
return details.find((item) => {
if (item.album) {
return item.album.id === albumId;
}
return false;
});
}
);

View file

@ -6,7 +6,7 @@ function createTrackFileSelector() {
(state) => state.trackFiles,
(trackFileId, trackFiles) => {
if (!trackFileId) {
return null;
return;
}
return trackFiles.items.find((trackFile) => trackFile.id === trackFileId);