From 22d9c5e6665feb1df6fc1e76d0f4d58228da9f83 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 10 Sep 2017 21:20:36 -0400 Subject: [PATCH] Fix GitIgnore, Update from Sonarr --- .gitignore | 2 - frontend/src/App/App.js | 13 +- .../Components/Page/Sidebar/PageSidebar.js | 10 +- .../EditDownloadClientModalConnector.js | 38 +++- .../Indexers/EditIndexerModalConnector.js | 38 +++- frontend/src/Settings/Settings.css | 18 ++ frontend/src/Settings/Settings.js | 122 +++++++++++ frontend/src/Settings/UI/UISettings.js | 197 ++++++++++++++++++ .../src/Settings/UI/UISettingsConnector.js | 82 ++++++++ .../Creators/createSaveProviderHandler.js | 23 +- .../Creators/createTestProviderHandler.js | 23 +- frontend/src/Store/Actions/actionTypes.js | 7 +- .../Store/Actions/addArtistActionHandlers.js | 24 +-- .../Store/Actions/settingsActionHandlers.js | 16 +- frontend/src/Store/Actions/settingsActions.js | 6 + frontend/src/Styles/Variables/colors.js | 2 + frontend/src/Utilities/createAjaxRequest.js | 33 +++ 17 files changed, 603 insertions(+), 51 deletions(-) create mode 100644 frontend/src/Settings/Settings.css create mode 100644 frontend/src/Settings/Settings.js create mode 100644 frontend/src/Settings/UI/UISettings.js create mode 100644 frontend/src/Settings/UI/UISettingsConnector.js create mode 100644 frontend/src/Utilities/createAjaxRequest.js diff --git a/.gitignore b/.gitignore index e52e5375c..cfac0d699 100644 --- a/.gitignore +++ b/.gitignore @@ -120,8 +120,6 @@ _tests/ setup/Output/ *.~is -UI/ - #VS outout folders bin obj diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index 8662b3ec9..2d49476f9 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -20,7 +20,7 @@ import QueueConnector from 'Activity/Queue/QueueConnector'; import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector'; import MissingConnector from 'Wanted/Missing/MissingConnector'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; -import UISettingsConnector from 'Settings/UI/UISettingsConnector'; +import Settings from 'Settings/Settings'; import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector'; import Profiles from 'Settings/Profiles/Profiles'; import Quality from 'Settings/Quality/Quality'; @@ -29,6 +29,7 @@ import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSetti import NotificationSettings from 'Settings/Notifications/NotificationSettings'; import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; +import UISettingsConnector from 'Settings/UI/UISettingsConnector'; import Status from 'System/Status/Status'; import TasksConnector from 'System/Tasks/TasksConnector'; import BackupsConnector from 'System/Backup/BackupsConnector'; @@ -142,8 +143,9 @@ function App({ store, history }) { */} + + {/* System */} diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js index 91d95a493..5bb0e873b 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.js +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js @@ -89,12 +89,8 @@ const links = [ { iconName: icons.SETTINGS, title: 'Settings', - to: '/settings/ui', + to: '/settings', children: [ - { - title: 'UI', - to: '/settings/ui' - }, { title: 'Media Management', to: '/settings/mediamanagement' @@ -126,6 +122,10 @@ const links = [ { title: 'General', to: '/settings/general' + }, + { + title: 'UI', + to: '/settings/ui' } ] }, diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalConnector.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalConnector.js index e6778452b..7991f4edc 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalConnector.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalConnector.js @@ -2,11 +2,24 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { clearPendingChanges } from 'Store/Actions/baseActions'; +import { cancelTestDownloadClient, cancelSaveDownloadClient } from 'Store/Actions/settingsActions'; import EditDownloadClientModal from './EditDownloadClientModal'; -const mapDispatchToProps = { - clearPendingChanges -}; +function createMapDispatchToProps(dispatch, props) { + return { + dispatchClearPendingChanges() { + dispatch(clearPendingChanges); + }, + + dispatchCancelTestDownloadClient() { + dispatch(cancelTestDownloadClient); + }, + + dispatchCancelSaveDownloadClient() { + dispatch(cancelSaveDownloadClient); + } + }; +} class EditDownloadClientModalConnector extends Component { @@ -14,7 +27,9 @@ class EditDownloadClientModalConnector extends Component { // Listeners onModalClose = () => { - this.props.clearPendingChanges({ section: 'downloadClients' }); + this.props.dispatchClearPendingChanges({ section: 'downloadClients' }); + this.props.dispatchCancelTestDownloadClient({ section: 'downloadClients' }); + this.props.dispatchCancelSaveDownloadClient({ section: 'downloadClients' }); this.props.onModalClose(); } @@ -22,9 +37,16 @@ class EditDownloadClientModalConnector extends Component { // Render render() { + const { + dispatchClearPendingChanges, + dispatchCancelTestDownloadClient, + dispatchCancelSaveDownloadClient, + ...otherProps + } = this.props; + return ( ); @@ -33,7 +55,9 @@ class EditDownloadClientModalConnector extends Component { EditDownloadClientModalConnector.propTypes = { onModalClose: PropTypes.func.isRequired, - clearPendingChanges: PropTypes.func.isRequired + dispatchClearPendingChanges: PropTypes.func.isRequired, + dispatchCancelTestDownloadClient: PropTypes.func.isRequired, + dispatchCancelSaveDownloadClient: PropTypes.func.isRequired }; -export default connect(null, mapDispatchToProps)(EditDownloadClientModalConnector); +export default connect(null, createMapDispatchToProps)(EditDownloadClientModalConnector); diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalConnector.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalConnector.js index 9e34e93c4..3168e90f0 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalConnector.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalConnector.js @@ -2,11 +2,24 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { clearPendingChanges } from 'Store/Actions/baseActions'; +import { cancelTestIndexer, cancelSaveIndexer } from 'Store/Actions/settingsActions'; import EditIndexerModal from './EditIndexerModal'; -const mapDispatchToProps = { - clearPendingChanges -}; +function createMapDispatchToProps(dispatch, props) { + return { + dispatchClearPendingChanges() { + dispatch(clearPendingChanges); + }, + + dispatchCancelTestIndexer() { + dispatch(cancelTestIndexer); + }, + + dispatchCancelSaveIndexer() { + dispatch(cancelSaveIndexer); + } + }; +} class EditIndexerModalConnector extends Component { @@ -14,7 +27,9 @@ class EditIndexerModalConnector extends Component { // Listeners onModalClose = () => { - this.props.clearPendingChanges({ section: 'indexers' }); + this.props.dispatchClearPendingChanges({ section: 'indexers' }); + this.props.dispatchCancelTestIndexer({ section: 'indexers' }); + this.props.dispatchCancelSaveIndexer({ section: 'indexers' }); this.props.onModalClose(); } @@ -22,9 +37,16 @@ class EditIndexerModalConnector extends Component { // Render render() { + const { + dispatchClearPendingChanges, + dispatchCancelTestIndexer, + dispatchCancelSaveIndexer, + ...otherProps + } = this.props; + return ( ); @@ -33,7 +55,9 @@ class EditIndexerModalConnector extends Component { EditIndexerModalConnector.propTypes = { onModalClose: PropTypes.func.isRequired, - clearPendingChanges: PropTypes.func.isRequired + dispatchClearPendingChanges: PropTypes.func.isRequired, + dispatchCancelTestIndexer: PropTypes.func.isRequired, + dispatchCancelSaveIndexer: PropTypes.func.isRequired }; -export default connect(null, mapDispatchToProps)(EditIndexerModalConnector); +export default connect(null, createMapDispatchToProps)(EditIndexerModalConnector); diff --git a/frontend/src/Settings/Settings.css b/frontend/src/Settings/Settings.css new file mode 100644 index 000000000..497ef47c0 --- /dev/null +++ b/frontend/src/Settings/Settings.css @@ -0,0 +1,18 @@ +.link { + composes: link from 'Components/Link/Link.css'; + + border-bottom: 1px solid #e5e5e5; + color: #3a3f51; + font-size: 21px; + + &:hover { + color: #616573; + text-decoration: none; + } +} + +.summary { + margin-top: 10px; + margin-bottom: 30px; + color: $dimColor; +} diff --git a/frontend/src/Settings/Settings.js b/frontend/src/Settings/Settings.js new file mode 100644 index 000000000..9c568056e --- /dev/null +++ b/frontend/src/Settings/Settings.js @@ -0,0 +1,122 @@ +import React from 'react'; +import Link from 'Components/Link/Link'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; +import SettingsToolbarConnector from './SettingsToolbarConnector'; +import styles from './Settings.css'; + +function Settings() { + return ( + + + + + + Media Management + + +
+ Naming and file management settings +
+ + + Profiles + + +
+ Quality, Language and Delay profiles +
+ + + Quality + + +
+ Quality sizes and naming +
+ + + Indexers + + +
+ Indexers and release restrictions +
+ + + Download Clients + + +
+ Download clients, download handling and remote path mappings +
+ + + Connect + + +
+ Notifications, connections to media servers/players and custom scripts +
+ + + Metadata + + +
+ Create metadata files when episodes are imported or series are refreshed +
+ + + General + + +
+ Port, SSL, username/password, proxy, analytics and updates +
+ + + UI + + +
+ Calendar, date and color impaired options +
+
+
+ ); +} + +Settings.propTypes = { +}; + +export default Settings; diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js new file mode 100644 index 000000000..7d0b09932 --- /dev/null +++ b/frontend/src/Settings/UI/UISettings.js @@ -0,0 +1,197 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { inputTypes } from 'Helpers/Props'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import FieldSet from 'Components/FieldSet'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; +import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import Form from 'Components/Form/Form'; +import FormGroup from 'Components/Form/FormGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import FormInputGroup from 'Components/Form/FormInputGroup'; + +class UISettings extends Component { + + // + // Render + + render() { + const { + isFetching, + error, + settings, + hasSettings, + onInputChange, + onSavePress, + ...otherProps + } = this.props; + + const firstDayOfWeekOptions = [ + { key: 0, value: 'Sunday' }, + { key: 1, value: 'Monday' } + ]; + + const weekColumnOptions = [ + { key: 'ddd M/D', value: 'Tue 3/5' }, + { key: 'ddd MM/DD', value: 'Tue 03/05' }, + { key: 'ddd D/M', value: 'Tue 5/3' }, + { key: 'ddd DD/MM', value: 'Tue 05/03' } + ]; + + const shortDateFormatOptions = [ + { key: 'MMM D YYYY', value: 'Mar 5 2014' }, + { key: 'DD MMM YYYY', value: '5 Mar 2014' }, + { key: 'MM/D/YYYY', value: '03/5/2014' }, + { key: 'MM/DD/YYYY', value: '03/05/2014' }, + { key: 'DD/MM/YYYY', value: '05/03/2014' }, + { key: 'YYYY-MM-DD', value: '2014-03-05' } + ]; + + const longDateFormatOptions = [ + { key: 'dddd, MMMM D YYYY', value: 'Tuesday, March 5, 2014' }, + { key: 'dddd, D MMMM YYYY', value: 'Tuesday, 5 March, 2014' } + ]; + + const timeFormatOptions = [ + { key: 'h(:mm)a', value: '5pm/5:30pm' }, + { key: 'HH:mm', value: '17:00/17:30' } + ]; + + return ( + + + + + { + isFetching && + + } + + { + !isFetching && error && +
Unable to load UI settings
+ } + + { + hasSettings && !isFetching && !error && +
+
+ + First Day of Week + + + + + + Week Column Header + + + +
+ +
+ + Short Date Format + + + + + + Long Date Format + + + + + + Time Format + + + + + + Show Relative Dates + + +
+ +
+ + Enable Color-Impaired mode + + +
+
+ } +
+
+ ); + } + +} + +UISettings.propTypes = { + isFetching: PropTypes.bool.isRequired, + error: PropTypes.object, + settings: PropTypes.object.isRequired, + hasSettings: PropTypes.bool.isRequired, + onSavePress: PropTypes.func.isRequired, + onInputChange: PropTypes.func.isRequired +}; + +export default UISettings; diff --git a/frontend/src/Settings/UI/UISettingsConnector.js b/frontend/src/Settings/UI/UISettingsConnector.js new file mode 100644 index 000000000..5c2765f96 --- /dev/null +++ b/frontend/src/Settings/UI/UISettingsConnector.js @@ -0,0 +1,82 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { createSelector } from 'reselect'; +import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector'; +import { setUISettingsValue, saveUISettings, fetchUISettings } from 'Store/Actions/settingsActions'; +import { clearPendingChanges } from 'Store/Actions/baseActions'; +import connectSection from 'Store/connectSection'; +import UISettings from './UISettings'; + +function createMapStateToProps() { + return createSelector( + (state) => state.settings.advancedSettings, + createSettingsSectionSelector(), + (advancedSettings, sectionSettings) => { + return { + advancedSettings, + ...sectionSettings + }; + } + ); +} + +const mapDispatchToProps = { + setUISettingsValue, + saveUISettings, + fetchUISettings, + clearPendingChanges +}; + +class UISettingsConnector extends Component { + + // + // Lifecycle + + componentDidMount() { + this.props.fetchUISettings(); + } + + componentWillUnmount() { + this.props.clearPendingChanges({ section: this.props.section }); + } + + // + // Listeners + + onInputChange = ({ name, value }) => { + this.props.setUISettingsValue({ name, value }); + } + + onSavePress = () => { + this.props.saveUISettings(); + } + + // + // Render + + render() { + return ( + + ); + } +} + +UISettingsConnector.propTypes = { + section: PropTypes.string.isRequired, + setUISettingsValue: PropTypes.func.isRequired, + saveUISettings: PropTypes.func.isRequired, + fetchUISettings: PropTypes.func.isRequired, + clearPendingChanges: PropTypes.func.isRequired +}; + +export default connectSection( + createMapStateToProps, + mapDispatchToProps, + undefined, + undefined, + { section: 'ui' } + )(UISettingsConnector); diff --git a/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js b/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js index 3a0148c71..2d2d50371 100644 --- a/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js +++ b/frontend/src/Store/Actions/Creators/createSaveProviderHandler.js @@ -1,8 +1,19 @@ -import $ from 'jquery'; import { batchActions } from 'redux-batched-actions'; +import createAjaxRequest from 'Utilities/createAjaxRequest'; import getProviderState from 'Utilities/State/getProviderState'; import { set, updateItem } from '../baseActions'; +const abortCurrentRequests = {}; + +export function createCancelSaveProviderHandler(section) { + return function(payload) { + if (abortCurrentRequests[section]) { + abortCurrentRequests[section](); + abortCurrentRequests[section] = null; + } + }; +} + function createSaveProviderHandler(section, url, getFromState) { return function(payload) { return function(dispatch, getState) { @@ -24,9 +35,11 @@ function createSaveProviderHandler(section, url, getFromState) { ajaxOptions.method = 'PUT'; } - const promise = $.ajax(ajaxOptions); + const { request, abortRequest } = createAjaxRequest()(ajaxOptions); - promise.done((data) => { + abortCurrentRequests[section] = abortRequest; + + request.done((data) => { dispatch(batchActions([ updateItem({ section, ...data }), @@ -39,11 +52,11 @@ function createSaveProviderHandler(section, url, getFromState) { ])); }); - promise.fail((xhr) => { + request.fail((xhr) => { dispatch(set({ section, isSaving: false, - saveError: xhr + saveError: xhr.aborted ? null : xhr })); }); }; diff --git a/frontend/src/Store/Actions/Creators/createTestProviderHandler.js b/frontend/src/Store/Actions/Creators/createTestProviderHandler.js index 3dbc1a113..236b7bcd4 100644 --- a/frontend/src/Store/Actions/Creators/createTestProviderHandler.js +++ b/frontend/src/Store/Actions/Creators/createTestProviderHandler.js @@ -1,7 +1,18 @@ -import $ from 'jquery'; +import createAjaxRequest from 'Utilities/createAjaxRequest'; import getProviderState from 'Utilities/State/getProviderState'; import { set } from '../baseActions'; +const abortCurrentRequests = {}; + +export function createCancelTestProviderHandler(section) { + return function(payload) { + if (abortCurrentRequests[section]) { + abortCurrentRequests[section](); + abortCurrentRequests[section] = null; + } + }; +} + function createTestProviderHandler(section, url, getFromState) { return function(payload) { return function(dispatch, getState) { @@ -17,9 +28,11 @@ function createTestProviderHandler(section, url, getFromState) { data: JSON.stringify(testData) }; - const promise = $.ajax(ajaxOptions); + const { request, abortRequest } = createAjaxRequest()(ajaxOptions); - promise.done((data) => { + abortCurrentRequests[section] = abortRequest; + + request.done((data) => { dispatch(set({ section, isTesting: false, @@ -27,11 +40,11 @@ function createTestProviderHandler(section, url, getFromState) { })); }); - promise.fail((xhr) => { + request.fail((xhr) => { dispatch(set({ section, isTesting: false, - saveError: xhr + saveError: xhr.aborted ? null : xhr })); }); }; diff --git a/frontend/src/Store/Actions/actionTypes.js b/frontend/src/Store/Actions/actionTypes.js index 456d79db3..5a6e74a05 100644 --- a/frontend/src/Store/Actions/actionTypes.js +++ b/frontend/src/Store/Actions/actionTypes.js @@ -247,8 +247,10 @@ export const SELECT_INDEXER_SCHEMA = 'SELECT_INDEXER_SCHEMA'; export const SET_INDEXER_VALUE = 'SET_INDEXER_VALUE'; export const SET_INDEXER_FIELD_VALUE = 'SET_INDEXER_FIELD_VALUE'; export const SAVE_INDEXER = 'SAVE_INDEXER'; +export const CANCEL_SAVE_INDEXER = 'CANCEL_SAVE_INDEXER'; export const DELETE_INDEXER = 'DELETE_INDEXER'; export const TEST_INDEXER = 'TEST_INDEXER'; +export const CANCEL_TEST_INDEXER = 'CANCEL_TEST_INDEXER'; export const FETCH_INDEXER_OPTIONS = 'FETCH_INDEXER_OPTIONS'; export const SET_INDEXER_OPTIONS_VALUE = 'SET_INDEXER_OPTIONS_VALUE'; @@ -265,8 +267,10 @@ export const SELECT_DOWNLOAD_CLIENT_SCHEMA = 'SELECT_DOWNLOAD_CLIENT_SCHEMA'; export const SET_DOWNLOAD_CLIENT_VALUE = 'SET_DOWNLOAD_CLIENT_VALUE'; export const SET_DOWNLOAD_CLIENT_FIELD_VALUE = 'SET_DOWNLOAD_CLIENT_FIELD_VALUE'; export const SAVE_DOWNLOAD_CLIENT = 'SAVE_DOWNLOAD_CLIENT'; +export const CANCEL_SAVE_DOWNLOAD_CLIENT = 'CANCEL_SAVE_DOWNLOAD_CLIENT'; export const DELETE_DOWNLOAD_CLIENT = 'DELETE_DOWNLOAD_CLIENT'; export const TEST_DOWNLOAD_CLIENT = 'TEST_DOWNLOAD_CLIENT'; +export const CANCEL_TEST_DOWNLOAD_CLIENT = 'CANCEL_TEST_DOWNLOAD_CLIENT'; export const FETCH_DOWNLOAD_CLIENT_OPTIONS = 'FETCH_DOWNLOAD_CLIENT_OPTIONS'; export const SET_DOWNLOAD_CLIENT_OPTIONS_VALUE = 'SET_DOWNLOAD_CLIENT_OPTIONS_VALUE'; @@ -283,8 +287,9 @@ export const SELECT_NOTIFICATION_SCHEMA = 'SELECT_NOTIFICATION_SCHEMA'; export const SET_NOTIFICATION_VALUE = 'SET_NOTIFICATION_VALUE'; export const SET_NOTIFICATION_FIELD_VALUE = 'SET_NOTIFICATION_FIELD_VALUE'; export const SAVE_NOTIFICATION = 'SAVE_NOTIFICATION'; +export const CANCEL_SAVE_NOTIFICATION = 'CANCEL_SAVE_NOTIFICATION'; export const DELETE_NOTIFICATION = 'DELETE_NOTIFICATION'; -export const TEST_NOTIFICATION = 'TEST_NOTIFICATION'; +export const CANCEL_TEST_NOTIFICATION = 'CANCEL_TEST_NOTIFICATION'; export const FETCH_METADATA = 'FETCH_METADATA'; export const SET_METADATA_VALUE = 'SET_METADATA_VALUE'; diff --git a/frontend/src/Store/Actions/addArtistActionHandlers.js b/frontend/src/Store/Actions/addArtistActionHandlers.js index 9647013d8..421e445bc 100644 --- a/frontend/src/Store/Actions/addArtistActionHandlers.js +++ b/frontend/src/Store/Actions/addArtistActionHandlers.js @@ -1,12 +1,12 @@ import _ from 'lodash'; import $ from 'jquery'; import { batchActions } from 'redux-batched-actions'; +import createAjaxRequest from 'Utilities/createAjaxRequest'; import getNewSeries from 'Utilities/Series/getNewSeries'; import * as types from './actionTypes'; import { set, update, updateItem } from './baseActions'; -let currentXHR = null; -let xhrCancelled = false; +let abortCurrentRequest = null; const section = 'addArtist'; const addArtistActionHandlers = { @@ -14,24 +14,20 @@ const addArtistActionHandlers = { return function(dispatch, getState) { dispatch(set({ section, isFetching: true })); - if (currentXHR) { - xhrCancelled = true; - currentXHR.abort(); - currentXHR = null; + if (abortCurrentRequest) { + abortCurrentRequest(); } - currentXHR = new window.XMLHttpRequest(); - xhrCancelled = false; - - const promise = $.ajax({ + const { request, abortRequest } = createAjaxRequest()({ url: '/artist/lookup', - xhr: () => currentXHR, data: { term: payload.term } }); - promise.done((data) => { + abortCurrentRequest = abortRequest; + + request.done((data) => { dispatch(batchActions([ update({ section, data }), @@ -44,12 +40,12 @@ const addArtistActionHandlers = { ])); }); - promise.fail((xhr) => { + request.fail((xhr) => { dispatch(set({ section, isFetching: false, isPopulated: false, - error: xhrCancelled ? null : xhr + error: xhr.aborted ? null : xhr })); }); }; diff --git a/frontend/src/Store/Actions/settingsActionHandlers.js b/frontend/src/Store/Actions/settingsActionHandlers.js index 4d7d6d4c1..d88d322b9 100644 --- a/frontend/src/Store/Actions/settingsActionHandlers.js +++ b/frontend/src/Store/Actions/settingsActionHandlers.js @@ -5,9 +5,9 @@ import * as types from './actionTypes'; import createFetchHandler from './Creators/createFetchHandler'; import createFetchSchemaHandler from './Creators/createFetchSchemaHandler'; import createSaveHandler from './Creators/createSaveHandler'; -import createSaveProviderHandler from './Creators/createSaveProviderHandler'; +import createSaveProviderHandler, { createCancelSaveProviderHandler } from './Creators/createSaveProviderHandler'; import createRemoveItemHandler from './Creators/createRemoveItemHandler'; -import createTestProviderHandler from './Creators/createTestProviderHandler'; +import createTestProviderHandler, { createCancelTestProviderHandler } from './Creators/createTestProviderHandler'; import { set, update, clearPendingChanges } from './baseActions'; const settingsActionHandlers = { @@ -161,6 +161,8 @@ const settingsActionHandlers = { '/indexer', (state) => state.settings.indexers), + [types.CANCEL_SAVE_INDEXER]: createCancelSaveProviderHandler('indexers'), + [types.DELETE_INDEXER]: createRemoveItemHandler('indexers', '/indexer', (state) => state.settings.indexers), @@ -169,6 +171,8 @@ const settingsActionHandlers = { '/indexer', (state) => state.settings.indexers), + [types.CANCEL_TEST_INDEXER]: createCancelTestProviderHandler('indexers'), + [types.FETCH_INDEXER_OPTIONS]: createFetchHandler('indexerOptions', '/config/indexer'), [types.SAVE_INDEXER_OPTIONS]: createSaveHandler('indexerOptions', '/config/indexer', (state) => state.settings.indexerOptions), @@ -189,6 +193,8 @@ const settingsActionHandlers = { '/downloadclient', (state) => state.settings.downloadClients), + [types.CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler('downloadClients'), + [types.DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler('downloadClients', '/downloadclient', (state) => state.settings.downloadClients), @@ -197,6 +203,8 @@ const settingsActionHandlers = { '/downloadclient', (state) => state.settings.downloadClients), + [types.CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler('downloadClients'), + [types.FETCH_DOWNLOAD_CLIENT_OPTIONS]: createFetchHandler('downloadClientOptions', '/config/downloadclient'), [types.SAVE_DOWNLOAD_CLIENT_OPTIONS]: createSaveHandler('downloadClientOptions', '/config/downloadclient', (state) => state.settings.downloadClientOptions), @@ -217,6 +225,8 @@ const settingsActionHandlers = { '/notification', (state) => state.settings.notifications), + [types.CANCEL_SAVE_NOTIFICATION]: createCancelSaveProviderHandler('notifications'), + [types.DELETE_NOTIFICATION]: createRemoveItemHandler('notifications', '/notification', (state) => state.settings.notifications), @@ -225,6 +235,8 @@ const settingsActionHandlers = { '/notification', (state) => state.settings.notifications), + [types.CANCEL_TEST_NOTIFICATION]: createCancelTestProviderHandler('notifications'), + [types.FETCH_METADATA]: createFetchHandler('metadata', '/metadata'), [types.SAVE_METADATA]: createSaveProviderHandler('metadata', diff --git a/frontend/src/Store/Actions/settingsActions.js b/frontend/src/Store/Actions/settingsActions.js index 394d25013..a8743de99 100644 --- a/frontend/src/Store/Actions/settingsActions.js +++ b/frontend/src/Store/Actions/settingsActions.js @@ -79,8 +79,10 @@ export const fetchIndexerSchema = settingsActionHandlers[types.FETCH_INDEXER_SCH export const selectIndexerSchema = createAction(types.SELECT_INDEXER_SCHEMA); export const saveIndexer = settingsActionHandlers[types.SAVE_INDEXER]; +export const cancelSaveIndexer = settingsActionHandlers[types.CANCEL_SAVE_INDEXER]; export const deleteIndexer = settingsActionHandlers[types.DELETE_INDEXER]; export const testIndexer = settingsActionHandlers[types.TEST_INDEXER]; +export const cancelTestIndexer = settingsActionHandlers[types.CANCEL_TEST_INDEXER]; export const setIndexerValue = createAction(types.SET_INDEXER_VALUE, (payload) => { return { @@ -121,8 +123,10 @@ export const fetchDownloadClientSchema = settingsActionHandlers[types.FETCH_DOWN export const selectDownloadClientSchema = createAction(types.SELECT_DOWNLOAD_CLIENT_SCHEMA); export const saveDownloadClient = settingsActionHandlers[types.SAVE_DOWNLOAD_CLIENT]; +export const cancelSaveDownloadClient = settingsActionHandlers[types.CANCEL_SAVE_DOWNLOAD_CLIENT]; export const deleteDownloadClient = settingsActionHandlers[types.DELETE_DOWNLOAD_CLIENT]; export const testDownloadClient = settingsActionHandlers[types.TEST_DOWNLOAD_CLIENT]; +export const cancelTestDownloadClient = settingsActionHandlers[types.CANCEL_TEST_DOWNLOAD_CLIENT]; export const setDownloadClientValue = createAction(types.SET_DOWNLOAD_CLIENT_VALUE, (payload) => { return { @@ -163,8 +167,10 @@ export const fetchNotificationSchema = settingsActionHandlers[types.FETCH_NOTIFI export const selectNotificationSchema = createAction(types.SELECT_NOTIFICATION_SCHEMA); export const saveNotification = settingsActionHandlers[types.SAVE_NOTIFICATION]; +export const cancelSaveNotification = settingsActionHandlers[types.CANCEL_SAVE_NOTIFICATION]; export const deleteNotification = settingsActionHandlers[types.DELETE_NOTIFICATION]; export const testNotification = settingsActionHandlers[types.TEST_NOTIFICATION]; +export const cancelTestNotification = settingsActionHandlers[types.CANCEL_TEST_NOTIFICATION]; export const setNotificationValue = createAction(types.SET_NOTIFICATION_VALUE, (payload) => { return { diff --git a/frontend/src/Styles/Variables/colors.js b/frontend/src/Styles/Variables/colors.js index d045f2c30..402358e66 100644 --- a/frontend/src/Styles/Variables/colors.js +++ b/frontend/src/Styles/Variables/colors.js @@ -1,6 +1,7 @@ module.exports = { defaultColor: '#333', disabledColor: '#999', + dimColor: '#555', black: '#000', white: '#fff', primaryColor: '#0b8750', @@ -84,6 +85,7 @@ module.exports = { dangerHoverBorderColor: '#ec2626;', iconButtonHoverColor: '#666', + iconButtonHoverLightColor: '#ccc', // // Modal diff --git a/frontend/src/Utilities/createAjaxRequest.js b/frontend/src/Utilities/createAjaxRequest.js new file mode 100644 index 000000000..1b56ae557 --- /dev/null +++ b/frontend/src/Utilities/createAjaxRequest.js @@ -0,0 +1,33 @@ +import $ from 'jquery'; + +export default function createAjaxRequest() { + return function(ajaxOptions) { + const requestXHR = new window.XMLHttpRequest(); + let aborted = false; + let complete = false; + + function abortRequest() { + if (!complete) { + aborted = true; + requestXHR.abort(); + } + } + + const request = $.ajax({ + xhr: () => requestXHR, + ...ajaxOptions + }).then(null, (xhr, textStatus, errorThrown) => { + xhr.aborted = aborted; + + return $.Deferred().reject(xhr, textStatus, errorThrown).promise(); + }) + .always(() => { + complete = true; + }); + + return { + request, + abortRequest + }; + }; +}