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:
Qstick 2017-12-29 22:23:04 -05:00
parent 5fae202760
commit d8c89f5bbd
79 changed files with 1075 additions and 376 deletions

View file

@ -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'
};

View file

@ -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';
}

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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);
},

View file

@ -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);
});
}
});