mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
UI Updates (Cancel Import, Move Artist, Manual Import from Artist)
Ability to cancel an import lookup/search at any point. Ability to move artist path from Artist Edit or bulk move from Mass Editor. Trigger manual import for Artist path from Artist Detail page. Pulled from Sonarr
This commit is contained in:
parent
5fae202760
commit
d8c89f5bbd
79 changed files with 1075 additions and 376 deletions
|
@ -6,13 +6,13 @@ function createRemoveItemHandler(section, url) {
|
|||
return function(getState, payload, dispatch) {
|
||||
const {
|
||||
id,
|
||||
...queryParms
|
||||
...queryParams
|
||||
} = payload;
|
||||
|
||||
dispatch(set({ section, isDeleting: true }));
|
||||
|
||||
const ajaxOptions = {
|
||||
url: `${url}/${id}?${$.param(queryParms, true)}`,
|
||||
url: `${url}/${id}?${$.param(queryParams, true)}`,
|
||||
method: 'DELETE'
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import $ from 'jquery';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getProviderState from 'Utilities/State/getProviderState';
|
||||
|
@ -14,15 +15,19 @@ export function createCancelSaveProviderHandler(section) {
|
|||
};
|
||||
}
|
||||
|
||||
function createSaveProviderHandler(section, url) {
|
||||
function createSaveProviderHandler(section, url, options = {}) {
|
||||
return function(getState, payload, dispatch) {
|
||||
dispatch(set({ section, isSaving: true }));
|
||||
|
||||
const id = payload.id;
|
||||
const {
|
||||
id,
|
||||
queryParams = {}
|
||||
} = payload;
|
||||
|
||||
const saveData = getProviderState(payload, getState, section);
|
||||
|
||||
const ajaxOptions = {
|
||||
url,
|
||||
url: `${url}?${$.param(queryParams, true)}`,
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
|
@ -30,7 +35,7 @@ function createSaveProviderHandler(section, url) {
|
|||
};
|
||||
|
||||
if (id) {
|
||||
ajaxOptions.url = `${url}/${id}`;
|
||||
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
|
||||
ajaxOptions.method = 'PUT';
|
||||
}
|
||||
|
||||
|
|
|
@ -46,8 +46,31 @@ export const TOGGLE_ALBUM_MONITORED = 'artist/toggleAlbumMonitored';
|
|||
// Action Creators
|
||||
|
||||
export const fetchArtist = createThunk(FETCH_ARTIST);
|
||||
export const saveArtist = createThunk(SAVE_ARTIST);
|
||||
export const deleteArtist = createThunk(DELETE_ARTIST);
|
||||
export const saveArtist = createThunk(SAVE_ARTIST, (payload) => {
|
||||
const newPayload = {
|
||||
...payload
|
||||
};
|
||||
|
||||
if (payload.moveFiles) {
|
||||
newPayload.queryParams = {
|
||||
moveFiles: true
|
||||
};
|
||||
}
|
||||
|
||||
delete newPayload.moveFiles;
|
||||
|
||||
return newPayload;
|
||||
});
|
||||
|
||||
export const deleteArtist = createThunk(DELETE_ARTIST, (payload) => {
|
||||
return {
|
||||
...payload,
|
||||
queryParams: {
|
||||
deleteFiles: payload.deleteFiles
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const toggleArtistMonitored = createThunk(TOGGLE_ARTIST_MONITORED);
|
||||
export const toggleAlbumMonitored = createThunk(TOGGLE_ALBUM_MONITORED);
|
||||
|
||||
|
@ -58,20 +81,25 @@ export const setArtistValue = createAction(SET_ARTIST_VALUE, (payload) => {
|
|||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Helpers
|
||||
|
||||
function getSaveAjaxOptions({ ajaxOptions, payload }) {
|
||||
if (payload.moveFolder) {
|
||||
ajaxOptions.url = `${ajaxOptions.url}?moveFolder=true`;
|
||||
}
|
||||
|
||||
return ajaxOptions;
|
||||
}
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[FETCH_ARTIST]: createFetchHandler(section, '/artist'),
|
||||
|
||||
[SAVE_ARTIST]: createSaveProviderHandler(
|
||||
section, '/artist'),
|
||||
|
||||
[DELETE_ARTIST]: createRemoveItemHandler(
|
||||
section,
|
||||
'/artist'
|
||||
),
|
||||
[SAVE_ARTIST]: createSaveProviderHandler(section, '/artist', { getAjaxOptions: getSaveAjaxOptions }),
|
||||
[DELETE_ARTIST]: createRemoveItemHandler(section, '/artist'),
|
||||
|
||||
[TOGGLE_ARTIST_MONITORED]: (getState, payload, dispatch) => {
|
||||
const {
|
||||
|
@ -115,7 +143,7 @@ export const actionHandlers = handleThunks({
|
|||
});
|
||||
},
|
||||
|
||||
[TOGGLE_ALBUM_MONITORED]: (getState, payload, dispatch) => {
|
||||
[TOGGLE_ALBUM_MONITORED]: function(getState, payload, dispatch) {
|
||||
const {
|
||||
artistId: id,
|
||||
seasonNumber,
|
||||
|
|
|
@ -112,7 +112,7 @@ export const actionHandlers = handleThunks({
|
|||
});
|
||||
|
||||
promise.done(() => {
|
||||
// SignaR will take care of removing the serires from the collection
|
||||
// SignalR will take care of removing the artist from the collection
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
|
|
|
@ -28,6 +28,7 @@ export const defaultState = {
|
|||
detailedProgressBar: false,
|
||||
size: 'large',
|
||||
showTitle: false,
|
||||
showMonitored: true,
|
||||
showQualityProfile: true
|
||||
},
|
||||
|
||||
|
@ -35,12 +36,14 @@ export const defaultState = {
|
|||
detailedProgressBar: false,
|
||||
size: 'large',
|
||||
showTitle: false,
|
||||
showMonitored: true,
|
||||
showQualityProfile: true
|
||||
},
|
||||
|
||||
overviewOptions: {
|
||||
detailedProgressBar: false,
|
||||
size: 'medium',
|
||||
showMonitored: true,
|
||||
showNetwork: true,
|
||||
showQualityProfile: true,
|
||||
showPreviousAiring: false,
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import getNewArtist from 'Utilities/Artist/getNewArtist';
|
||||
|
@ -15,14 +16,14 @@ import { fetchRootFolders } from './rootFolderActions';
|
|||
|
||||
export const section = 'importArtist';
|
||||
let concurrentLookups = 0;
|
||||
let abortCurrentLookup = null;
|
||||
const queue = [];
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isLookingUpArtist: false,
|
||||
isImporting: false,
|
||||
isImported: false,
|
||||
importError: null,
|
||||
|
@ -34,9 +35,10 @@ export const defaultState = {
|
|||
|
||||
export const QUEUE_LOOKUP_ARTIST = 'importArtist/queueLookupArtist';
|
||||
export const START_LOOKUP_ARTIST = 'importArtist/startLookupArtist';
|
||||
export const CLEAR_IMPORT_ARTIST = 'importArtist/importArtist';
|
||||
export const SET_IMPORT_ARTIST_VALUE = 'importArtist/clearImportArtist';
|
||||
export const IMPORT_ARTIST = 'importArtist/setImportArtistValue';
|
||||
export const CANCEL_LOOKUP_ARTIST = 'importArtist/cancelLookupArtist';
|
||||
export const CLEAR_IMPORT_ARTIST = 'importArtist/clearImportArtist';
|
||||
export const SET_IMPORT_ARTIST_VALUE = 'importArtist/setImportArtistValue';
|
||||
export const IMPORT_ARTIST = 'importArtist/importArtist';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
@ -45,10 +47,10 @@ export const queueLookupArtist = createThunk(QUEUE_LOOKUP_ARTIST);
|
|||
export const startLookupArtist = createThunk(START_LOOKUP_ARTIST);
|
||||
export const importArtist = createThunk(IMPORT_ARTIST);
|
||||
export const clearImportArtist = createAction(CLEAR_IMPORT_ARTIST);
|
||||
export const cancelLookupArtist = createAction(CANCEL_LOOKUP_ARTIST);
|
||||
|
||||
export const setImportArtistValue = createAction(SET_IMPORT_ARTIST_VALUE, (payload) => {
|
||||
return {
|
||||
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
|
@ -63,7 +65,8 @@ export const actionHandlers = handleThunks({
|
|||
const {
|
||||
name,
|
||||
path,
|
||||
term
|
||||
term,
|
||||
topOfQueue = false
|
||||
} = payload;
|
||||
|
||||
const state = getState().importArtist;
|
||||
|
@ -84,8 +87,20 @@ export const actionHandlers = handleThunks({
|
|||
items: []
|
||||
}));
|
||||
|
||||
const itemIndex = queue.indexOf(item.id);
|
||||
|
||||
if (itemIndex >= 0) {
|
||||
queue.splice(itemIndex, 1);
|
||||
}
|
||||
|
||||
if (topOfQueue) {
|
||||
queue.unshift(item.id);
|
||||
} else {
|
||||
queue.push(item.id);
|
||||
}
|
||||
|
||||
if (term && term.length > 2) {
|
||||
dispatch(startLookupArtist());
|
||||
dispatch(startLookupArtist({ start: true }));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -95,13 +110,27 @@ export const actionHandlers = handleThunks({
|
|||
}
|
||||
|
||||
const state = getState().importArtist;
|
||||
const queued = _.find(state.items, { queued: true });
|
||||
|
||||
if (!queued) {
|
||||
const {
|
||||
isLookingUpArtist,
|
||||
items
|
||||
} = state;
|
||||
|
||||
const queueId = queue[0];
|
||||
|
||||
if (payload.start && !isLookingUpArtist) {
|
||||
dispatch(set({ section, isLookingUpArtist: true }));
|
||||
} else if (!isLookingUpArtist) {
|
||||
return;
|
||||
} else if (!queueId) {
|
||||
dispatch(set({ section, isLookingUpArtist: false }));
|
||||
return;
|
||||
}
|
||||
|
||||
concurrentLookups++;
|
||||
queue.splice(0, 1);
|
||||
|
||||
const queued = items.find((i) => i.id === queueId);
|
||||
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
|
@ -109,14 +138,16 @@ export const actionHandlers = handleThunks({
|
|||
isFetching: true
|
||||
}));
|
||||
|
||||
const promise = $.ajax({
|
||||
const { request, abortRequest } = createAjaxRequest({
|
||||
url: '/artist/lookup',
|
||||
data: {
|
||||
term: queued.term
|
||||
}
|
||||
});
|
||||
|
||||
promise.done((data) => {
|
||||
abortCurrentLookup = abortRequest;
|
||||
|
||||
request.done((data) => {
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
id: queued.id,
|
||||
|
@ -125,23 +156,26 @@ export const actionHandlers = handleThunks({
|
|||
error: null,
|
||||
items: data,
|
||||
queued: false,
|
||||
selectedArtist: queued.selectedArtist || data[0]
|
||||
selectedArtist: queued.selectedArtist || data[0],
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
request.fail((xhr) => {
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
id: queued.id,
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: xhr,
|
||||
queued: false
|
||||
queued: false,
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
|
||||
promise.always(() => {
|
||||
request.always(() => {
|
||||
concurrentLookups--;
|
||||
|
||||
dispatch(startLookupArtist());
|
||||
});
|
||||
},
|
||||
|
@ -159,7 +193,7 @@ export const actionHandlers = handleThunks({
|
|||
|
||||
// Make sure we have a selected artist and
|
||||
// the same artist hasn't been added yet.
|
||||
if (selectedArtist && !_.some(acc, { tvdbId: selectedArtist.tvdbId })) {
|
||||
if (selectedArtist && !_.some(acc, { foreignArtistId: selectedArtist.foreignArtistId })) {
|
||||
const newArtist = getNewArtist(_.cloneDeep(selectedArtist), item);
|
||||
newArtist.path = item.path;
|
||||
|
||||
|
@ -216,7 +250,19 @@ export const actionHandlers = handleThunks({
|
|||
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[CANCEL_LOOKUP_ARTIST]: function(state) {
|
||||
return Object.assign({}, state, { isLookingUpArtist: false });
|
||||
},
|
||||
|
||||
[CLEAR_IMPORT_ARTIST]: function(state) {
|
||||
if (abortCurrentLookup) {
|
||||
abortCurrentLookup();
|
||||
|
||||
abortCurrentLookup = null;
|
||||
}
|
||||
|
||||
queue.splice(0, queue.length);
|
||||
|
||||
return Object.assign({}, state, defaultState);
|
||||
},
|
||||
|
||||
|
|
|
@ -35,24 +35,22 @@ export const addTag = createThunk(ADD_TAG);
|
|||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[FETCH_TAGS]: createFetchHandler('tags', '/tag'),
|
||||
[FETCH_TAGS]: createFetchHandler(section, '/tag'),
|
||||
|
||||
[ADD_TAG]: function(payload) {
|
||||
return (dispatch, getState) => {
|
||||
const promise = $.ajax({
|
||||
url: '/tag',
|
||||
method: 'POST',
|
||||
data: JSON.stringify(payload.tag)
|
||||
});
|
||||
[ADD_TAG]: function(getState, payload, dispatch) {
|
||||
const promise = $.ajax({
|
||||
url: '/tag',
|
||||
method: 'POST',
|
||||
data: JSON.stringify(payload.tag)
|
||||
});
|
||||
|
||||
promise.done((data) => {
|
||||
const tags = getState().tags.items.slice();
|
||||
tags.push(data);
|
||||
promise.done((data) => {
|
||||
const tags = getState().tags.items.slice();
|
||||
tags.push(data);
|
||||
|
||||
dispatch(update({ section: 'tags', data: tags }));
|
||||
payload.onTagCreated(data);
|
||||
});
|
||||
};
|
||||
dispatch(update({ section, data: tags }));
|
||||
payload.onTagCreated(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue