1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-27 22:57:09 -04:00

New: Project Aphrodite

This commit is contained in:
Qstick
2018-11-23 02:04:42 -05:00
parent 65efa15551
commit 8430cb40ab
1080 changed files with 73015 additions and 0 deletions
@@ -0,0 +1,12 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createClearReducer(section, defaultState) {
return (state) => {
const newState = Object.assign(getSectionState(state, section), defaultState);
return updateSectionState(state, section, newState);
};
}
export default createClearReducer;
@@ -0,0 +1,14 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createSetClientSideCollectionFilterReducer(section) {
return (state, { payload }) => {
const newState = getSectionState(state, section);
newState.selectedFilterKey = payload.selectedFilterKey;
return updateSectionState(state, section, newState);
};
}
export default createSetClientSideCollectionFilterReducer;
@@ -0,0 +1,29 @@
import { sortDirections } from 'Helpers/Props';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createSetClientSideCollectionSortReducer(section) {
return (state, { payload }) => {
const newState = getSectionState(state, section);
const sortKey = payload.sortKey || newState.sortKey;
let sortDirection = payload.sortDirection;
if (!sortDirection) {
if (payload.sortKey === newState.sortKey) {
sortDirection = newState.sortDirection === sortDirections.ASCENDING ?
sortDirections.DESCENDING :
sortDirections.ASCENDING;
} else {
sortDirection = newState.sortDirection;
}
}
newState.sortKey = sortKey;
newState.sortDirection = sortDirection;
return updateSectionState(state, section, newState);
};
}
export default createSetClientSideCollectionSortReducer;
@@ -0,0 +1,23 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createSetProviderFieldValueReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const { name, value } = payload;
const newState = getSectionState(state, section);
newState.pendingChanges = Object.assign({}, newState.pendingChanges);
const fields = Object.assign({}, newState.pendingChanges.fields || {});
fields[name] = value;
newState.pendingChanges.fields = fields;
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createSetProviderFieldValueReducer;
@@ -0,0 +1,36 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createSetSettingValueReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const { name, value } = payload;
const newState = getSectionState(state, section);
newState.pendingChanges = Object.assign({}, newState.pendingChanges);
const currentValue = newState.item ? newState.item[name] : null;
const pendingState = newState.pendingChanges;
let parsedValue = null;
if (_.isNumber(currentValue) && value != null) {
parsedValue = parseInt(value);
} else {
parsedValue = value;
}
if (currentValue === parsedValue) {
delete pendingState[name];
} else {
pendingState[name] = parsedValue;
}
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createSetSettingValueReducer;
@@ -0,0 +1,21 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
const whitelistedProperties = [
'pageSize',
'columns',
'tableOptions'
];
function createSetTableOptionReducer(section) {
return (state, { payload }) => {
const newState = Object.assign(
getSectionState(state, section),
_.pick(payload, whitelistedProperties));
return updateSectionState(state, section, newState);
};
}
export default createSetTableOptionReducer;
@@ -0,0 +1,42 @@
import $ from 'jquery';
import updateEpisodes from 'Utilities/Episode/updateEpisodes';
import getSectionState from 'Utilities/State/getSectionState';
function createBatchToggleEpisodeMonitoredHandler(section, fetchHandler) {
return function(getState, payload, dispatch) {
const {
episodeIds,
monitored
} = payload;
const state = getSectionState(getState(), section, true);
dispatch(updateEpisodes(section, state.items, episodeIds, {
isSaving: true
}));
const promise = $.ajax({
url: '/episode/monitor',
method: 'PUT',
data: JSON.stringify({ episodeIds, monitored }),
dataType: 'json'
});
promise.done(() => {
dispatch(updateEpisodes(section, state.items, episodeIds, {
isSaving: false,
monitored
}));
dispatch(fetchHandler());
});
promise.fail(() => {
dispatch(updateEpisodes(section, state.items, episodeIds, {
isSaving: false
}));
});
};
}
export default createBatchToggleEpisodeMonitoredHandler;
@@ -0,0 +1,44 @@
import { batchActions } from 'redux-batched-actions';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import { set, update, updateItem } from '../baseActions';
export default function createFetchHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const {
id,
...otherPayload
} = payload;
const { request, abortRequest } = createAjaxRequest({
url: id == null ? url : `${url}/${id}`,
data: otherPayload,
traditional: true
});
request.done((data) => {
dispatch(batchActions([
id == null ? update({ section, data }) : updateItem({ section, ...data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
request.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr.aborted ? null : xhr
}));
});
return abortRequest;
};
}
@@ -0,0 +1,33 @@
import $ from 'jquery';
import { set } from '../baseActions';
function createFetchSchemaHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isSchemaFetching: true }));
const promise = $.ajax({
url
});
promise.done((data) => {
dispatch(set({
section,
isSchemaFetching: false,
isSchemaPopulated: true,
schemaError: null,
schema: data
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSchemaFetching: false,
isSchemaPopulated: true,
schemaError: xhr
}));
});
};
}
export default createFetchSchemaHandler;
@@ -0,0 +1,67 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import getSectionState from 'Utilities/State/getSectionState';
import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const sectionState = getSectionState(getState(), section, true);
const page = payload.page || sectionState.page || 1;
const data = Object.assign({ page },
_.pick(sectionState, [
'pageSize',
'sortDirection',
'sortKey'
]));
if (fetchDataAugmenter) {
fetchDataAugmenter(getState, payload, data);
}
const {
selectedFilterKey,
filters,
customFilters
} = sectionState;
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
selectedFilters.forEach((filter) => {
data[filter.key] = filter.value;
});
const promise = $.ajax({
url,
data
});
promise.done((response) => {
dispatch(batchActions([
updateServerSideCollection({ section, data: response }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
};
}
export default createFetchServerSideCollectionHandler;
@@ -0,0 +1,143 @@
import _ from 'lodash';
import { handleActions } from 'redux-actions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import {
SET,
UPDATE,
UPDATE_ITEM,
UPDATE_SERVER_SIDE_COLLECTION,
CLEAR_PENDING_CHANGES,
REMOVE_ITEM
} from 'Store/Actions/baseActions';
const blacklistedProperties = [
'section',
'id'
];
export default function createHandleActions(handlers, defaultState, section) {
return handleActions({
[SET]: function(state, { payload }) {
const payloadSection = payload.section;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const newState = Object.assign(getSectionState(state, payloadSection),
_.omit(payload, blacklistedProperties));
return updateSectionState(state, payloadSection, newState);
}
return state;
},
[UPDATE]: function(state, { payload }) {
const payloadSection = payload.section;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const newState = getSectionState(state, payloadSection);
if (_.isArray(payload.data)) {
newState.items = payload.data;
} else {
newState.item = payload.data;
}
return updateSectionState(state, payloadSection, newState);
}
return state;
},
[UPDATE_ITEM]: function(state, { payload }) {
const {
section: payloadSection,
updateOnly = false,
...otherProps
} = payload;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const newState = getSectionState(state, payloadSection);
const items = newState.items;
const index = _.findIndex(items, { id: payload.id });
newState.items = [...items];
// TODO: Move adding to it's own reducer
if (index >= 0) {
const item = items[index];
newState.items.splice(index, 1, { ...item, ...otherProps });
} else if (!updateOnly) {
newState.items.push({ ...otherProps });
}
return updateSectionState(state, payloadSection, newState);
}
return state;
},
[CLEAR_PENDING_CHANGES]: function(state, { payload }) {
const payloadSection = payload.section;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const newState = getSectionState(state, payloadSection);
newState.pendingChanges = {};
if (newState.hasOwnProperty('saveError')) {
newState.saveError = null;
}
return updateSectionState(state, payloadSection, newState);
}
return state;
},
[REMOVE_ITEM]: function(state, { payload }) {
const payloadSection = payload.section;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const newState = getSectionState(state, payloadSection);
newState.items = [...newState.items];
_.remove(newState.items, { id: payload.id });
return updateSectionState(state, payloadSection, newState);
}
return state;
},
[UPDATE_SERVER_SIDE_COLLECTION]: function(state, { payload }) {
const payloadSection = payload.section;
const [baseSection] = payloadSection.split('.');
if (section === baseSection) {
const data = payload.data;
const newState = getSectionState(state, payloadSection);
const serverState = _.omit(data, ['records']);
const calculatedState = {
totalPages: Math.max(Math.ceil(data.totalRecords / data.pageSize), 1),
items: data.records
};
return updateSectionState(state, payloadSection, Object.assign(newState, serverState, calculatedState));
}
return state;
},
...handlers
}, defaultState);
}
@@ -0,0 +1,45 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { set, removeItem } from '../baseActions';
function createRemoveItemHandler(section, url) {
return function(getState, payload, dispatch) {
const {
id,
...queryParams
} = payload;
dispatch(set({ section, isDeleting: true }));
const ajaxOptions = {
url: `${url}/${id}?${$.param(queryParams, true)}`,
method: 'DELETE'
};
const promise = $.ajax(ajaxOptions);
promise.done((data) => {
dispatch(batchActions([
set({
section,
isDeleting: false,
deleteError: null
}),
removeItem({ section, id })
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
return promise;
};
}
export default createRemoveItemHandler;
@@ -0,0 +1,43 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import getSectionState from 'Utilities/State/getSectionState';
import { set, update } from '../baseActions';
function createSaveHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isSaving: true }));
const state = getSectionState(getState(), section, true);
const saveData = Object.assign({}, state.item, state.pendingChanges, payload);
const promise = $.ajax({
url,
method: 'PUT',
dataType: 'json',
data: JSON.stringify(saveData)
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isSaving: false,
saveError: null,
pendingChanges: {}
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
};
}
export default createSaveHandler;
@@ -0,0 +1,70 @@
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(getState, payload, dispatch) {
if (abortCurrentRequests[section]) {
abortCurrentRequests[section]();
abortCurrentRequests[section] = null;
}
};
}
function createSaveProviderHandler(section, url, options = {}) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isSaving: true }));
const {
id,
queryParams = {},
...otherPayload
} = payload;
const saveData = getProviderState({ id, ...otherPayload }, getState, section);
const ajaxOptions = {
url: `${url}?${$.param(queryParams, true)}`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(saveData)
};
if (id) {
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
ajaxOptions.method = 'PUT';
}
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
dispatch(batchActions([
updateItem({ section, ...data }),
set({
section,
isSaving: false,
saveError: null,
pendingChanges: {}
})
]));
});
request.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr.aborted ? null : xhr
}));
});
};
}
export default createSaveProviderHandler;
@@ -0,0 +1,52 @@
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import pages from 'Utilities/pages';
import createFetchServerSideCollectionHandler from './createFetchServerSideCollectionHandler';
import createSetServerSideCollectionPageHandler from './createSetServerSideCollectionPageHandler';
import createSetServerSideCollectionSortHandler from './createSetServerSideCollectionSortHandler';
import createSetServerSideCollectionFilterHandler from './createSetServerSideCollectionFilterHandler';
function createServerSideCollectionHandlers(section, url, fetchThunk, handlers, fetchDataAugmenter) {
const actionHandlers = {};
const fetchHandlerType = handlers[serverSideCollectionHandlers.FETCH];
const fetchHandler = createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter);
actionHandlers[fetchHandlerType] = fetchHandler;
if (handlers.hasOwnProperty(serverSideCollectionHandlers.FIRST_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.FIRST_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.FIRST, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.PREVIOUS_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.PREVIOUS_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.PREVIOUS, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.NEXT_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.NEXT_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.NEXT, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.LAST_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.LAST_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.LAST, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.EXACT_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.EXACT_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.EXACT, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.SORT)) {
const handlerType = handlers[serverSideCollectionHandlers.SORT];
actionHandlers[handlerType] = createSetServerSideCollectionSortHandler(section, fetchThunk);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.FILTER)) {
const handlerType = handlers[serverSideCollectionHandlers.FILTER];
actionHandlers[handlerType] = createSetServerSideCollectionFilterHandler(section, fetchThunk);
}
return actionHandlers;
}
export default createServerSideCollectionHandlers;
@@ -0,0 +1,10 @@
import { set } from '../baseActions';
function createSetServerSideCollectionFilterHandler(section, fetchHandler) {
return function(getState, payload, dispatch) {
dispatch(set({ section, ...payload }));
dispatch(fetchHandler({ page: 1 }));
};
}
export default createSetServerSideCollectionFilterHandler;
@@ -0,0 +1,35 @@
import pages from 'Utilities/pages';
import getSectionState from 'Utilities/State/getSectionState';
function createSetServerSideCollectionPageHandler(section, page, fetchHandler) {
return function(getState, payload, dispatch) {
const sectionState = getSectionState(getState(), section, true);
const currentPage = sectionState.page || 1;
let nextPage = 0;
switch (page) {
case pages.FIRST:
nextPage = 1;
break;
case pages.PREVIOUS:
nextPage = currentPage - 1;
break;
case pages.NEXT:
nextPage = currentPage + 1;
break;
case pages.LAST:
nextPage = sectionState.totalPages;
break;
default:
nextPage = payload.page;
}
// If we prefer to update the page immediately we should
// set the page and not pass a page to the fetch handler.
// dispatch(set({ section, page: nextPage }));
dispatch(fetchHandler({ page: nextPage }));
};
}
export default createSetServerSideCollectionPageHandler;
@@ -0,0 +1,26 @@
import getSectionState from 'Utilities/State/getSectionState';
import { sortDirections } from 'Helpers/Props';
import { set } from '../baseActions';
function createSetServerSideCollectionSortHandler(section, fetchHandler) {
return function(getState, payload, dispatch) {
const sectionState = getSectionState(getState(), section, true);
const sortKey = payload.sortKey || sectionState.sortKey;
let sortDirection = payload.sortDirection;
if (!sortDirection) {
if (payload.sortKey === sectionState.sortKey) {
sortDirection = sectionState.sortDirection === sortDirections.ASCENDING ?
sortDirections.DESCENDING :
sortDirections.ASCENDING;
} else {
sortDirection = sectionState.sortDirection;
}
}
dispatch(set({ section, sortKey, sortDirection }));
dispatch(fetchHandler());
};
}
export default createSetServerSideCollectionSortHandler;
@@ -0,0 +1,34 @@
import createAjaxRequest from 'Utilities/createAjaxRequest';
import { set } from '../baseActions';
function createTestAllProvidersHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isTestingAll: true }));
const ajaxOptions = {
url: `${url}/testall`,
method: 'POST',
contentType: 'application/json',
dataType: 'json'
};
const { request } = createAjaxRequest(ajaxOptions);
request.done((data) => {
dispatch(set({
section,
isTestingAll: false,
saveError: null
}));
});
request.fail((xhr) => {
dispatch(set({
section,
isTestingAll: false
}));
});
};
}
export default createTestAllProvidersHandler;
@@ -0,0 +1,52 @@
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions';
const abortCurrentRequests = {};
export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) {
if (abortCurrentRequests[section]) {
abortCurrentRequests[section]();
abortCurrentRequests[section] = null;
}
};
}
function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) {
dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, section);
const ajaxOptions = {
url: `${url}/test`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(testData)
};
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
dispatch(set({
section,
isTesting: false,
saveError: null
}));
});
request.fail((xhr) => {
dispatch(set({
section,
isTesting: false,
saveError: xhr.aborted ? null : xhr
}));
});
};
}
export default createTestProviderHandler;
@@ -0,0 +1,103 @@
import _ from 'lodash';
import $ from 'jquery';
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 createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
import { update } from 'Store/Actions/baseActions';
//
// Variables
const section = 'settings.delayProfiles';
//
// Actions Types
export const FETCH_DELAY_PROFILES = 'settings/delayProfiles/fetchDelayProfiles';
export const FETCH_DELAY_PROFILE_SCHEMA = 'settings/delayProfiles/fetchDelayProfileSchema';
export const SAVE_DELAY_PROFILE = 'settings/delayProfiles/saveDelayProfile';
export const DELETE_DELAY_PROFILE = 'settings/delayProfiles/deleteDelayProfile';
export const REORDER_DELAY_PROFILE = 'settings/delayProfiles/reorderDelayProfile';
export const SET_DELAY_PROFILE_VALUE = 'settings/delayProfiles/setDelayProfileValue';
//
// Action Creators
export const fetchDelayProfiles = createThunk(FETCH_DELAY_PROFILES);
export const fetchDelayProfileSchema = createThunk(FETCH_DELAY_PROFILE_SCHEMA);
export const saveDelayProfile = createThunk(SAVE_DELAY_PROFILE);
export const deleteDelayProfile = createThunk(DELETE_DELAY_PROFILE);
export const reorderDelayProfile = createThunk(REORDER_DELAY_PROFILE);
export const setDelayProfileValue = createAction(SET_DELAY_PROFILE_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: [],
isSaving: false,
saveError: null,
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_DELAY_PROFILES]: createFetchHandler(section, '/delayprofile'),
[FETCH_DELAY_PROFILE_SCHEMA]: createFetchSchemaHandler(section, '/delayprofile/schema'),
[SAVE_DELAY_PROFILE]: createSaveProviderHandler(section, '/delayprofile'),
[DELETE_DELAY_PROFILE]: createRemoveItemHandler(section, '/delayprofile'),
[REORDER_DELAY_PROFILE]: (getState, payload, dispatch) => {
const { id, moveIndex } = payload;
const moveOrder = moveIndex + 1;
const delayProfiles = getState().settings.delayProfiles.items;
const moving = _.find(delayProfiles, { id });
// Don't move if the order hasn't changed
if (moving.order === moveOrder) {
return;
}
const after = moveIndex > 0 ? _.find(delayProfiles, { order: moveIndex }) : null;
const afterQueryParam = after ? `after=${after.id}` : '';
const promise = $.ajax({
method: 'PUT',
url: `/delayprofile/reorder/${id}?${afterQueryParam}`
});
promise.done((data) => {
dispatch(update({ section, data }));
});
}
},
//
// Reducers
reducers: {
[SET_DELAY_PROFILE_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.downloadClientOptions';
//
// Actions Types
export const FETCH_DOWNLOAD_CLIENT_OPTIONS = 'FETCH_DOWNLOAD_CLIENT_OPTIONS';
export const SET_DOWNLOAD_CLIENT_OPTIONS_VALUE = 'SET_DOWNLOAD_CLIENT_OPTIONS_VALUE';
export const SAVE_DOWNLOAD_CLIENT_OPTIONS = 'SAVE_DOWNLOAD_CLIENT_OPTIONS';
//
// Action Creators
export const fetchDownloadClientOptions = createThunk(FETCH_DOWNLOAD_CLIENT_OPTIONS);
export const saveDownloadClientOptions = createThunk(SAVE_DOWNLOAD_CLIENT_OPTIONS);
export const setDownloadClientOptionsValue = createAction(SET_DOWNLOAD_CLIENT_OPTIONS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_DOWNLOAD_CLIENT_OPTIONS]: createFetchHandler(section, '/config/downloadclient'),
[SAVE_DOWNLOAD_CLIENT_OPTIONS]: createSaveHandler(section, '/config/downloadclient')
},
//
// Reducers
reducers: {
[SET_DOWNLOAD_CLIENT_OPTIONS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,117 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.downloadClients';
//
// Actions Types
export const FETCH_DOWNLOAD_CLIENTS = 'settings/downloadClients/fetchDownloadClients';
export const FETCH_DOWNLOAD_CLIENT_SCHEMA = 'settings/downloadClients/fetchDownloadClientSchema';
export const SELECT_DOWNLOAD_CLIENT_SCHEMA = 'settings/downloadClients/selectDownloadClientSchema';
export const SET_DOWNLOAD_CLIENT_VALUE = 'settings/downloadClients/setDownloadClientValue';
export const SET_DOWNLOAD_CLIENT_FIELD_VALUE = 'settings/downloadClients/setDownloadClientFieldValue';
export const SAVE_DOWNLOAD_CLIENT = 'settings/downloadClients/saveDownloadClient';
export const CANCEL_SAVE_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelSaveDownloadClient';
export const DELETE_DOWNLOAD_CLIENT = 'settings/downloadClients/deleteDownloadClient';
export const TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/testDownloadClient';
export const CANCEL_TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelTestDownloadClient';
export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients';
//
// Action Creators
export const fetchDownloadClients = createThunk(FETCH_DOWNLOAD_CLIENTS);
export const fetchDownloadClientSchema = createThunk(FETCH_DOWNLOAD_CLIENT_SCHEMA);
export const selectDownloadClientSchema = createAction(SELECT_DOWNLOAD_CLIENT_SCHEMA);
export const saveDownloadClient = createThunk(SAVE_DOWNLOAD_CLIENT);
export const cancelSaveDownloadClient = createThunk(CANCEL_SAVE_DOWNLOAD_CLIENT);
export const deleteDownloadClient = createThunk(DELETE_DOWNLOAD_CLIENT);
export const testDownloadClient = createThunk(TEST_DOWNLOAD_CLIENT);
export const cancelTestDownloadClient = createThunk(CANCEL_TEST_DOWNLOAD_CLIENT);
export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS);
export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setDownloadClientFieldValue = createAction(SET_DOWNLOAD_CLIENT_FIELD_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],
selectedSchema: {},
isSaving: false,
saveError: null,
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
},
//
// Reducers
reducers: {
[SET_DOWNLOAD_CLIENT_VALUE]: createSetSettingValueReducer(section),
[SET_DOWNLOAD_CLIENT_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
[SELECT_DOWNLOAD_CLIENT_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
selectedSchema.enable = true;
return selectedSchema;
});
}
}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.general';
//
// Actions Types
export const FETCH_GENERAL_SETTINGS = 'settings/general/fetchGeneralSettings';
export const SET_GENERAL_SETTINGS_VALUE = 'settings/general/setGeneralSettingsValue';
export const SAVE_GENERAL_SETTINGS = 'settings/general/saveGeneralSettings';
//
// Action Creators
export const fetchGeneralSettings = createThunk(FETCH_GENERAL_SETTINGS);
export const saveGeneralSettings = createThunk(SAVE_GENERAL_SETTINGS);
export const setGeneralSettingsValue = createAction(SET_GENERAL_SETTINGS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_GENERAL_SETTINGS]: createFetchHandler(section, '/config/host'),
[SAVE_GENERAL_SETTINGS]: createSaveHandler(section, '/config/host')
},
//
// Reducers
reducers: {
[SET_GENERAL_SETTINGS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.indexerOptions';
//
// Actions Types
export const FETCH_INDEXER_OPTIONS = 'settings/indexerOptions/fetchIndexerOptions';
export const SAVE_INDEXER_OPTIONS = 'settings/indexerOptions/saveIndexerOptions';
export const SET_INDEXER_OPTIONS_VALUE = 'settings/indexerOptions/setIndexerOptionsValue';
//
// Action Creators
export const fetchIndexerOptions = createThunk(FETCH_INDEXER_OPTIONS);
export const saveIndexerOptions = createThunk(SAVE_INDEXER_OPTIONS);
export const setIndexerOptionsValue = createAction(SET_INDEXER_OPTIONS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_INDEXER_OPTIONS]: createFetchHandler(section, '/config/indexer'),
[SAVE_INDEXER_OPTIONS]: createSaveHandler(section, '/config/indexer')
},
//
// Reducers
reducers: {
[SET_INDEXER_OPTIONS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,118 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.indexers';
//
// Actions Types
export const FETCH_INDEXERS = 'settings/indexers/fetchIndexers';
export const FETCH_INDEXER_SCHEMA = 'settings/indexers/fetchIndexerSchema';
export const SELECT_INDEXER_SCHEMA = 'settings/indexers/selectIndexerSchema';
export const SET_INDEXER_VALUE = 'settings/indexers/setIndexerValue';
export const SET_INDEXER_FIELD_VALUE = 'settings/indexers/setIndexerFieldValue';
export const SAVE_INDEXER = 'settings/indexers/saveIndexer';
export const CANCEL_SAVE_INDEXER = 'settings/indexers/cancelSaveIndexer';
export const DELETE_INDEXER = 'settings/indexers/deleteIndexer';
export const TEST_INDEXER = 'settings/indexers/testIndexer';
export const CANCEL_TEST_INDEXER = 'settings/indexers/cancelTestIndexer';
export const TEST_ALL_INDEXERS = 'settings/indexers/testAllIndexers';
//
// Action Creators
export const fetchIndexers = createThunk(FETCH_INDEXERS);
export const fetchIndexerSchema = createThunk(FETCH_INDEXER_SCHEMA);
export const selectIndexerSchema = createAction(SELECT_INDEXER_SCHEMA);
export const saveIndexer = createThunk(SAVE_INDEXER);
export const cancelSaveIndexer = createThunk(CANCEL_SAVE_INDEXER);
export const deleteIndexer = createThunk(DELETE_INDEXER);
export const testIndexer = createThunk(TEST_INDEXER);
export const cancelTestIndexer = createThunk(CANCEL_TEST_INDEXER);
export const testAllIndexers = createThunk(TEST_ALL_INDEXERS);
export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setIndexerFieldValue = createAction(SET_INDEXER_FIELD_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],
selectedSchema: {},
isSaving: false,
saveError: null,
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_INDEXERS]: createFetchHandler(section, '/indexer'),
[FETCH_INDEXER_SCHEMA]: createFetchSchemaHandler(section, '/indexer/schema'),
[SAVE_INDEXER]: createSaveProviderHandler(section, '/indexer'),
[CANCEL_SAVE_INDEXER]: createCancelSaveProviderHandler(section),
[DELETE_INDEXER]: createRemoveItemHandler(section, '/indexer'),
[TEST_INDEXER]: createTestProviderHandler(section, '/indexer'),
[CANCEL_TEST_INDEXER]: createCancelTestProviderHandler(section),
[TEST_ALL_INDEXERS]: createTestAllProvidersHandler(section, '/indexer')
},
//
// Reducers
reducers: {
[SET_INDEXER_VALUE]: createSetSettingValueReducer(section),
[SET_INDEXER_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
selectedSchema.enableRss = selectedSchema.supportsRss;
selectedSchema.enableSearch = selectedSchema.supportsSearch;
return selectedSchema;
});
}
}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.mediaManagement';
//
// Actions Types
export const FETCH_MEDIA_MANAGEMENT_SETTINGS = 'settings/mediaManagement/fetchMediaManagementSettings';
export const SAVE_MEDIA_MANAGEMENT_SETTINGS = 'settings/mediaManagement/saveMediaManagementSettings';
export const SET_MEDIA_MANAGEMENT_SETTINGS_VALUE = 'settings/mediaManagement/setMediaManagementSettingsValue';
//
// Action Creators
export const fetchMediaManagementSettings = createThunk(FETCH_MEDIA_MANAGEMENT_SETTINGS);
export const saveMediaManagementSettings = createThunk(SAVE_MEDIA_MANAGEMENT_SETTINGS);
export const setMediaManagementSettingsValue = createAction(SET_MEDIA_MANAGEMENT_SETTINGS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_MEDIA_MANAGEMENT_SETTINGS]: createFetchHandler(section, '/config/mediamanagement'),
[SAVE_MEDIA_MANAGEMENT_SETTINGS]: createSaveHandler(section, '/config/mediamanagement')
},
//
// Reducers
reducers: {
[SET_MEDIA_MANAGEMENT_SETTINGS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,75 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
//
// Variables
const section = 'settings.metadata';
//
// Actions Types
export const FETCH_METADATA = 'settings/metadata/fetchMetadata';
export const SET_METADATA_VALUE = 'settings/metadata/setMetadataValue';
export const SET_METADATA_FIELD_VALUE = 'settings/metadata/setMetadataFieldValue';
export const SAVE_METADATA = 'settings/metadata/saveMetadata';
//
// Action Creators
export const fetchMetadata = createThunk(FETCH_METADATA);
export const saveMetadata = createThunk(SAVE_METADATA);
export const setMetadataValue = createAction(SET_METADATA_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setMetadataFieldValue = createAction(SET_METADATA_FIELD_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_METADATA]: createFetchHandler(section, '/metadata'),
[SAVE_METADATA]: createSaveProviderHandler(section, '/metadata')
},
//
// Reducers
reducers: {
[SET_METADATA_VALUE]: createSetSettingValueReducer(section),
[SET_METADATA_FIELD_VALUE]: createSetProviderFieldValueReducer(section)
}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.naming';
//
// Actions Types
export const FETCH_NAMING_SETTINGS = 'settings/naming/fetchNamingSettings';
export const SAVE_NAMING_SETTINGS = 'settings/naming/saveNamingSettings';
export const SET_NAMING_SETTINGS_VALUE = 'settings/naming/setNamingSettingsValue';
//
// Action Creators
export const fetchNamingSettings = createThunk(FETCH_NAMING_SETTINGS);
export const saveNamingSettings = createThunk(SAVE_NAMING_SETTINGS);
export const setNamingSettingsValue = createAction(SET_NAMING_SETTINGS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_NAMING_SETTINGS]: createFetchHandler(section, '/config/naming'),
[SAVE_NAMING_SETTINGS]: createSaveHandler(section, '/config/naming')
},
//
// Reducers
reducers: {
[SET_NAMING_SETTINGS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,79 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { createThunk } from 'Store/thunks';
import { set, update } from 'Store/Actions/baseActions';
//
// Variables
const section = 'settings.namingExamples';
//
// Actions Types
export const FETCH_NAMING_EXAMPLES = 'settings/namingExamples/fetchNamingExamples';
//
// Action Creators
export const fetchNamingExamples = createThunk(FETCH_NAMING_EXAMPLES);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_NAMING_EXAMPLES]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const naming = getState().settings.naming;
const promise = $.ajax({
url: '/config/naming/examples',
data: Object.assign({}, naming.item, naming.pendingChanges)
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
}
},
//
// Reducers
reducers: {}
};
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.netImportOptions';
//
// Actions Types
export const FETCH_NET_IMPORT_OPTIONS = 'settings/netImportOptions/fetchNetImportOptions';
export const SAVE_NET_IMPORT_OPTIONS = 'settings/netImportOptions/saveNetImportOptions';
export const SET_NET_IMPORT_OPTIONS_VALUE = 'settings/netImportOptions/setNetImportOptionsValue';
//
// Action Creators
export const fetchNetImportOptions = createThunk(FETCH_NET_IMPORT_OPTIONS);
export const saveNetImportOptions = createThunk(SAVE_NET_IMPORT_OPTIONS);
export const setNetImportOptionsValue = createAction(SET_NET_IMPORT_OPTIONS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_NET_IMPORT_OPTIONS]: createFetchHandler(section, '/config/netimport'),
[SAVE_NET_IMPORT_OPTIONS]: createSaveHandler(section, '/config/netimport')
},
//
// Reducers
reducers: {
[SET_NET_IMPORT_OPTIONS_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -0,0 +1,116 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.netImports';
//
// Actions Types
export const FETCH_NET_IMPORTS = 'settings/netImports/fetchNetImports';
export const FETCH_NET_IMPORT_SCHEMA = 'settings/netImports/fetchNetImportSchema';
export const SELECT_NET_IMPORT_SCHEMA = 'settings/netImports/selectNetImportSchema';
export const SET_NET_IMPORT_VALUE = 'settings/netImports/setNetImportValue';
export const SET_NET_IMPORT_FIELD_VALUE = 'settings/netImports/setNetImportFieldValue';
export const SAVE_NET_IMPORT = 'settings/netImports/saveNetImport';
export const CANCEL_SAVE_NET_IMPORT = 'settings/netImports/cancelSaveNetImport';
export const DELETE_NET_IMPORT = 'settings/netImports/deleteNetImport';
export const TEST_NET_IMPORT = 'settings/netImports/testNetImport';
export const CANCEL_TEST_NET_IMPORT = 'settings/netImports/cancelTestNetImport';
export const TEST_ALL_NET_IMPORT = 'settings/netImports/testAllNetImport';
//
// Action Creators
export const fetchNetImports = createThunk(FETCH_NET_IMPORTS);
export const fetchNetImportSchema = createThunk(FETCH_NET_IMPORT_SCHEMA);
export const selectNetImportSchema = createAction(SELECT_NET_IMPORT_SCHEMA);
export const saveNetImport = createThunk(SAVE_NET_IMPORT);
export const cancelSaveNetImport = createThunk(CANCEL_SAVE_NET_IMPORT);
export const deleteNetImport = createThunk(DELETE_NET_IMPORT);
export const testNetImport = createThunk(TEST_NET_IMPORT);
export const cancelTestNetImport = createThunk(CANCEL_TEST_NET_IMPORT);
export const testAllNetImport = createThunk(TEST_ALL_NET_IMPORT);
export const setNetImportValue = createAction(SET_NET_IMPORT_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setNetImportFieldValue = createAction(SET_NET_IMPORT_FIELD_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],
selectedSchema: {},
isSaving: false,
saveError: null,
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_NET_IMPORTS]: createFetchHandler(section, '/netimport'),
[FETCH_NET_IMPORT_SCHEMA]: createFetchSchemaHandler(section, '/netimport/schema'),
[SAVE_NET_IMPORT]: createSaveProviderHandler(section, '/netimport'),
[CANCEL_SAVE_NET_IMPORT]: createCancelSaveProviderHandler(section),
[DELETE_NET_IMPORT]: createRemoveItemHandler(section, '/netimport'),
[TEST_NET_IMPORT]: createTestProviderHandler(section, '/netimport'),
[CANCEL_TEST_NET_IMPORT]: createCancelTestProviderHandler(section),
[TEST_ALL_NET_IMPORT]: createTestAllProvidersHandler(section, '/netimport')
},
//
// Reducers
reducers: {
[SET_NET_IMPORT_VALUE]: createSetSettingValueReducer(section),
[SET_NET_IMPORT_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
[SELECT_NET_IMPORT_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
return selectedSchema;
});
}
}
};
@@ -0,0 +1,115 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.notifications';
//
// Actions Types
export const FETCH_NOTIFICATIONS = 'settings/notifications/fetchNotifications';
export const FETCH_NOTIFICATION_SCHEMA = 'settings/notifications/fetchNotificationSchema';
export const SELECT_NOTIFICATION_SCHEMA = 'settings/notifications/selectNotificationSchema';
export const SET_NOTIFICATION_VALUE = 'settings/notifications/setNotificationValue';
export const SET_NOTIFICATION_FIELD_VALUE = 'settings/notifications/setNotificationFieldValue';
export const SAVE_NOTIFICATION = 'settings/notifications/saveNotification';
export const CANCEL_SAVE_NOTIFICATION = 'settings/notifications/cancelSaveNotification';
export const DELETE_NOTIFICATION = 'settings/notifications/deleteNotification';
export const TEST_NOTIFICATION = 'settings/notifications/testNotification';
export const CANCEL_TEST_NOTIFICATION = 'settings/notifications/cancelTestNotification';
//
// Action Creators
export const fetchNotifications = createThunk(FETCH_NOTIFICATIONS);
export const fetchNotificationSchema = createThunk(FETCH_NOTIFICATION_SCHEMA);
export const selectNotificationSchema = createAction(SELECT_NOTIFICATION_SCHEMA);
export const saveNotification = createThunk(SAVE_NOTIFICATION);
export const cancelSaveNotification = createThunk(CANCEL_SAVE_NOTIFICATION);
export const deleteNotification = createThunk(DELETE_NOTIFICATION);
export const testNotification = createThunk(TEST_NOTIFICATION);
export const cancelTestNotification = createThunk(CANCEL_TEST_NOTIFICATION);
export const setNotificationValue = createAction(SET_NOTIFICATION_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setNotificationFieldValue = createAction(SET_NOTIFICATION_FIELD_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: [],
selectedSchema: {},
isSaving: false,
saveError: null,
isTesting: false,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_NOTIFICATIONS]: createFetchHandler(section, '/notification'),
[FETCH_NOTIFICATION_SCHEMA]: createFetchSchemaHandler(section, '/notification/schema'),
[SAVE_NOTIFICATION]: createSaveProviderHandler(section, '/notification'),
[CANCEL_SAVE_NOTIFICATION]: createCancelSaveProviderHandler(section),
[DELETE_NOTIFICATION]: createRemoveItemHandler(section, '/notification'),
[TEST_NOTIFICATION]: createTestProviderHandler(section, '/notification'),
[CANCEL_TEST_NOTIFICATION]: createCancelTestProviderHandler(section)
},
//
// Reducers
reducers: {
[SET_NOTIFICATION_VALUE]: createSetSettingValueReducer(section),
[SET_NOTIFICATION_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
return selectProviderSchema(state, section, payload, (selectedSchema) => {
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
selectedSchema.onRename = selectedSchema.supportsOnRename;
return selectedSchema;
});
}
}
};
@@ -0,0 +1,135 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk } from 'Store/thunks';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
import { clearPendingChanges, set, update } from 'Store/Actions/baseActions';
//
// Variables
const section = 'settings.qualityDefinitions';
//
// Actions Types
export const FETCH_QUALITY_DEFINITIONS = 'settings/qualityDefinitions/fetchQualityDefinitions';
export const SAVE_QUALITY_DEFINITIONS = 'settings/qualityDefinitions/saveQualityDefinitions';
export const SET_QUALITY_DEFINITION_VALUE = 'settings/qualityDefinitions/setQualityDefinitionValue';
//
// Action Creators
export const fetchQualityDefinitions = createThunk(FETCH_QUALITY_DEFINITIONS);
export const saveQualityDefinitions = createThunk(SAVE_QUALITY_DEFINITIONS);
export const setQualityDefinitionValue = createAction(SET_QUALITY_DEFINITION_VALUE);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: [],
isSaving: false,
saveError: null,
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_QUALITY_DEFINITIONS]: createFetchHandler(section, '/qualitydefinition'),
[SAVE_QUALITY_DEFINITIONS]: createSaveHandler(section, '/qualitydefinition'),
[SAVE_QUALITY_DEFINITIONS]: function(getState, payload, dispatch) {
const qualityDefinitions = getState().settings.qualityDefinitions;
const upatedDefinitions = Object.keys(qualityDefinitions.pendingChanges).map((key) => {
const id = parseInt(key);
const pendingChanges = qualityDefinitions.pendingChanges[id] || {};
const item = _.find(qualityDefinitions.items, { id });
return Object.assign({}, item, pendingChanges);
});
// If there is nothing to save don't bother isSaving
if (!upatedDefinitions || !upatedDefinitions.length) {
return;
}
dispatch(set({
section,
isSaving: true
}));
const promise = $.ajax({
method: 'PUT',
url: '/qualityDefinition/update',
data: JSON.stringify(upatedDefinitions)
});
promise.done((data) => {
dispatch(batchActions([
set({
section,
isSaving: false,
saveError: null
}),
update({ section, data }),
clearPendingChanges({ section })
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
}
},
//
// Reducers
reducers: {
[SET_QUALITY_DEFINITION_VALUE]: function(state, { payload }) {
const { id, name, value } = payload;
const newState = getSectionState(state, section);
newState.pendingChanges = _.cloneDeep(newState.pendingChanges);
const pendingState = newState.pendingChanges[id] || {};
const currentValue = _.find(newState.items, { id })[name];
if (currentValue === value) {
delete pendingState[name];
} else {
pendingState[name] = value;
}
if (_.isEmpty(pendingState)) {
delete newState.pendingChanges[id];
} else {
newState.pendingChanges[id] = pendingState;
}
return updateSectionState(state, section, newState);
}
}
};
@@ -0,0 +1,97 @@
import { createAction } from 'redux-actions';
import { createThunk } from 'Store/thunks';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
//
// Variables
const section = 'settings.qualityProfiles';
//
// Actions Types
export const FETCH_QUALITY_PROFILES = 'settings/qualityProfiles/fetchQualityProfiles';
export const FETCH_QUALITY_PROFILE_SCHEMA = 'settings/qualityProfiles/fetchQualityProfileSchema';
export const SAVE_QUALITY_PROFILE = 'settings/qualityProfiles/saveQualityProfile';
export const DELETE_QUALITY_PROFILE = 'settings/qualityProfiles/deleteQualityProfile';
export const SET_QUALITY_PROFILE_VALUE = 'settings/qualityProfiles/setQualityProfileValue';
export const CLONE_QUALITY_PROFILE = 'settings/qualityProfiles/cloneQualityProfile';
//
// Action Creators
export const fetchQualityProfiles = createThunk(FETCH_QUALITY_PROFILES);
export const fetchQualityProfileSchema = createThunk(FETCH_QUALITY_PROFILE_SCHEMA);
export const saveQualityProfile = createThunk(SAVE_QUALITY_PROFILE);
export const deleteQualityProfile = createThunk(DELETE_QUALITY_PROFILE);
export const setQualityProfileValue = createAction(SET_QUALITY_PROFILE_VALUE, (payload) => {
return {
section,
...payload
};
});
export const cloneQualityProfile = createAction(CLONE_QUALITY_PROFILE);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
isDeleting: false,
deleteError: null,
isSchemaFetching: false,
isSchemaPopulated: false,
schemaError: null,
schema: {},
isSaving: false,
saveError: null,
items: [],
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_QUALITY_PROFILES]: createFetchHandler(section, '/qualityprofile'),
[FETCH_QUALITY_PROFILE_SCHEMA]: createFetchSchemaHandler(section, '/qualityprofile/schema'),
[SAVE_QUALITY_PROFILE]: createSaveProviderHandler(section, '/qualityprofile'),
[DELETE_QUALITY_PROFILE]: createRemoveItemHandler(section, '/qualityprofile')
},
//
// Reducers
reducers: {
[SET_QUALITY_PROFILE_VALUE]: createSetSettingValueReducer(section),
[CLONE_QUALITY_PROFILE]: function(state, { payload }) {
const id = payload.id;
const newState = getSectionState(state, section);
const item = newState.items.find((i) => i.id === id);
const pendingChanges = { ...item, id: 0 };
delete pendingChanges.id;
pendingChanges.name = `${pendingChanges.name} - Copy`;
newState.pendingChanges = pendingChanges;
return updateSectionState(state, section, newState);
}
}
};
@@ -0,0 +1,69 @@
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.remotePathMappings';
//
// Actions Types
export const FETCH_REMOTE_PATH_MAPPINGS = 'settings/remotePathMappings/fetchRemotePathMappings';
export const SAVE_REMOTE_PATH_MAPPING = 'settings/remotePathMappings/saveRemotePathMapping';
export const DELETE_REMOTE_PATH_MAPPING = 'settings/remotePathMappings/deleteRemotePathMapping';
export const SET_REMOTE_PATH_MAPPING_VALUE = 'settings/remotePathMappings/setRemotePathMappingValue';
//
// Action Creators
export const fetchRemotePathMappings = createThunk(FETCH_REMOTE_PATH_MAPPINGS);
export const saveRemotePathMapping = createThunk(SAVE_REMOTE_PATH_MAPPING);
export const deleteRemotePathMapping = createThunk(DELETE_REMOTE_PATH_MAPPING);
export const setRemotePathMappingValue = createAction(SET_REMOTE_PATH_MAPPING_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: [],
isSaving: false,
saveError: null,
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_REMOTE_PATH_MAPPINGS]: createFetchHandler(section, '/remotepathmapping'),
[SAVE_REMOTE_PATH_MAPPING]: createSaveProviderHandler(section, '/remotepathmapping'),
[DELETE_REMOTE_PATH_MAPPING]: createRemoveItemHandler(section, '/remotepathmapping')
},
//
// Reducers
reducers: {
[SET_REMOTE_PATH_MAPPING_VALUE]: createSetSettingValueReducer(section)
}
};
@@ -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.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)
}
};
+64
View File
@@ -0,0 +1,64 @@
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 createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
//
// Variables
const section = 'settings.ui';
//
// Actions Types
export const FETCH_UI_SETTINGS = 'settings/ui/fetchUiSettings';
export const SET_UI_SETTINGS_VALUE = 'SET_UI_SETTINGS_VALUE';
export const SAVE_UI_SETTINGS = 'SAVE_UI_SETTINGS';
//
// Action Creators
export const fetchUISettings = createThunk(FETCH_UI_SETTINGS);
export const saveUISettings = createThunk(SAVE_UI_SETTINGS);
export const setUISettingsValue = createAction(SET_UI_SETTINGS_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
pendingChanges: {},
isSaving: false,
saveError: null,
item: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_UI_SETTINGS]: createFetchHandler(section, '/config/ui'),
[SAVE_UI_SETTINGS]: createSaveHandler(section, '/config/ui')
},
//
// Reducers
reducers: {
[SET_UI_SETTINGS_VALUE]: createSetSettingValueReducer(section)
}
};
+21
View File
@@ -0,0 +1,21 @@
//
// App
export const SHOW_MESSAGE = 'SHOW_MESSAGE';
export const HIDE_MESSAGE = 'HIDE_MESSAGE';
export const SAVE_DIMENSIONS = 'SAVE_DIMENSIONS';
export const SET_VERSION = 'SET_VERSION';
export const SET_APP_VALUE = 'SET_APP_VALUE';
export const SET_IS_SIDEBAR_VISIBLE = 'SET_IS_SIDEBAR_VISIBLE';
//
// Settings
export const FETCH_GENERAL_SETTINGS = 'settings/general/fetchGeneralSettings';
export const SET_GENERAL_SETTINGS_VALUE = 'settings/general/setGeneralSettingsValue';
export const SAVE_GENERAL_SETTINGS = 'settings/general/saveGeneralSettings';
//
// Languages
export const FETCH_LANGUAGES = 'FETCH_LANGUAGES';
@@ -0,0 +1,177 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie';
import { createThunk, handleThunks } from 'Store/thunks';
import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
import createHandleActions from './Creators/createHandleActions';
import { set, update, updateItem } from './baseActions';
//
// Variables
export const section = 'addMovie';
let abortCurrentRequest = null;
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isAdding: false,
isAdded: false,
addError: null,
items: [],
defaults: {
rootFolderPath: '',
monitor: 'true',
qualityProfileId: 0,
tags: []
}
};
export const persistState = [
'addMovie.defaults'
];
//
// Actions Types
export const LOOKUP_MOVIE = 'addMovie/lookupMovie';
export const ADD_MOVIE = 'addMovie/addMovie';
export const SET_ADD_MOVIE_VALUE = 'addMovie/setAddMovieValue';
export const CLEAR_ADD_MOVIE = 'addMovie/clearAddMovie';
export const SET_ADD_MOVIE_DEFAULT = 'addMovie/setAddMovieDefault';
//
// Action Creators
export const lookupMovie = createThunk(LOOKUP_MOVIE);
export const addMovie = createThunk(ADD_MOVIE);
export const clearAddMovie = createAction(CLEAR_ADD_MOVIE);
export const setAddMovieDefault = createAction(SET_ADD_MOVIE_DEFAULT);
export const setAddMovieValue = createAction(SET_ADD_MOVIE_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Action Handlers
export const actionHandlers = handleThunks({
[LOOKUP_MOVIE]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
if (abortCurrentRequest) {
abortCurrentRequest();
}
const { request, abortRequest } = createAjaxRequest({
url: '/movie/lookup',
data: {
term: payload.term
}
});
abortCurrentRequest = abortRequest;
request.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
request.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr.aborted ? null : xhr
}));
});
},
[ADD_MOVIE]: function(getState, payload, dispatch) {
dispatch(set({ section, isAdding: true }));
const tmdbId = payload.tmdbId;
const items = getState().addMovie.items;
const newSeries = getNewMovie(_.cloneDeep(_.find(items, { tmdbId })), payload);
const promise = $.ajax({
url: '/movie',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(newSeries)
});
promise.done((data) => {
dispatch(batchActions([
updateItem({ section: 'movies', ...data }),
set({
section,
isAdding: false,
isAdded: true,
addError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isAdding: false,
isAdded: false,
addError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_ADD_MOVIE_VALUE]: createSetSettingValueReducer(section),
[SET_ADD_MOVIE_DEFAULT]: function(state, { payload }) {
const newState = getSectionState(state, section);
newState.defaults = {
...newState.defaults,
...payload
};
return updateSectionState(state, section, newState);
},
[CLEAR_ADD_MOVIE]: function(state) {
const {
defaults,
...otherDefaultState
} = defaultState;
return Object.assign({}, state, otherDefaultState);
}
}, defaultState, section);
+135
View File
@@ -0,0 +1,135 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import createHandleActions from './Creators/createHandleActions';
function getDimensions(width, height) {
const dimensions = {
width,
height,
isExtraSmallScreen: width <= 480,
isSmallScreen: width <= 768,
isMediumScreen: width <= 992,
isLargeScreen: width <= 1200
};
return dimensions;
}
//
// Variables
export const section = 'app';
const messagesSection = 'app.messages';
//
// State
export const defaultState = {
dimensions: getDimensions(window.innerWidth, window.innerHeight),
messages: {
items: []
},
version: window.Radarr.version,
isUpdated: false,
isConnected: true,
isReconnecting: false,
isDisconnected: false,
isRestarting: false,
isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen
};
//
// Action Types
export const SHOW_MESSAGE = 'app/showMessage';
export const HIDE_MESSAGE = 'app/hideMessage';
export const SAVE_DIMENSIONS = 'app/saveDimensions';
export const SET_VERSION = 'app/setVersion';
export const SET_APP_VALUE = 'app/setAppValue';
export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible';
//
// Action Creators
export const saveDimensions = createAction(SAVE_DIMENSIONS);
export const setVersion = createAction(SET_VERSION);
export const setIsSidebarVisible = createAction(SET_IS_SIDEBAR_VISIBLE);
export const setAppValue = createAction(SET_APP_VALUE);
export const showMessage = createAction(SHOW_MESSAGE);
export const hideMessage = createAction(HIDE_MESSAGE);
//
// Reducers
export const reducers = createHandleActions({
[SAVE_DIMENSIONS]: function(state, { payload }) {
const {
width,
height
} = payload;
const dimensions = getDimensions(width, height);
return Object.assign({}, state, { dimensions });
},
[SHOW_MESSAGE]: function(state, { payload }) {
const newState = getSectionState(state, messagesSection);
const items = newState.items;
const index = _.findIndex(items, { id: payload.id });
newState.items = [...items];
if (index >= 0) {
const item = items[index];
newState.items.splice(index, 1, { ...item, ...payload });
} else {
newState.items.push({ ...payload });
}
return updateSectionState(state, messagesSection, newState);
},
[HIDE_MESSAGE]: function(state, { payload }) {
const newState = getSectionState(state, messagesSection);
newState.items = [...newState.items];
_.remove(newState.items, { id: payload.id });
return updateSectionState(state, messagesSection, newState);
},
[SET_APP_VALUE]: function(state, { payload }) {
const newState = Object.assign(getSectionState(state, section), payload);
return updateSectionState(state, section, newState);
},
[SET_VERSION]: function(state, { payload }) {
const version = payload.version;
const newState = {
version
};
if (state.version !== version) {
newState.isUpdated = true;
}
return Object.assign({}, state, newState);
},
[SET_IS_SIDEBAR_VISIBLE]: function(state, { payload }) {
const newState = {
isSidebarVisible: payload.isSidebarVisible
};
return Object.assign({}, state, newState);
}
}, defaultState, section);
+29
View File
@@ -0,0 +1,29 @@
import { createAction } from 'redux-actions';
//
// Action Types
export const SET = 'base/set';
export const UPDATE = 'base/update';
export const UPDATE_ITEM = 'base/updateItem';
export const UPDATE_SERVER_SIDE_COLLECTION = 'base/updateServerSideCollection';
export const SET_SETTING_VALUE = 'base/setSettingValue';
export const CLEAR_PENDING_CHANGES = 'base/clearPendingChanges';
export const REMOVE_ITEM = 'base/removeItem';
//
// Action Creators
export const set = createAction(SET);
export const update = createAction(UPDATE);
export const updateItem = createAction(UPDATE_ITEM);
export const updateServerSideCollection = createAction(UPDATE_SERVER_SIDE_COLLECTION);
export const setSettingValue = createAction(SET_SETTING_VALUE);
export const clearPendingChanges = createAction(CLEAR_PENDING_CHANGES);
export const removeItem = createAction(REMOVE_ITEM);
@@ -0,0 +1,139 @@
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';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
//
// Variables
export const section = 'blacklist';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
pageSize: 20,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING,
error: null,
items: [],
columns: [
{
name: 'movie.sortTitle',
label: 'Movie Title',
isSortable: true,
isVisible: true
},
{
name: 'sourceTitle',
label: 'Source Title',
isSortable: true,
isVisible: true
},
{
name: 'quality',
label: 'Quality',
isVisible: true
},
{
name: 'date',
label: 'Date',
isSortable: true,
isVisible: true
},
{
name: 'indexer',
label: 'Indexer',
isSortable: true,
isVisible: false
},
{
name: 'actions',
columnLabel: 'Actions',
isVisible: true,
isModifiable: false
}
]
};
export const persistState = [
'blacklist.pageSize',
'blacklist.sortKey',
'blacklist.sortDirection',
'blacklist.columns'
];
//
// Action Types
export const FETCH_BLACKLIST = 'blacklist/fetchBlacklist';
export const GOTO_FIRST_BLACKLIST_PAGE = 'blacklist/gotoBlacklistFirstPage';
export const GOTO_PREVIOUS_BLACKLIST_PAGE = 'blacklist/gotoBlacklistPreviousPage';
export const GOTO_NEXT_BLACKLIST_PAGE = 'blacklist/gotoBlacklistNextPage';
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';
export const CLEAR_BLACKLIST = 'blacklist/clearBlacklist';
//
// Action Creators
export const fetchBlacklist = createThunk(FETCH_BLACKLIST);
export const gotoBlacklistFirstPage = createThunk(GOTO_FIRST_BLACKLIST_PAGE);
export const gotoBlacklistPreviousPage = createThunk(GOTO_PREVIOUS_BLACKLIST_PAGE);
export const gotoBlacklistNextPage = createThunk(GOTO_NEXT_BLACKLIST_PAGE);
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);
export const clearBlacklist = createAction(CLEAR_BLACKLIST);
//
// Action Handlers
export const actionHandlers = handleThunks({
...createServerSideCollectionHandlers(
section,
'/blacklist',
fetchBlacklist,
{
[serverSideCollectionHandlers.FETCH]: FETCH_BLACKLIST,
[serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_BLACKLIST_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_BLACKLIST_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_BLACKLIST_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_BLACKLIST_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_BLACKLIST_PAGE,
[serverSideCollectionHandlers.SORT]: SET_BLACKLIST_SORT
}),
[REMOVE_FROM_BLACKLIST]: createRemoveItemHandler(section, '/blacklist')
});
//
// Reducers
export const reducers = createHandleActions({
[SET_BLACKLIST_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_BLACKLIST]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);
@@ -0,0 +1,389 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
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
export const section = 'calendar';
const viewRanges = {
[calendarViews.MONTH]: 'month',
[calendarViews.FORECAST]: 'day'
};
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
start: null,
end: null,
dates: [],
dayCount: 7,
view: window.innerWidth > 768 ? 'month' : 'day',
error: null,
items: [],
searchMissingCommandId: null,
options: {
collapseMultipleEpisodes: false,
showEpisodeInformation: true,
showFinaleIcon: false,
showSpecialIcon: false,
showCutoffUnmetIcon: false
},
selectedFilterKey: 'monitored',
filters: [
{
key: 'all',
label: 'All',
filters: [
{
key: 'monitored',
value: false,
type: filterTypes.EQUAL
}
]
},
{
key: 'monitored',
label: 'Monitored Only',
filters: [
{
key: 'monitored',
value: true,
type: filterTypes.EQUAL
}
]
}
]
};
export const persistState = [
'calendar.view',
'calendar.selectedFilterKey',
'calendar.options'
];
//
// Actions Types
export const FETCH_CALENDAR = 'calendar/fetchCalendar';
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_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
function getDays(start, end) {
const startTime = moment(start);
const endTime = moment(end);
const difference = endTime.diff(startTime, 'days');
// Difference is one less than the number of days we need to account for.
return _.times(difference + 1, (i) => {
return startTime.clone().add(i, 'days').toISOString();
});
}
function getDates(time, view, firstDayOfWeek, dayCount) {
const weekName = firstDayOfWeek === 0 ? 'week' : 'isoWeek';
let start = time.clone().startOf('day');
let end = time.clone().endOf('day');
if (view === calendarViews.WEEK) {
start = time.clone().startOf(weekName);
end = time.clone().endOf(weekName);
}
if (view === calendarViews.FORECAST) {
start = time.clone().subtract(1, 'day').startOf('day');
end = time.clone().add(dayCount - 2, 'days').endOf('day');
}
if (view === calendarViews.MONTH) {
start = time.clone().startOf('month').startOf(weekName);
end = time.clone().endOf('month').endOf(weekName);
}
if (view === calendarViews.AGENDA) {
start = time.clone().subtract(1, 'day').startOf('day');
end = time.clone().add(1, 'month').endOf('day');
}
return {
start: start.toISOString(),
end: end.toISOString(),
time: time.toISOString(),
dates: getDays(start, end)
};
}
function getPopulatableRange(startDate, endDate, view) {
switch (view) {
case calendarViews.DAY:
return {
start: moment(startDate).subtract(1, 'day').toISOString(),
end: moment(endDate).add(1, 'day').toISOString()
};
case calendarViews.WEEK:
case calendarViews.FORECAST:
return {
start: moment(startDate).subtract(1, 'week').toISOString(),
end: moment(endDate).add(1, 'week').toISOString()
};
default:
return {
start: startDate,
end: endDate
};
}
}
function isRangePopulated(start, end, state) {
const {
start: currentStart,
end: currentEnd,
view: currentView
} = state;
if (!currentStart || !currentEnd) {
return false;
}
const {
start: currentPopulatedStart,
end: currentPopulatedEnd
} = getPopulatableRange(currentStart, currentEnd, currentView);
if (
moment(start).isAfter(currentPopulatedStart) &&
moment(start).isBefore(currentPopulatedEnd)
) {
return true;
}
return false;
}
//
// Action Creators
export const fetchCalendar = createThunk(FETCH_CALENDAR);
export const setCalendarDaysCount = createThunk(SET_CALENDAR_DAYS_COUNT);
export const setCalendarFilter = createThunk(SET_CALENDAR_FILTER);
export const setCalendarView = createThunk(SET_CALENDAR_VIEW);
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
export const actionHandlers = handleThunks({
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
const state = getState();
const calendar = state.calendar;
const unmonitored = calendar.selectedFilterKey === 'all';
const {
time = calendar.time,
view = calendar.view
} = payload;
const dayCount = state.calendar.dayCount;
const dates = getDates(moment(time), view, state.settings.ui.item.firstDayOfWeek, dayCount);
const { start, end } = getPopulatableRange(dates.start, dates.end, view);
const isPrePopulated = isRangePopulated(start, end, state.calendar);
const basesAttrs = {
section,
isFetching: true
};
const attrs = isPrePopulated ?
{
view,
...basesAttrs,
...dates
} :
basesAttrs;
dispatch(set(attrs));
const promise = $.ajax({
url: '/calendar',
data: {
unmonitored,
start,
end
}
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
view,
...dates,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
},
[SET_CALENDAR_DAYS_COUNT]: function(getState, payload, dispatch) {
if (payload.dayCount === getState().calendar.dayCount) {
return;
}
dispatch(set({
section,
dayCount: payload.dayCount
}));
const state = getState();
const { time, view } = state.calendar;
dispatch(fetchCalendar({ time, view }));
},
[SET_CALENDAR_FILTER]: function(getState, payload, dispatch) {
dispatch(set({
section,
selectedFilterKey: payload.selectedFilterKey
}));
const state = getState();
const { time, view } = state.calendar;
dispatch(fetchCalendar({ time, view }));
},
[SET_CALENDAR_VIEW]: function(getState, payload, dispatch) {
const state = getState();
const view = payload.view;
const time = view === calendarViews.FORECAST || calendarViews.AGENDA ?
moment() :
state.calendar.time;
dispatch(fetchCalendar({ time, view }));
},
[GOTO_CALENDAR_TODAY]: function(getState, payload, dispatch) {
const state = getState();
const view = state.calendar.view;
const time = moment();
dispatch(fetchCalendar({ time, view }));
},
[GOTO_CALENDAR_PREVIOUS_RANGE]: function(getState, payload, dispatch) {
const state = getState();
const {
view,
dayCount
} = state.calendar;
const amount = view === calendarViews.FORECAST ? dayCount : 1;
const time = moment(state.calendar.time).subtract(amount, viewRanges[view]);
dispatch(fetchCalendar({ time, view }));
},
[GOTO_CALENDAR_NEXT_RANGE]: function(getState, payload, dispatch) {
const state = getState();
const {
view,
dayCount
} = state.calendar;
const amount = view === calendarViews.FORECAST ? dayCount : 1;
const time = moment(state.calendar.time).add(amount, viewRanges[view]);
dispatch(fetchCalendar({ time, view }));
},
[SEARCH_MISSING]: function(getState, payload, dispatch) {
const { episodeIds } = payload;
const commandPayload = {
name: commandNames.MOVIE_SEARCH,
episodeIds
};
executeCommandHelper(commandPayload, dispatch).then((data) => {
dispatch(set({
section,
searchMissingCommandId: data.id
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_CALENDAR]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: []
}),
[SET_CALENDAR_OPTION]: function(state, { payload }) {
const options = state.options;
return {
...state,
options: {
...options,
...payload
}
};
}
}, defaultState, section);
@@ -0,0 +1,119 @@
import { createAction } from 'redux-actions';
import requestAction from 'Utilities/requestAction';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'captcha';
//
// State
export const defaultState = {
refreshing: false,
token: null,
siteKey: null,
secretToken: null,
ray: null,
stoken: null,
responseUrl: null
};
//
// Actions Types
export const REFRESH_CAPTCHA = 'captcha/refreshCaptcha';
export const GET_CAPTCHA_COOKIE = 'captcha/getCaptchaCookie';
export const SET_CAPTCHA_VALUE = 'captcha/setCaptchaValue';
export const RESET_CAPTCHA = 'captcha/resetCaptcha';
//
// Action Creators
export const refreshCaptcha = createThunk(REFRESH_CAPTCHA);
export const getCaptchaCookie = createThunk(GET_CAPTCHA_COOKIE);
export const setCaptchaValue = createAction(SET_CAPTCHA_VALUE);
export const resetCaptcha = createAction(RESET_CAPTCHA);
//
// Action Handlers
export const actionHandlers = handleThunks({
[REFRESH_CAPTCHA]: function(getState, payload, dispatch) {
const actionPayload = {
action: 'checkCaptcha',
...payload
};
dispatch(setCaptchaValue({
refreshing: true
}));
const promise = requestAction(actionPayload);
promise.done((data) => {
if (!data.captchaRequest) {
dispatch(setCaptchaValue({
refreshing: false
}));
}
dispatch(setCaptchaValue({
refreshing: false,
...data.captchaRequest
}));
});
promise.fail(() => {
dispatch(setCaptchaValue({
refreshing: false
}));
});
},
[GET_CAPTCHA_COOKIE]: function(getState, payload, dispatch) {
const state = getState().captcha;
const queryParams = {
responseUrl: state.responseUrl,
ray: state.ray,
captchaResponse: payload.captchaResponse
};
const actionPayload = {
action: 'getCaptchaCookie',
queryParams,
...payload
};
const promise = requestAction(actionPayload);
promise.done((data) => {
dispatch(setCaptchaValue({
token: data.captchaToken
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_CAPTCHA_VALUE]: function(state, { payload }) {
const newState = Object.assign(getSectionState(state, section), payload);
return updateSectionState(state, section, newState);
},
[RESET_CAPTCHA]: function(state) {
return updateSectionState(state, section, defaultState);
}
}, defaultState);
@@ -0,0 +1,215 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { isSameCommand } from 'Utilities/Command';
import { messageTypes } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import { showMessage, hideMessage } from './appActions';
import { updateItem } from './baseActions';
//
// Variables
export const section = 'commands';
let lastCommand = null;
let lastCommandTimeout = null;
const removeCommandTimeoutIds = {};
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
handlers: {}
};
//
// Actions Types
export const FETCH_COMMANDS = 'commands/fetchCommands';
export const EXECUTE_COMMAND = 'commands/executeCommand';
export const CANCEL_COMMAND = 'commands/cancelCommand';
export const ADD_COMMAND = 'commands/updateCommand';
export const UPDATE_COMMAND = 'commands/finishCommand';
export const FINISH_COMMAND = 'commands/addCommand';
export const REMOVE_COMMAND = 'commands/removeCommand';
//
// Action Creators
export const fetchCommands = createThunk(FETCH_COMMANDS);
export const executeCommand = createThunk(EXECUTE_COMMAND);
export const cancelCommand = createThunk(CANCEL_COMMAND);
export const updateCommand = createThunk(UPDATE_COMMAND);
export const finishCommand = createThunk(FINISH_COMMAND);
export const addCommand = createAction(ADD_COMMAND);
export const removeCommand = createAction(REMOVE_COMMAND);
//
// Helpers
function showCommandMessage(payload, dispatch) {
const {
id,
name,
trigger,
message,
body = {},
status
} = payload;
const {
sendUpdatesToClient,
suppressMessages
} = body;
if (!message || !body || !sendUpdatesToClient || suppressMessages) {
return;
}
let type = messageTypes.INFO;
let hideAfter = 0;
if (status === 'completed') {
type = messageTypes.SUCCESS;
hideAfter = 4;
} else if (status === 'failed') {
type = messageTypes.ERROR;
hideAfter = trigger === 'manual' ? 10 : 4;
}
dispatch(showMessage({
id,
name,
message,
type,
hideAfter
}));
}
function scheduleRemoveCommand(command, dispatch) {
const {
id,
status
} = command;
if (status === 'queued') {
return;
}
const timeoutId = removeCommandTimeoutIds[id];
if (timeoutId) {
clearTimeout(timeoutId);
}
removeCommandTimeoutIds[id] = setTimeout(() => {
dispatch(batchActions([
removeCommand({ section: 'commands', id }),
hideMessage({ id })
]));
delete removeCommandTimeoutIds[id];
}, 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
export const actionHandlers = handleThunks({
[FETCH_COMMANDS]: createFetchHandler('commands', '/command'),
[EXECUTE_COMMAND]: function(getState, payload, dispatch) {
executeCommandHelper(payload, dispatch);
},
[CANCEL_COMMAND]: createRemoveItemHandler(section, '/command'),
[UPDATE_COMMAND]: function(getState, payload, dispatch) {
dispatch(updateItem({ section: 'commands', ...payload }));
showCommandMessage(payload, dispatch);
scheduleRemoveCommand(payload, dispatch);
},
[FINISH_COMMAND]: function(getState, payload, dispatch) {
const state = getState();
const handlers = state.commands.handlers;
Object.keys(handlers).forEach((key) => {
const handler = handlers[key];
if (handler.name === payload.name) {
dispatch(handler.handler(payload));
}
});
dispatch(updateItem({ section: 'commands', ...payload }));
scheduleRemoveCommand(payload, dispatch);
showCommandMessage(payload, dispatch);
}
});
//
// Reducers
export const reducers = createHandleActions({
[ADD_COMMAND]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.items = [...state.items, payload];
return newState;
},
[REMOVE_COMMAND]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.items = [...state.items];
const index = _.findIndex(newState.items, { id: payload.id });
if (index > -1) {
newState.items.splice(index, 1);
}
return newState;
}
}, defaultState, section);
@@ -0,0 +1,55 @@
import { createThunk, handleThunks } from 'Store/thunks';
import createFetchHandler from './Creators/createFetchHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'customFilters';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
isDeleting: false,
deleteError: null,
items: [],
pendingChanges: {}
};
//
// Actions Types
export const FETCH_CUSTOM_FILTERS = 'customFilters/fetchCustomFilters';
export const SAVE_CUSTOM_FILTER = 'customFilters/saveCustomFilter';
export const DELETE_CUSTOM_FILTER = 'customFilters/deleteCustomFilter';
//
// Action Creators
export const fetchCustomFilters = createThunk(FETCH_CUSTOM_FILTERS);
export const saveCustomFilter = createThunk(SAVE_CUSTOM_FILTER);
export const deleteCustomFilter = createThunk(DELETE_CUSTOM_FILTER);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_CUSTOM_FILTERS]: createFetchHandler(section, '/customFilter'),
[SAVE_CUSTOM_FILTER]: createSaveProviderHandler(section, '/customFilter'),
[DELETE_CUSTOM_FILTER]: createRemoveItemHandler(section, '/customFilter')
});
//
// Reducers
export const reducers = createHandleActions({}, defaultState, section);
@@ -0,0 +1,83 @@
import { createAction } from 'redux-actions';
import requestAction from 'Utilities/requestAction';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import { set } from './baseActions';
//
// Variables
export const section = 'devices';
//
// State
export const defaultState = {
items: [],
isFetching: false,
isPopulated: false,
error: false
};
//
// Actions Types
export const FETCH_DEVICES = 'devices/fetchDevices';
export const CLEAR_DEVICES = 'devices/clearDevices';
//
// Action Creators
export const fetchDevices = createThunk(FETCH_DEVICES);
export const clearDevices = createAction(CLEAR_DEVICES);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_DEVICES]: function(getState, payload, dispatch) {
const actionPayload = {
action: 'getDevices',
...payload
};
dispatch(set({
section,
isFetching: true
}));
const promise = requestAction(actionPayload);
promise.done((data) => {
dispatch(set({
section,
isFetching: false,
isPopulated: true,
error: null,
items: data.devices || []
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_DEVICES]: function(state) {
return updateSectionState(state, section, defaultState);
}
}, defaultState, section);
@@ -0,0 +1,257 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import { filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
import createHandleActions from './Creators/createHandleActions';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import { updateItem } from './baseActions';
//
// Variables
export const section = 'history';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
pageSize: 20,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING,
items: [],
columns: [
{
name: 'eventType',
columnLabel: 'Event Type',
isVisible: true,
isModifiable: false
},
{
name: 'movie.sortTitle',
label: 'Movie',
isSortable: true,
isVisible: true
},
{
name: 'language',
label: 'Language',
isVisible: false
},
{
name: 'quality',
label: 'Quality',
isVisible: true
},
{
name: 'date',
label: 'Date',
isSortable: true,
isVisible: true
},
{
name: 'downloadClient',
label: 'Download Client',
isVisible: false
},
{
name: 'indexer',
label: 'Indexer',
isVisible: false
},
{
name: 'releaseGroup',
label: 'Release Group',
isVisible: false
},
{
name: 'details',
columnLabel: 'Details',
isVisible: true,
isModifiable: false
}
],
selectedFilterKey: 'all',
filters: [
{
key: 'all',
label: 'All',
filters: []
},
{
key: 'grabbed',
label: 'Grabbed',
filters: [
{
key: 'eventType',
value: '1',
type: filterTypes.EQUAL
}
]
},
{
key: 'imported',
label: 'Imported',
filters: [
{
key: 'eventType',
value: '3',
type: filterTypes.EQUAL
}
]
},
{
key: 'failed',
label: 'Failed',
filters: [
{
key: 'eventType',
value: '4',
type: filterTypes.EQUAL
}
]
},
{
key: 'deleted',
label: 'Deleted',
filters: [
{
key: 'eventType',
value: '5',
type: filterTypes.EQUAL
}
]
},
{
key: 'renamed',
label: 'Renamed',
filters: [
{
key: 'eventType',
value: '6',
type: filterTypes.EQUAL
}
]
}
]
};
export const persistState = [
'history.pageSize',
'history.sortKey',
'history.sortDirection',
'history.selectedFilterKey'
];
//
// Actions Types
export const FETCH_HISTORY = 'history/fetchHistory';
export const GOTO_FIRST_HISTORY_PAGE = 'history/gotoHistoryFirstPage';
export const GOTO_PREVIOUS_HISTORY_PAGE = 'history/gotoHistoryPreviousPage';
export const GOTO_NEXT_HISTORY_PAGE = 'history/gotoHistoryNextPage';
export const GOTO_LAST_HISTORY_PAGE = 'history/gotoHistoryLastPage';
export const GOTO_HISTORY_PAGE = 'history/gotoHistoryPage';
export const SET_HISTORY_SORT = 'history/setHistorySort';
export const SET_HISTORY_FILTER = 'history/setHistoryFilter';
export const SET_HISTORY_TABLE_OPTION = 'history/setHistoryTableOption';
export const CLEAR_HISTORY = 'history/clearHistory';
export const MARK_AS_FAILED = 'history/markAsFailed';
//
// Action Creators
export const fetchHistory = createThunk(FETCH_HISTORY);
export const gotoHistoryFirstPage = createThunk(GOTO_FIRST_HISTORY_PAGE);
export const gotoHistoryPreviousPage = createThunk(GOTO_PREVIOUS_HISTORY_PAGE);
export const gotoHistoryNextPage = createThunk(GOTO_NEXT_HISTORY_PAGE);
export const gotoHistoryLastPage = createThunk(GOTO_LAST_HISTORY_PAGE);
export const gotoHistoryPage = createThunk(GOTO_HISTORY_PAGE);
export const setHistorySort = createThunk(SET_HISTORY_SORT);
export const setHistoryFilter = createThunk(SET_HISTORY_FILTER);
export const setHistoryTableOption = createAction(SET_HISTORY_TABLE_OPTION);
export const clearHistory = createAction(CLEAR_HISTORY);
export const markAsFailed = createThunk(MARK_AS_FAILED);
//
// Action Handlers
export const actionHandlers = handleThunks({
...createServerSideCollectionHandlers(
section,
'/history',
fetchHistory,
{
[serverSideCollectionHandlers.FETCH]: FETCH_HISTORY,
[serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_HISTORY_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_HISTORY_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_HISTORY_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_HISTORY_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_HISTORY_PAGE,
[serverSideCollectionHandlers.SORT]: SET_HISTORY_SORT,
[serverSideCollectionHandlers.FILTER]: SET_HISTORY_FILTER
}),
[MARK_AS_FAILED]: function(getState, payload, dispatch) {
const id = payload.id;
dispatch(updateItem({
section,
id,
isMarkingAsFailed: true
}));
const promise = $.ajax({
url: '/history/failed',
method: 'POST',
data: {
id
}
});
promise.done(() => {
dispatch(updateItem({
section,
id,
isMarkingAsFailed: false,
markAsFailedError: null
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
section,
id,
isMarkingAsFailed: false,
markAsFailedError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_HISTORY_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_HISTORY]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);
@@ -0,0 +1,287 @@
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 getNewMovie from 'Utilities/Movie/getNewMovie';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import { set, removeItem, updateItem } from './baseActions';
import { fetchRootFolders } from './rootFolderActions';
//
// Variables
export const section = 'importMovie';
let concurrentLookups = 0;
let abortCurrentLookup = null;
const queue = [];
//
// State
export const defaultState = {
isLookingUpMovie: false,
isImporting: false,
isImported: false,
importError: null,
items: []
};
//
// Actions Types
export const QUEUE_LOOKUP_MOVIE = 'importMovie/queueLookupMovie';
export const START_LOOKUP_MOVIE = 'importMovie/startLookupMovie';
export const CANCEL_LOOKUP_MOVIE = 'importMovie/cancelLookupMovie';
export const CLEAR_IMPORT_MOVIE = 'importMovie/clearImportMovie';
export const SET_IMPORT_MOVIE_VALUE = 'importMovie/setImportMovieValue';
export const IMPORT_MOVIE = 'importMovie/importMovie';
//
// Action Creators
export const queueLookupMovie = createThunk(QUEUE_LOOKUP_MOVIE);
export const startLookupMovie = createThunk(START_LOOKUP_MOVIE);
export const importMovie = createThunk(IMPORT_MOVIE);
export const clearImportMovie = createAction(CLEAR_IMPORT_MOVIE);
export const cancelLookupMovie = createAction(CANCEL_LOOKUP_MOVIE);
export const setImportMovieValue = createAction(SET_IMPORT_MOVIE_VALUE, (payload) => {
return {
section,
...payload
};
});
//
// Action Handlers
export const actionHandlers = handleThunks({
[QUEUE_LOOKUP_MOVIE]: function(getState, payload, dispatch) {
const {
name,
path,
term,
topOfQueue = false
} = payload;
const state = getState().importMovie;
const item = _.find(state.items, { id: name }) || {
id: name,
term,
path,
isFetching: false,
isPopulated: false,
error: null
};
dispatch(updateItem({
section,
...item,
term,
queued: true,
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(startLookupMovie({ start: true }));
}
},
[START_LOOKUP_MOVIE]: function(getState, payload, dispatch) {
if (concurrentLookups >= 1) {
return;
}
const state = getState().importMovie;
const {
isLookingUpMovie,
items
} = state;
const queueId = queue[0];
if (payload.start && !isLookingUpMovie) {
dispatch(set({ section, isLookingUpMovie: true }));
} else if (!isLookingUpMovie) {
return;
} else if (!queueId) {
dispatch(set({ section, isLookingUpMovie: false }));
return;
}
concurrentLookups++;
queue.splice(0, 1);
const queued = items.find((i) => i.id === queueId);
dispatch(updateItem({
section,
id: queued.id,
isFetching: true
}));
const { request, abortRequest } = createAjaxRequest({
url: '/movie/lookup',
data: {
term: queued.term
}
});
abortCurrentLookup = abortRequest;
request.done((data) => {
dispatch(updateItem({
section,
id: queued.id,
isFetching: false,
isPopulated: true,
error: null,
items: data,
queued: false,
selectedMovie: queued.selectedMovie || data[0],
updateOnly: true
}));
});
request.fail((xhr) => {
dispatch(updateItem({
section,
id: queued.id,
isFetching: false,
isPopulated: false,
error: xhr,
queued: false,
updateOnly: true
}));
});
request.always(() => {
concurrentLookups--;
dispatch(startLookupMovie());
});
},
[IMPORT_MOVIE]: function(getState, payload, dispatch) {
dispatch(set({ section, isImporting: true }));
const ids = payload.ids;
const items = getState().importMovie.items;
const addedIds = [];
const allNewMovies = ids.reduce((acc, id) => {
const item = _.find(items, { id });
const selectedMovie = item.selectedMovie;
// Make sure we have a selected series and
// the same series hasn't been added yet.
if (selectedMovie && !_.some(acc, { tmdbId: selectedMovie.tmdbId })) {
const newSeries = getNewMovie(_.cloneDeep(selectedMovie), item);
newSeries.path = item.path;
addedIds.push(id);
acc.push(newSeries);
}
return acc;
}, []);
const promise = $.ajax({
url: '/movie/import',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(allNewMovies)
});
promise.done((data) => {
dispatch(batchActions([
set({
section,
isImporting: false,
isImported: true
}),
...data.map((series) => updateItem({ section: 'movies', ...series })),
...addedIds.map((id) => removeItem({ section, id }))
]));
dispatch(fetchRootFolders());
});
promise.fail((xhr) => {
dispatch(batchActions(
set({
section,
isImporting: false,
isImported: true
}),
addedIds.map((id) => updateItem({
section,
id,
importError: xhr
}))
));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CANCEL_LOOKUP_MOVIE]: function(state) {
return Object.assign({}, state, { isLookingUpMovie: false });
},
[CLEAR_IMPORT_MOVIE]: function(state) {
if (abortCurrentLookup) {
abortCurrentLookup();
abortCurrentLookup = null;
}
queue.splice(0, queue.length);
return Object.assign({}, state, defaultState);
},
[SET_IMPORT_MOVIE_VALUE]: function(state, { payload }) {
const newState = getSectionState(state, section);
const items = newState.items;
const index = _.findIndex(items, { id: payload.id });
newState.items = [...items];
if (index >= 0) {
const item = items[index];
newState.items.splice(index, 1, { ...item, ...payload });
} else {
newState.items.push({ ...payload });
}
return updateSectionState(state, section, newState);
}
}, defaultState, section);
+53
View File
@@ -0,0 +1,53 @@
import * as addMovie from './addMovieActions';
import * as app from './appActions';
import * as blacklist from './blacklistActions';
import * as calendar from './calendarActions';
import * as captcha from './captchaActions';
import * as customFilters from './customFilterActions';
import * as devices from './deviceActions';
import * as commands from './commandActions';
import * as movieFiles from './movieFileActions';
import * as history from './historyActions';
import * as importMovie from './importMovieActions';
import * as interactiveImportActions from './interactiveImportActions';
import * as oAuth from './oAuthActions';
import * as organizePreview from './organizePreviewActions';
import * as paths from './pathActions';
import * as queue from './queueActions';
import * as releases from './releaseActions';
import * as rootFolders from './rootFolderActions';
import * as movies from './movieActions';
import * as movieEditor from './movieEditorActions';
import * as movieHistory from './movieHistoryActions';
import * as movieIndex from './movieIndexActions';
import * as settings from './settingsActions';
import * as system from './systemActions';
import * as tags from './tagActions';
export default [
addMovie,
app,
blacklist,
calendar,
captcha,
commands,
customFilters,
devices,
movieFiles,
history,
importMovie,
interactiveImportActions,
oAuth,
organizePreview,
paths,
queue,
releases,
rootFolders,
movies,
movieEditor,
movieHistory,
movieIndex,
settings,
system,
tags
];
@@ -0,0 +1,204 @@
import $ from 'jquery';
import moment from 'moment';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk, handleThunks } from 'Store/thunks';
import { sortDirections } from 'Helpers/Props';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import { set, update } from './baseActions';
//
// Variables
export const section = 'interactiveImport';
const episodesSection = `${section}.episodes`;
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
sortKey: 'quality',
sortDirection: sortDirections.DESCENDING,
recentFolders: [],
importMode: 'move',
sortPredicates: {
relativePath: function(item, direction) {
const relativePath = item.relativePath;
return relativePath.toLowerCase();
},
series: function(item, direction) {
const series = item.series;
return series ? series.sortTitle : '';
},
quality: function(item, direction) {
return item.quality ? item.quality.qualityWeight : 0;
}
},
episodes: {
isFetching: false,
isPopulated: false,
error: null,
sortKey: 'episodeNumber',
sortDirection: sortDirections.ASCENDING,
items: []
}
};
export const persistState = [
'interactiveImport.recentFolders',
'interactiveImport.importMode'
];
//
// Actions Types
export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'interactiveImport/fetchInteractiveImportItems';
export const SET_INTERACTIVE_IMPORT_SORT = 'interactiveImport/setInteractiveImportSort';
export const UPDATE_INTERACTIVE_IMPORT_ITEM = 'interactiveImport/updateInteractiveImportItem';
export const CLEAR_INTERACTIVE_IMPORT = 'interactiveImport/clearInteractiveImport';
export const ADD_RECENT_FOLDER = 'interactiveImport/addRecentFolder';
export const REMOVE_RECENT_FOLDER = 'interactiveImport/removeRecentFolder';
export const SET_INTERACTIVE_IMPORT_MODE = 'interactiveImport/setInteractiveImportMode';
export const FETCH_INTERACTIVE_IMPORT_EPISODES = 'interactiveImport/fetchInteractiveImportEpisodes';
export const SET_INTERACTIVE_IMPORT_EPISODES_SORT = 'interactiveImport/setInteractiveImportEpisodesSort';
export const CLEAR_INTERACTIVE_IMPORT_EPISODES = 'interactiveImport/clearInteractiveImportEpisodes';
//
// Action Creators
export const fetchInteractiveImportItems = createThunk(FETCH_INTERACTIVE_IMPORT_ITEMS);
export const setInteractiveImportSort = createAction(SET_INTERACTIVE_IMPORT_SORT);
export const updateInteractiveImportItem = createAction(UPDATE_INTERACTIVE_IMPORT_ITEM);
export const clearInteractiveImport = createAction(CLEAR_INTERACTIVE_IMPORT);
export const addRecentFolder = createAction(ADD_RECENT_FOLDER);
export const removeRecentFolder = createAction(REMOVE_RECENT_FOLDER);
export const setInteractiveImportMode = createAction(SET_INTERACTIVE_IMPORT_MODE);
export const fetchInteractiveImportEpisodes = createThunk(FETCH_INTERACTIVE_IMPORT_EPISODES);
export const setInteractiveImportEpisodesSort = createAction(SET_INTERACTIVE_IMPORT_EPISODES_SORT);
export const clearInteractiveImportEpisodes = createAction(CLEAR_INTERACTIVE_IMPORT_EPISODES);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) {
if (!payload.downloadId && !payload.folder) {
dispatch(set({ section, error: { message: '`downloadId` or `folder` is required.' } }));
return;
}
dispatch(set({ section, isFetching: true }));
const promise = $.ajax({
url: '/manualimport',
data: payload
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
},
[FETCH_INTERACTIVE_IMPORT_EPISODES]: createFetchHandler('interactiveImport.episodes', '/episode')
});
//
// Reducers
export const reducers = createHandleActions({
[UPDATE_INTERACTIVE_IMPORT_ITEM]: (state, { payload }) => {
const id = payload.id;
const newState = Object.assign({}, state);
const items = newState.items;
const index = items.findIndex((item) => item.id === id);
const item = Object.assign({}, items[index], payload);
newState.items = [...items];
newState.items.splice(index, 1, item);
return newState;
},
[ADD_RECENT_FOLDER]: function(state, { payload }) {
const folder = payload.folder;
const recentFolder = { folder, lastUsed: moment().toISOString() };
const recentFolders = [...state.recentFolders];
const index = recentFolders.findIndex((r) => r.folder === folder);
if (index > -1) {
recentFolders.splice(index, 1, recentFolder);
} else {
recentFolders.push(recentFolder);
}
return Object.assign({}, state, { recentFolders });
},
[REMOVE_RECENT_FOLDER]: function(state, { payload }) {
const folder = payload.folder;
const recentFolders = [...state.recentFolders];
const index = recentFolders.findIndex((r) => r.folder === folder);
recentFolders.splice(index, 1);
return Object.assign({}, state, { recentFolders });
},
[CLEAR_INTERACTIVE_IMPORT]: function(state) {
const newState = {
...defaultState,
recentFolders: state.recentFolders,
importMode: state.importMode
};
return newState;
},
[SET_INTERACTIVE_IMPORT_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_INTERACTIVE_IMPORT_MODE]: function(state, { payload }) {
return Object.assign({}, state, { importMode: payload.importMode });
},
[SET_INTERACTIVE_IMPORT_EPISODES_SORT]: createSetClientSideCollectionSortReducer(episodesSection),
[CLEAR_INTERACTIVE_IMPORT_EPISODES]: (state) => {
return updateSectionState(state, episodesSection, {
...defaultState.episodes
});
}
}, defaultState, section);
+255
View File
@@ -0,0 +1,255 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
// import { batchActions } from 'redux-batched-actions';
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';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createHandleActions from './Creators/createHandleActions';
import { updateItem } from './baseActions';
//
// Variables
export const section = 'movies';
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: 'wanted',
label: 'Wanted Missing',
filters: [
{
key: 'monitored',
value: true,
type: filterTypes.EQUAL
},
{
key: 'hasFile',
value: false,
type: filterTypes.EQUAL
}
]
},
{
key: 'cutoffunmet',
label: 'Cut-off Unmet',
filters: [
{
key: 'monitored',
value: true,
type: filterTypes.EQUAL
},
{
key: 'hasFile',
value: true,
type: filterTypes.EQUAL
},
{
key: 'movieFile.qualityCutoffNotMet',
value: true,
type: filterTypes.EQUAL
}
]
}
];
export const filterPredicates = {
missing: function(item) {
const { statistics = {} } = item;
return statistics.episodeCount - statistics.episodeFileCount > 0;
},
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);
}
};
export const sortPredicates = {
status: function(item) {
let result = 0;
if (item.monitored) {
result += 2;
}
if (item.status === 'continuing') {
result++;
}
return result;
}
};
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
items: [],
sortKey: 'sortTitle',
sortDirection: sortDirections.ASCENDING,
pendingChanges: {}
};
//
// Actions Types
export const FETCH_MOVIES = 'movies/fetchMovies';
export const SET_MOVIE_VALUE = 'movies/setMovieValue';
export const SAVE_MOVIE = 'movies/saveMovie';
export const DELETE_MOVIE = 'movies/deleteMovie';
export const TOGGLE_MOVIE_MONITORED = 'movies/toggleMovieMonitored';
//
// Action Creators
export const fetchMovies = createThunk(FETCH_MOVIES);
export const saveMovie = createThunk(SAVE_MOVIE, (payload) => {
const newPayload = {
...payload
};
if (payload.moveFiles) {
newPayload.queryParams = {
moveFiles: true
};
}
delete newPayload.moveFiles;
return newPayload;
});
export const deleteMovie = createThunk(DELETE_MOVIE, (payload) => {
return {
...payload,
queryParams: {
deleteFiles: payload.deleteFiles
}
};
});
export const toggleMovieMonitored = createThunk(TOGGLE_MOVIE_MONITORED);
export const setMovieValue = createAction(SET_MOVIE_VALUE, (payload) => {
return {
section: 'movies',
...payload
};
});
//
// Helpers
function getSaveAjaxOptions({ ajaxOptions, payload }) {
if (payload.moveFolder) {
ajaxOptions.url = `${ajaxOptions.url}?moveFolder=true`;
}
return ajaxOptions;
}
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_MOVIES]: createFetchHandler(section, '/movie'),
[SAVE_MOVIE]: createSaveProviderHandler(section, '/movie', { getAjaxOptions: getSaveAjaxOptions }),
[DELETE_MOVIE]: createRemoveItemHandler(section, '/movie'),
[TOGGLE_MOVIE_MONITORED]: (getState, payload, dispatch) => {
const {
movieId: id,
monitored
} = payload;
const movie = _.find(getState().movies.items, { id });
dispatch(updateItem({
id,
section,
isSaving: true
}));
const promise = $.ajax({
url: `/movie/${id}`,
method: 'PUT',
data: JSON.stringify({
...movie,
monitored
}),
dataType: 'json'
});
promise.done((data) => {
dispatch(updateItem({
id,
section,
isSaving: false,
monitored
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
id,
section,
isSaving: false
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_MOVIE_VALUE]: createSetSettingValueReducer(section)
}, defaultState, section);
@@ -0,0 +1,179 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
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 createHandleActions from './Creators/createHandleActions';
import { set, updateItem } from './baseActions';
import { filters, filterPredicates, sortPredicates } from './movieActions';
//
// Variables
export const section = 'movieEditor';
//
// State
export const defaultState = {
isSaving: false,
saveError: null,
isDeleting: false,
deleteError: null,
sortKey: 'sortTitle',
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortTitle',
secondarySortDirection: sortDirections.ASCENDING,
selectedFilterKey: 'all',
filters,
filterPredicates,
filterBuilderProps: [
{
name: 'monitored',
label: 'Monitored',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.BOOL
},
{
name: 'status',
label: 'Status',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.SERIES_STATUS
},
{
name: 'qualityProfileId',
label: 'Quality Profile',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.QUALITY_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
}
],
sortPredicates
};
export const persistState = [
'movieEditor.sortKey',
'movieEditor.sortDirection',
'movieEditor.selectedFilterKey',
'movieEditor.customFilters'
];
//
// Actions Types
export const SET_SERIES_EDITOR_SORT = 'movieEditor/setSeriesEditorSort';
export const SET_SERIES_EDITOR_FILTER = 'movieEditor/setSeriesEditorFilter';
export const SAVE_SERIES_EDITOR = 'movieEditor/saveSeriesEditor';
export const BULK_DELETE_SERIES = 'movieEditor/bulkDeleteSeries';
//
// Action Creators
export const setSeriesEditorSort = createAction(SET_SERIES_EDITOR_SORT);
export const setSeriesEditorFilter = createAction(SET_SERIES_EDITOR_FILTER);
export const saveSeriesEditor = createThunk(SAVE_SERIES_EDITOR);
export const bulkDeleteSeries = createThunk(BULK_DELETE_SERIES);
//
// Action Handlers
export const actionHandlers = handleThunks({
[SAVE_SERIES_EDITOR]: function(getState, payload, dispatch) {
dispatch(set({
section,
isSaving: true
}));
const promise = $.ajax({
url: '/series/editor',
method: 'PUT',
data: JSON.stringify(payload),
dataType: 'json'
});
promise.done((data) => {
dispatch(batchActions([
...data.map((series) => {
return updateItem({
id: series.id,
section: 'series',
...series
});
}),
set({
section,
isSaving: false,
saveError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
},
[BULK_DELETE_SERIES]: function(getState, payload, dispatch) {
dispatch(set({
section,
isDeleting: true
}));
const promise = $.ajax({
url: '/series/editor',
method: 'DELETE',
data: JSON.stringify(payload),
dataType: 'json'
});
promise.done(() => {
// SignaR will take care of removing the series from the collection
dispatch(set({
section,
isDeleting: false,
deleteError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_SERIES_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_SERIES_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
}, defaultState, section);
@@ -0,0 +1,210 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import movieEntities from 'Movie/movieEntities';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import { set, removeItem, updateItem } from './baseActions';
//
// Variables
export const section = 'movieFiles';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isDeleting: false,
deleteError: null,
isSaving: false,
saveError: null,
items: []
};
//
// Actions Types
export const FETCH_MOVIE_FILES = 'movieFiles/fetchMovieFiles';
export const DELETE_MOVIE_FILE = 'movieFiles/deleteMovieFile';
export const DELETE_MOVIE_FILES = 'movieFiles/deleteMovieFiles';
export const UPDATE_MOVIE_FILES = 'movieFiles/updateMovieFiles';
export const CLEAR_MOVIE_FILES = 'movieFiles/clearMovieFiles';
//
// Action Creators
export const fetchMovieFiles = createThunk(FETCH_MOVIE_FILES);
export const deleteMovieFile = createThunk(DELETE_MOVIE_FILE);
export const deleteMovieFiles = createThunk(DELETE_MOVIE_FILES);
export const updateMovieFiles = createThunk(UPDATE_MOVIE_FILES);
export const clearMovieFiles = createAction(CLEAR_MOVIE_FILES);
//
// Helpers
const deleteMovieFileHelper = createRemoveItemHandler(section, '/movieFile');
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_MOVIE_FILES]: createFetchHandler(section, '/movieFile'),
[DELETE_MOVIE_FILE]: function(getState, payload, dispatch) {
const {
id: movieFileId,
movieEntity = movieEntities.MOVIES
} = payload;
const movieSection = _.last(movieEntity.split('.'));
const deletePromise = deleteMovieFileHelper(getState, payload, dispatch);
deletePromise.done(() => {
const movies = getState().movies.items;
const moviesWithRemovedFiles = _.filter(movies, { movieFileId });
dispatch(batchActions([
...moviesWithRemovedFiles.map((movie) => {
return updateItem({
section: movieSection,
...movie,
movieFileId: 0,
hasFile: false
});
})
]));
});
},
[DELETE_MOVIE_FILES]: function(getState, payload, dispatch) {
const {
movieFileIds
} = payload;
dispatch(set({ section, isDeleting: true }));
const promise = $.ajax({
url: '/movieFile/bulk',
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ movieFileIds })
});
promise.done(() => {
const movies = getState().movies.items;
const moviesWithRemovedFiles = movieFileIds.reduce((acc, movieFileId) => {
acc.push(..._.filter(movies, { movieFileId }));
return acc;
}, []);
dispatch(batchActions([
...movieFileIds.map((id) => {
return removeItem({ section, id });
}),
...moviesWithRemovedFiles.map((movie) => {
return updateItem({
section: 'movies',
...movie,
movieFileId: 0,
hasFile: false
});
}),
set({
section,
isDeleting: false,
deleteError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
},
[UPDATE_MOVIE_FILES]: function(getState, payload, dispatch) {
const {
movieFileIds,
language,
quality
} = payload;
dispatch(set({ section, isSaving: true }));
const data = {
movieFileIds
};
if (language) {
data.language = language;
}
if (quality) {
data.quality = quality;
}
const promise = $.ajax({
url: '/movieFile/editor',
method: 'PUT',
dataType: 'json',
data: JSON.stringify(data)
});
promise.done(() => {
dispatch(batchActions([
...movieFileIds.map((id) => {
const props = {};
if (language) {
props.language = language;
}
if (quality) {
props.quality = quality;
}
return updateItem({ section, id, ...props });
}),
set({
section,
isSaving: false,
saveError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_MOVIE_FILES]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState, section);
@@ -0,0 +1,104 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import { set, update } from './baseActions';
//
// Variables
export const section = 'movieHistory';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: []
};
//
// Actions Types
export const FETCH_SERIES_HISTORY = 'seriesHistory/fetchMovieHistory';
export const CLEAR_SERIES_HISTORY = 'seriesHistory/clearMovieHistory';
export const SERIES_HISTORY_MARK_AS_FAILED = 'seriesHistory/seriesHistoryMarkAsFailed';
//
// Action Creators
export const fetchMovieHistory = createThunk(FETCH_SERIES_HISTORY);
export const clearMovieHistory = createAction(CLEAR_SERIES_HISTORY);
export const seriesHistoryMarkAsFailed = createThunk(SERIES_HISTORY_MARK_AS_FAILED);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_SERIES_HISTORY]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const promise = $.ajax({
url: '/history/series',
data: payload
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
},
[SERIES_HISTORY_MARK_AS_FAILED]: function(getState, payload, dispatch) {
const {
historyId,
seriesId,
seasonNumber
} = payload;
const promise = $.ajax({
url: '/history/failed',
method: 'POST',
data: {
id: historyId
}
});
promise.done(() => {
dispatch(fetchMovieHistory({ seriesId, seasonNumber }));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_SERIES_HISTORY]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState, section);
@@ -0,0 +1,320 @@
import { createAction } from 'redux-actions';
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 createHandleActions from './Creators/createHandleActions';
import { filters, filterPredicates, sortPredicates } from './movieActions';
//
// Variables
export const section = 'movieIndex';
//
// State
export const defaultState = {
sortKey: 'sortTitle',
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortTitle',
secondarySortDirection: sortDirections.ASCENDING,
view: 'posters',
posterOptions: {
detailedProgressBar: false,
size: 'large',
showTitle: false,
showMonitored: true,
showQualityProfile: true,
showSearchAction: false
},
overviewOptions: {
detailedProgressBar: false,
size: 'medium',
showMonitored: true,
showStudio: true,
showQualityProfile: true,
showAdded: false,
showPath: false,
showSizeOnDisk: false,
showSearchAction: false
},
tableOptions: {
showSearchAction: false
},
columns: [
{
name: 'status',
columnLabel: 'Status',
isSortable: true,
isVisible: true,
isModifiable: false
},
{
name: 'sortTitle',
label: 'Movie Title',
isSortable: true,
isVisible: true,
isModifiable: false
},
{
name: 'studio',
label: 'Studio',
isSortable: true,
isVisible: true
},
{
name: 'qualityProfileId',
label: 'Quality Profile',
isSortable: true,
isVisible: true
},
{
name: 'added',
label: 'Added',
isSortable: true,
isVisible: false
},
{
name: 'inCinemas',
label: 'In Cinemas',
isSortable: true,
isVisible: false
},
{
name: 'physicalRelease',
label: 'Physical Release',
isSortable: true,
isVisible: false
},
{
name: 'path',
label: 'Path',
isSortable: true,
isVisible: false
},
{
name: 'genres',
label: 'Genres',
isSortable: false,
isVisible: false
},
{
name: 'ratings',
label: 'Rating',
isSortable: true,
isVisible: false
},
{
name: 'certification',
label: 'Certification',
isSortable: false,
isVisible: false
},
{
name: 'tags',
label: 'Tags',
isSortable: false,
isVisible: false
},
{
name: 'actions',
columnLabel: 'Actions',
isVisible: true,
isModifiable: false
}
],
sortPredicates: {
...sortPredicates,
studio: function(item) {
const studio = item.studio;
return studio ? studio.toLowerCase() : '';
},
sizeOnDisk: function(item) {
const { statistics = {} } = item;
return statistics.sizeOnDisk;
},
ratings: function(item) {
const { ratings = {} } = item;
return ratings.value;
}
},
selectedFilterKey: 'all',
filters,
filterPredicates,
filterBuilderProps: [
{
name: 'monitored',
label: 'Monitored',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.BOOL
},
{
name: 'status',
label: 'Status',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.SERIES_STATUS
},
{
name: 'studio',
label: 'Studio',
type: filterBuilderTypes.STRING
},
{
name: 'qualityProfileId',
label: 'Quality Profile',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.QUALITY_PROFILE
},
{
name: 'added',
label: 'Added',
type: filterBuilderTypes.DATE,
valueType: filterBuilderValueTypes.DATE
},
{
name: 'inCinemas',
label: 'In Cinemas',
type: filterBuilderTypes.DATE,
valueType: filterBuilderValueTypes.DATE
},
{
name: 'physicalRelease',
label: 'Physical Release',
type: filterBuilderTypes.DATE,
valueType: filterBuilderValueTypes.DATE
},
{
name: 'path',
label: 'Path',
type: filterBuilderTypes.STRING
},
{
name: 'sizeOnDisk',
label: 'Size on Disk',
type: filterBuilderTypes.NUMBER,
valueType: filterBuilderValueTypes.BYTES
},
{
name: 'genres',
label: 'Genres',
type: filterBuilderTypes.ARRAY,
optionsSelector: function(items) {
const tagList = items.reduce((acc, series) => {
series.genres.forEach((genre) => {
acc.push({
id: genre,
name: genre
});
});
return acc;
}, []);
return tagList.sort(sortByName);
}
},
{
name: 'ratings',
label: 'Rating',
type: filterBuilderTypes.NUMBER
},
{
name: 'certification',
label: 'Certification',
type: filterBuilderTypes.EXACT
},
{
name: 'tags',
label: 'Tags',
type: filterBuilderTypes.ARRAY,
valueType: filterBuilderValueTypes.TAG
}
]
};
export const persistState = [
'movieIndex.sortKey',
'movieIndex.sortDirection',
'movieIndex.selectedFilterKey',
'movieIndex.customFilters',
'movieIndex.view',
'movieIndex.columns',
'movieIndex.posterOptions',
'movieIndex.overviewOptions',
'movieIndex.tableOptions'
];
//
// Actions Types
export const SET_MOVIE_SORT = 'movieIndex/setMovieSort';
export const SET_MOVIE_FILTER = 'movieIndex/setMovieFilter';
export const SET_MOVIE_VIEW = 'movieIndex/setMovieView';
export const SET_MOVIE_TABLE_OPTION = 'movieIndex/setMovieTableOption';
export const SET_MOVIE_POSTER_OPTION = 'movieIndex/setMoviePosterOption';
export const SET_MOVIE_OVERVIEW_OPTION = 'movieIndex/setMovieOverviewOption';
//
// Action Creators
export const setMovieSort = createAction(SET_MOVIE_SORT);
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
export const setMovieView = createAction(SET_MOVIE_VIEW);
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
export const setMoviePosterOption = createAction(SET_MOVIE_POSTER_OPTION);
export const setMovieOverviewOption = createAction(SET_MOVIE_OVERVIEW_OPTION);
//
// Reducers
export const reducers = createHandleActions({
[SET_MOVIE_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_MOVIE_FILTER]: createSetClientSideCollectionFilterReducer(section),
[SET_MOVIE_VIEW]: function(state, { payload }) {
return Object.assign({}, state, { view: payload.view });
},
[SET_MOVIE_TABLE_OPTION]: createSetTableOptionReducer(section),
[SET_MOVIE_POSTER_OPTION]: function(state, { payload }) {
const posterOptions = state.posterOptions;
return {
...state,
posterOptions: {
...posterOptions,
...payload
}
};
},
[SET_MOVIE_OVERVIEW_OPTION]: function(state, { payload }) {
const overviewOptions = state.overviewOptions;
return {
...state,
overviewOptions: {
...overviewOptions,
...payload
}
};
}
}, defaultState, section);
+205
View File
@@ -0,0 +1,205 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import requestAction from 'Utilities/requestAction';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import { createThunk, handleThunks } from 'Store/thunks';
import { set } from 'Store/Actions/baseActions';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'oAuth';
const callbackUrl = `${window.location.origin}${window.Radarr.urlBase}/oauth.html`;
//
// State
export const defaultState = {
authorizing: false,
result: null,
error: null
};
//
// Actions Types
export const START_OAUTH = 'oAuth/startOAuth';
export const SET_OAUTH_VALUE = 'oAuth/setOAuthValue';
export const RESET_OAUTH = 'oAuth/resetOAuth';
//
// Action Creators
export const startOAuth = createThunk(START_OAUTH);
export const setOAuthValue = createAction(SET_OAUTH_VALUE);
export const resetOAuth = createAction(RESET_OAUTH);
//
// Helpers
function showOAuthWindow(url, payload) {
const deferred = $.Deferred();
const selfWindow = window;
const newWindow = window.open(url);
if (
!newWindow ||
newWindow.closed ||
typeof newWindow.closed == 'undefined'
) {
// A fake validation error to mimic a 400 response from the API.
const error = {
status: 400,
responseJSON: [
{
propertyName: payload.name,
errorMessage: 'Pop-ups are being blocked by your browser'
}
]
};
return deferred.reject(error).promise();
}
selfWindow.onCompleteOauth = function(query, onComplete) {
delete selfWindow.onCompleteOauth;
const queryParams = {};
const splitQuery = query.substring(1).split('&');
splitQuery.forEach((param) => {
if (param) {
const paramSplit = param.split('=');
queryParams[paramSplit[0]] = paramSplit[1];
}
});
onComplete();
deferred.resolve(queryParams);
};
return deferred.promise();
}
function executeIntermediateRequest(payload, ajaxOptions) {
return $.ajax(ajaxOptions).then((data) => {
return requestAction({
action: 'continueOAuth',
queryParams: {
...data,
callbackUrl
},
...payload
});
});
}
//
// Action Handlers
export const actionHandlers = handleThunks({
[START_OAUTH]: function(getState, payload, dispatch) {
const {
name,
section: actionSection,
...otherPayload
} = payload;
const actionPayload = {
action: 'startOAuth',
queryParams: { callbackUrl },
...otherPayload
};
dispatch(setOAuthValue({
authorizing: true
}));
let startResponse = {};
const promise = requestAction(actionPayload)
.then((response) => {
startResponse = response;
if (response.oauthUrl) {
return showOAuthWindow(response.oauthUrl, payload);
}
return executeIntermediateRequest(otherPayload, response).then((intermediateResponse) => {
startResponse = intermediateResponse;
return showOAuthWindow(intermediateResponse.oauthUrl, payload);
});
})
.then((queryParams) => {
return requestAction({
action: 'getOAuthToken',
queryParams: {
...startResponse,
...queryParams
},
...otherPayload
});
})
.then((response) => {
dispatch(setOAuthValue({
authorizing: false,
result: response,
error: null
}));
});
promise.done(() => {
// Clear any previously set save error.
dispatch(set({
section: actionSection,
saveError: null
}));
});
promise.fail((xhr) => {
const actions = [
setOAuthValue({
authorizing: false,
result: null,
error: xhr
})
];
if (xhr.status === 400) {
// Set a save error so the UI can display validation errors to the user.
actions.splice(0, 0, set({
section: actionSection,
saveError: xhr
}));
}
dispatch(batchActions(actions));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_OAUTH_VALUE]: function(state, { payload }) {
const newState = Object.assign(getSectionState(state, section), payload);
return updateSectionState(state, section, newState);
},
[RESET_OAUTH]: function(state) {
return updateSectionState(state, section, defaultState);
}
}, defaultState, section);
@@ -0,0 +1,51 @@
import { createAction } from 'redux-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'organizePreview';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: []
};
//
// Actions Types
export const FETCH_ORGANIZE_PREVIEW = 'organizePreview/fetchOrganizePreview';
export const CLEAR_ORGANIZE_PREVIEW = 'organizePreview/clearOrganizePreview';
//
// Action Creators
export const fetchOrganizePreview = createThunk(FETCH_ORGANIZE_PREVIEW);
export const clearOrganizePreview = createAction(CLEAR_ORGANIZE_PREVIEW);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_ORGANIZE_PREVIEW]: createFetchHandler('organizePreview', '/rename')
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_ORGANIZE_PREVIEW]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState, section);
+110
View File
@@ -0,0 +1,110 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import { set } from './baseActions';
//
// Variables
export const section = 'paths';
//
// State
export const defaultState = {
currentPath: '',
isPopulated: false,
isFetching: false,
error: null,
directories: [],
files: [],
parent: null
};
//
// Actions Types
export const FETCH_PATHS = 'paths/fetchPaths';
export const UPDATE_PATHS = 'paths/updatePaths';
export const CLEAR_PATHS = 'paths/clearPaths';
//
// Action Creators
export const fetchPaths = createThunk(FETCH_PATHS);
export const updatePaths = createAction(UPDATE_PATHS);
export const clearPaths = createAction(CLEAR_PATHS);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_PATHS]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const {
path,
allowFoldersWithoutTrailingSlashes = false
} = payload;
const promise = $.ajax({
url: '/filesystem',
data: {
path,
allowFoldersWithoutTrailingSlashes
}
});
promise.done((data) => {
dispatch(updatePaths({ path, ...data }));
dispatch(set({
section,
isFetching: false,
isPopulated: true,
error: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[UPDATE_PATHS]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.currentPath = payload.path;
newState.directories = payload.directories;
newState.files = payload.files;
newState.parent = payload.parent;
return newState;
},
[CLEAR_PATHS]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.path = '';
newState.directories = [];
newState.files = [];
newState.parent = '';
return newState;
}
}, defaultState, section);
+439
View File
@@ -0,0 +1,439 @@
import _ from 'lodash';
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import { set, updateItem } from './baseActions';
//
// Variables
export const section = 'queue';
const status = `${section}.status`;
const details = `${section}.details`;
const paged = `${section}.paged`;
//
// State
export const defaultState = {
options: {
includeUnknownMovieItems: false
},
status: {
isFetching: false,
isPopulated: false,
error: null,
item: {}
},
details: {
isFetching: false,
isPopulated: false,
error: null,
items: [],
params: {}
},
paged: {
isFetching: false,
isPopulated: false,
pageSize: 20,
sortKey: 'timeleft',
sortDirection: sortDirections.ASCENDING,
error: null,
items: [],
isGrabbing: false,
isRemoving: false,
columns: [
{
name: 'status',
columnLabel: 'Status',
isSortable: true,
isVisible: true,
isModifiable: false
},
{
name: 'series.sortTitle',
label: 'Series',
isSortable: true,
isVisible: true
},
{
name: 'episode',
label: 'Episode',
isSortable: true,
isVisible: true
},
{
name: 'episode.title',
label: 'Episode Title',
isVisible: true
},
{
name: 'episode.airDateUtc',
label: 'Episode Air Date',
isSortable: true,
isVisible: false
},
{
name: 'quality',
label: 'Quality',
isSortable: true,
isVisible: true
},
{
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
},
{
name: 'estimatedCompletionTime',
label: 'Timeleft',
isSortable: true,
isVisible: true
},
{
name: 'progress',
label: 'Progress',
isSortable: true,
isVisible: true
},
{
name: 'actions',
columnLabel: 'Actions',
isVisible: true,
isModifiable: false
}
]
}
};
export const persistState = [
'queue.options',
'queue.paged.pageSize',
'queue.paged.sortKey',
'queue.paged.sortDirection',
'queue.paged.columns'
];
//
// Helpers
function fetchDataAugmenter(getState, payload, data) {
data.includeUnknownMovieItems = getState().queue.options.includeUnknownMovieItems;
}
//
// Actions Types
export const FETCH_QUEUE_STATUS = 'queue/fetchQueueStatus';
export const FETCH_QUEUE_DETAILS = 'queue/fetchQueueDetails';
export const CLEAR_QUEUE_DETAILS = 'queue/clearQueueDetails';
export const FETCH_QUEUE = 'queue/fetchQueue';
export const GOTO_FIRST_QUEUE_PAGE = 'queue/gotoQueueFirstPage';
export const GOTO_PREVIOUS_QUEUE_PAGE = 'queue/gotoQueuePreviousPage';
export const GOTO_NEXT_QUEUE_PAGE = 'queue/gotoQueueNextPage';
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';
export const GRAB_QUEUE_ITEMS = 'queue/grabQueueItems';
export const REMOVE_QUEUE_ITEM = 'queue/removeQueueItem';
export const REMOVE_QUEUE_ITEMS = 'queue/removeQueueItems';
//
// Action Creators
export const fetchQueueStatus = createThunk(FETCH_QUEUE_STATUS);
export const fetchQueueDetails = createThunk(FETCH_QUEUE_DETAILS);
export const clearQueueDetails = createAction(CLEAR_QUEUE_DETAILS);
export const fetchQueue = createThunk(FETCH_QUEUE);
export const gotoQueueFirstPage = createThunk(GOTO_FIRST_QUEUE_PAGE);
export const gotoQueuePreviousPage = createThunk(GOTO_PREVIOUS_QUEUE_PAGE);
export const gotoQueueNextPage = createThunk(GOTO_NEXT_QUEUE_PAGE);
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);
export const grabQueueItems = createThunk(GRAB_QUEUE_ITEMS);
export const removeQueueItem = createThunk(REMOVE_QUEUE_ITEM);
export const removeQueueItems = createThunk(REMOVE_QUEUE_ITEMS);
//
// Helpers
const fetchQueueDetailsHelper = createFetchHandler(details, '/queue/details');
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_QUEUE_STATUS]: createFetchHandler(status, '/queue/status'),
[FETCH_QUEUE_DETAILS]: function(getState, payload, dispatch) {
let params = payload;
// If the payload params are empty try to get params from state.
if (params && !_.isEmpty(params)) {
dispatch(set({ section: details, params }));
} else {
params = getState().queue.details.params;
}
// Ensure there are params before trying to fetch the queue
// so we don't make a bad request to the server.
if (params && !_.isEmpty(params)) {
fetchQueueDetailsHelper(getState, params, dispatch);
}
},
...createServerSideCollectionHandlers(
paged,
'/queue',
fetchQueue,
{
[serverSideCollectionHandlers.FETCH]: FETCH_QUEUE,
[serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_QUEUE_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_QUEUE_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_QUEUE_PAGE,
[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;
dispatch(updateItem({ section: paged, id, isGrabbing: true }));
const promise = $.ajax({
url: `/queue/grab/${id}`,
method: 'POST'
});
promise.done((data) => {
dispatch(batchActions([
fetchQueue(),
set({
section: paged,
isGrabbing: false,
grabError: null
})
]));
});
promise.fail((xhr) => {
dispatch(updateItem({
section: paged,
id,
isGrabbing: false,
grabError: xhr
}));
});
},
[GRAB_QUEUE_ITEMS]: function(getState, payload, dispatch) {
const ids = payload.ids;
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section: paged,
id,
isGrabbing: true
});
}),
set({
section: paged,
isGrabbing: true
})
]));
const promise = $.ajax({
url: '/queue/grab/bulk',
method: 'POST',
dataType: 'json',
data: JSON.stringify(payload)
});
promise.done((data) => {
dispatch(batchActions([
fetchQueue(),
...ids.map((id) => {
return updateItem({
section: paged,
id,
isGrabbing: false,
grabError: null
});
}),
set({
section: paged,
isGrabbing: false,
grabError: null
})
]));
});
promise.fail((xhr) => {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section: paged,
id,
isGrabbing: false,
grabError: null
});
}),
set({ section: paged, isGrabbing: false })
]));
});
},
[REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) {
const {
id,
blacklist
} = payload;
dispatch(updateItem({ section: paged, id, isRemoving: true }));
const promise = $.ajax({
url: `/queue/${id}?blacklist=${blacklist}`,
method: 'DELETE'
});
promise.done((data) => {
dispatch(fetchQueue());
});
promise.fail((xhr) => {
dispatch(updateItem({ section: paged, id, isRemoving: false }));
});
},
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
const {
ids,
blacklist
} = payload;
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section: paged,
id,
isRemoving: true
});
}),
set({ section: paged, isRemoving: true })
]));
const promise = $.ajax({
url: `/queue/bulk?blacklist=${blacklist}`,
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ ids })
});
promise.done((data) => {
dispatch(batchActions([
set({ section: paged, isRemoving: false }),
fetchQueue()
]));
});
promise.fail((xhr) => {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section: paged,
id,
isRemoving: false
});
}),
set({ section: paged, isRemoving: false })
]));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_QUEUE_DETAILS]: createClearReducer(details, defaultState.details),
[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: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);
+20
View File
@@ -0,0 +1,20 @@
import { combineReducers } from 'redux';
import { enableBatching } from 'redux-batched-actions';
import { routerReducer } from 'react-router-redux';
import actions from 'Store/Actions';
const defaultState = {};
const reducers = {
routing: routerReducer
};
actions.forEach((action) => {
const section = action.section;
defaultState[section] = action.defaultState;
reducers[section] = action.reducers;
});
export { defaultState };
export default enableBatching(combineReducers(reducers));
@@ -0,0 +1,280 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'releases';
export const episodeSection = 'releases.episode';
export const seasonSection = 'releases.season';
let abortCurrentRequest = null;
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
sortKey: 'releaseWeight',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
peers: function(item, direction) {
const seeders = item.seeders || 0;
const leechers = item.leechers || 0;
return seeders * 1000000 + leechers;
},
rejections: function(item, direction) {
const rejections = item.rejections;
const releaseWeight = item.releaseWeight;
if (rejections.length !== 0) {
return releaseWeight + 1000000;
}
return releaseWeight;
}
},
filters: [
{
key: 'all',
label: 'All',
filters: []
},
{
key: 'season-pack',
label: 'Season Pack',
filters: [
{
key: 'fullSeason',
value: true,
type: filterTypes.EQUAL
}
]
},
{
key: 'not-season-pack',
label: 'Not Season Pack',
filters: [
{
key: 'fullSeason',
value: false,
type: filterTypes.EQUAL
}
]
}
],
filterPredicates: {
quality: function(item, value, type) {
const qualityId = item.quality.quality.id;
if (type === filterTypes.EQUAL) {
return qualityId === value;
}
if (type === filterTypes.NOT_EQUAL) {
return qualityId !== value;
}
// Default to false
return false;
}
},
filterBuilderProps: [
{
name: 'title',
label: 'Title',
type: filterBuilderTypes.STRING
},
{
name: 'age',
label: 'Age',
type: filterBuilderTypes.NUMBER
},
{
name: 'protocol',
label: 'Protocol',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.PROTOCOL
},
{
name: 'indexerId',
label: 'Indexer',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.INDEXER
},
{
name: 'size',
label: 'Size',
type: filterBuilderTypes.NUMBER
},
{
name: 'seeders',
label: 'Seeders',
type: filterBuilderTypes.NUMBER
},
{
name: 'peers',
label: 'Peers',
type: filterBuilderTypes.NUMBER
},
{
name: 'quality',
label: 'Quality',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'rejections',
label: 'Rejections',
type: filterBuilderTypes.NUMBER
}
],
episode: {
selectedFilterKey: 'all'
},
season: {
selectedFilterKey: 'season-pack'
}
};
export const persistState = [
'releases.selectedFilterKey',
'releases.episode.customFilters',
'releases.season.customFilters'
];
//
// Actions Types
export const FETCH_RELEASES = 'releases/fetchReleases';
export const CANCEL_FETCH_RELEASES = 'releases/cancelFetchReleases';
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_EPISODE_RELEASES_FILTER = 'releases/setEpisodeReleasesFilter';
export const SET_SEASON_RELEASES_FILTER = 'releases/setSeasonReleasesFilter';
//
// Action Creators
export const fetchReleases = createThunk(FETCH_RELEASES);
export const cancelFetchReleases = createThunk(CANCEL_FETCH_RELEASES);
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 setEpisodeReleasesFilter = createAction(SET_EPISODE_RELEASES_FILTER);
export const setSeasonReleasesFilter = createAction(SET_SEASON_RELEASES_FILTER);
//
// Helpers
const fetchReleasesHelper = createFetchHandler(section, '/release');
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_RELEASES]: function(getState, payload, dispatch) {
const abortRequest = fetchReleasesHelper(getState, payload, dispatch);
abortCurrentRequest = abortRequest;
},
[CANCEL_FETCH_RELEASES]: function(getState, payload, dispatch) {
if (abortCurrentRequest) {
abortCurrentRequest = abortCurrentRequest();
}
},
[GRAB_RELEASE]: function(getState, payload, dispatch) {
const guid = payload.guid;
dispatch(updateRelease({ guid, isGrabbing: true }));
const promise = $.ajax({
url: '/release',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(payload)
});
promise.done((data) => {
dispatch(updateRelease({
guid,
isGrabbing: false,
isGrabbed: true,
grabError: null
}));
});
promise.fail((xhr) => {
const grabError = xhr.responseJSON && xhr.responseJSON.message || 'Failed to add to download queue';
dispatch(updateRelease({
guid,
isGrabbing: false,
isGrabbed: false,
grabError
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_RELEASES]: (state) => {
const {
episode,
season,
...otherDefaultState
} = defaultState;
return Object.assign({}, state, otherDefaultState);
},
[UPDATE_RELEASE]: (state, { payload }) => {
const guid = payload.guid;
const newState = Object.assign({}, state);
const items = newState.items;
// Return early if there aren't any items (the user closed the modal)
if (!items.length) {
return;
}
const index = items.findIndex((item) => item.guid === guid);
const item = Object.assign({}, items[index], payload);
newState.items = [...items];
newState.items.splice(index, 1, item);
return newState;
},
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_EPISODE_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(episodeSection),
[SET_SEASON_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(seasonSection)
}, defaultState, section);
@@ -0,0 +1,97 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import { set, updateItem } from './baseActions';
//
// Variables
export const section = 'rootFolders';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isSaving: false,
saveError: null,
items: []
};
//
// Actions Types
export const FETCH_ROOT_FOLDERS = 'rootFolders/fetchRootFolders';
export const ADD_ROOT_FOLDER = 'rootFolders/addRootFolder';
export const DELETE_ROOT_FOLDER = 'rootFolders/deleteRootFolder';
//
// Action Creators
export const fetchRootFolders = createThunk(FETCH_ROOT_FOLDERS);
export const addRootFolder = createThunk(ADD_ROOT_FOLDER);
export const deleteRootFolder = createThunk(DELETE_ROOT_FOLDER);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_ROOT_FOLDERS]: createFetchHandler('rootFolders', '/rootFolder'),
[DELETE_ROOT_FOLDER]: createRemoveItemHandler(
'rootFolders',
'/rootFolder',
(state) => state.rootFolders
),
[ADD_ROOT_FOLDER]: function(getState, payload, dispatch) {
const path = payload.path;
dispatch(set({
section,
isSaving: true
}));
const promise = $.ajax({
url: '/rootFolder',
method: 'POST',
data: JSON.stringify({ path }),
dataType: 'json'
});
promise.done((data) => {
dispatch(batchActions([
updateItem({
section,
...data
}),
set({
section,
isSaving: false,
saveError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({}, defaultState, section);
@@ -0,0 +1,139 @@
import { createAction } from 'redux-actions';
import { handleThunks } from 'Store/thunks';
import createHandleActions from './Creators/createHandleActions';
import delayProfiles from './Settings/delayProfiles';
import downloadClients from './Settings/downloadClients';
import downloadClientOptions from './Settings/downloadClientOptions';
import general from './Settings/general';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import netImportOptions from './Settings/netImportOptions';
import netImports from './Settings/netImports';
import mediaManagement from './Settings/mediaManagement';
import metadata from './Settings/metadata';
import naming from './Settings/naming';
import namingExamples from './Settings/namingExamples';
import notifications from './Settings/notifications';
import qualityDefinitions from './Settings/qualityDefinitions';
import qualityProfiles from './Settings/qualityProfiles';
import remotePathMappings from './Settings/remotePathMappings';
import restrictions from './Settings/restrictions';
import ui from './Settings/ui';
export * from './Settings/delayProfiles';
export * from './Settings/downloadClients';
export * from './Settings/downloadClientOptions';
export * from './Settings/general';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/netImportOptions';
export * from './Settings/netImports';
export * from './Settings/mediaManagement';
export * from './Settings/metadata';
export * from './Settings/naming';
export * from './Settings/namingExamples';
export * from './Settings/notifications';
export * from './Settings/qualityDefinitions';
export * from './Settings/qualityProfiles';
export * from './Settings/remotePathMappings';
export * from './Settings/restrictions';
export * from './Settings/ui';
//
// Variables
export const section = 'settings';
//
// State
export const defaultState = {
advancedSettings: false,
delayProfiles: delayProfiles.defaultState,
downloadClients: downloadClients.defaultState,
downloadClientOptions: downloadClientOptions.defaultState,
general: general.defaultState,
indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState,
netImportOptions: netImportOptions.defaultState,
netImports: netImports.defaultState,
mediaManagement: mediaManagement.defaultState,
metadata: metadata.defaultState,
naming: naming.defaultState,
namingExamples: namingExamples.defaultState,
notifications: notifications.defaultState,
qualityDefinitions: qualityDefinitions.defaultState,
qualityProfiles: qualityProfiles.defaultState,
remotePathMappings: remotePathMappings.defaultState,
restrictions: restrictions.defaultState,
ui: ui.defaultState
};
export const persistState = [
'settings.advancedSettings'
];
//
// Actions Types
export const TOGGLE_ADVANCED_SETTINGS = 'settings/toggleAdvancedSettings';
//
// Action Creators
export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
//
// Action Handlers
export const actionHandlers = handleThunks({
...delayProfiles.actionHandlers,
...downloadClients.actionHandlers,
...downloadClientOptions.actionHandlers,
...general.actionHandlers,
...indexerOptions.actionHandlers,
...indexers.actionHandlers,
...netImportOptions.actionHandlers,
...netImports.actionHandlers,
...mediaManagement.actionHandlers,
...metadata.actionHandlers,
...naming.actionHandlers,
...namingExamples.actionHandlers,
...notifications.actionHandlers,
...qualityDefinitions.actionHandlers,
...qualityProfiles.actionHandlers,
...remotePathMappings.actionHandlers,
...restrictions.actionHandlers,
...ui.actionHandlers
});
//
// Reducers
export const reducers = createHandleActions({
[TOGGLE_ADVANCED_SETTINGS]: (state, { payload }) => {
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
},
...delayProfiles.reducers,
...downloadClients.reducers,
...downloadClientOptions.reducers,
...general.reducers,
...indexerOptions.reducers,
...indexers.reducers,
...netImportOptions.reducers,
...netImports.reducers,
...mediaManagement.reducers,
...metadata.reducers,
...naming.reducers,
...namingExamples.reducers,
...notifications.reducers,
...qualityDefinitions.reducers,
...qualityProfiles.reducers,
...remotePathMappings.reducers,
...restrictions.reducers,
...ui.reducers
}, defaultState, section);
+392
View File
@@ -0,0 +1,392 @@
import $ from 'jquery';
import { createAction } from 'redux-actions';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
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';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import { set } from './baseActions';
//
// Variables
export const section = 'system';
const backupsSection = 'system.backups';
//
// State
export const defaultState = {
status: {
isFetching: false,
isPopulated: false,
error: null,
item: {}
},
health: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
diskSpace: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
tasks: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
backups: {
isFetching: false,
isPopulated: false,
error: null,
isRestoring: false,
restoreError: null,
isDeleting: false,
deleteError: null,
items: []
},
updates: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
logs: {
isFetching: false,
isPopulated: false,
pageSize: 50,
sortKey: 'time',
sortDirection: sortDirections.DESCENDING,
error: null,
items: [],
columns: [
{
name: 'level',
columnLabel: 'Level',
isSortable: false,
isVisible: true,
isModifiable: false
},
{
name: 'logger',
label: 'Component',
isSortable: false,
isVisible: true,
isModifiable: false
},
{
name: 'message',
label: 'Message',
isVisible: true,
isModifiable: false
},
{
name: 'time',
label: 'Time',
isSortable: true,
isVisible: true,
isModifiable: false
},
{
name: 'actions',
columnLabel: 'Actions',
isSortable: true,
isVisible: true,
isModifiable: false
}
],
selectedFilterKey: 'all',
filters: [
{
key: 'all',
label: 'All',
filters: []
},
{
key: 'info',
label: 'Info',
filters: [
{
key: 'level',
value: 'info',
type: filterTypes.EQUAL
}
]
},
{
key: 'warn',
label: 'Warn',
filters: [
{
key: 'level',
value: 'warn',
type: filterTypes.EQUAL
}
]
},
{
key: 'error',
label: 'Error',
filters: [
{
key: 'level',
value: 'error',
type: filterTypes.EQUAL
}
]
}
]
},
logFiles: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
updateLogFiles: {
isFetching: false,
isPopulated: false,
error: null,
items: []
}
};
export const persistState = [
'system.logs.pageSize',
'system.logs.sortKey',
'system.logs.sortDirection',
'system.logs.selectedFilterKey'
];
//
// Actions Types
export const FETCH_STATUS = 'system/status/fetchStatus';
export const FETCH_HEALTH = 'system/health/fetchHealth';
export const FETCH_DISK_SPACE = 'system/diskSpace/fetchDiskSPace';
export const FETCH_TASK = 'system/tasks/fetchTask';
export const FETCH_TASKS = 'system/tasks/fetchTasks';
export const FETCH_BACKUPS = 'system/backups/fetchBackups';
export const RESTORE_BACKUP = 'system/backups/restoreBackup';
export const CLEAR_RESTORE_BACKUP = 'system/backups/clearRestoreBackup';
export const DELETE_BACKUP = 'system/backups/deleteBackup';
export const FETCH_UPDATES = 'system/updates/fetchUpdates';
export const FETCH_LOGS = 'system/logs/fetchLogs';
export const GOTO_FIRST_LOGS_PAGE = 'system/logs/gotoLogsFirstPage';
export const GOTO_PREVIOUS_LOGS_PAGE = 'system/logs/gotoLogsPreviousPage';
export const GOTO_NEXT_LOGS_PAGE = 'system/logs/gotoLogsNextPage';
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/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';
export const RESTART = 'system/restart';
export const SHUTDOWN = 'system/shutdown';
//
// Action Creators
export const fetchStatus = createThunk(FETCH_STATUS);
export const fetchHealth = createThunk(FETCH_HEALTH);
export const fetchDiskSpace = createThunk(FETCH_DISK_SPACE);
export const fetchTask = createThunk(FETCH_TASK);
export const fetchTasks = createThunk(FETCH_TASKS);
export const fetchBackups = createThunk(FETCH_BACKUPS);
export const restoreBackup = createThunk(RESTORE_BACKUP);
export const clearRestoreBackup = createAction(CLEAR_RESTORE_BACKUP);
export const deleteBackup = createThunk(DELETE_BACKUP);
export const fetchUpdates = createThunk(FETCH_UPDATES);
export const fetchLogs = createThunk(FETCH_LOGS);
export const gotoLogsFirstPage = createThunk(GOTO_FIRST_LOGS_PAGE);
export const gotoLogsPreviousPage = createThunk(GOTO_PREVIOUS_LOGS_PAGE);
export const gotoLogsNextPage = createThunk(GOTO_NEXT_LOGS_PAGE);
export const gotoLogsLastPage = createThunk(GOTO_LAST_LOGS_PAGE);
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);
export const restart = createThunk(RESTART);
export const shutdown = createThunk(SHUTDOWN);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_STATUS]: createFetchHandler('system.status', '/system/status'),
[FETCH_HEALTH]: createFetchHandler('system.health', '/health'),
[FETCH_DISK_SPACE]: createFetchHandler('system.diskSpace', '/diskspace'),
[FETCH_TASK]: createFetchHandler('system.tasks', '/system/task'),
[FETCH_TASKS]: createFetchHandler('system.tasks', '/system/task'),
[FETCH_BACKUPS]: createFetchHandler(backupsSection, '/system/backup'),
[RESTORE_BACKUP]: function(getState, payload, dispatch) {
const {
id,
file
} = payload;
dispatch(set({
section: backupsSection,
isRestoring: true
}));
let ajaxOptions = null;
if (id) {
ajaxOptions = {
url: `/system/backup/restore/${id}`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
id
})
};
} else if (file) {
const formData = new FormData();
formData.append('restore', file);
ajaxOptions = {
url: '/system/backup/restore/upload',
method: 'POST',
processData: false,
contentType: false,
data: formData
};
} else {
dispatch(set({
section: backupsSection,
isRestoring: false,
restoreError: 'Error restoring backup'
}));
}
const promise = $.ajax(ajaxOptions);
promise.done((data) => {
dispatch(set({
section: backupsSection,
isRestoring: false,
restoreError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section: backupsSection,
isRestoring: false,
restoreError: xhr
}));
});
},
[DELETE_BACKUP]: createRemoveItemHandler(backupsSection, '/system/backup'),
[FETCH_UPDATES]: createFetchHandler('system.updates', '/update'),
[FETCH_LOG_FILES]: createFetchHandler('system.logFiles', '/log/file'),
[FETCH_UPDATE_LOG_FILES]: createFetchHandler('system.updateLogFiles', '/log/file/update'),
...createServerSideCollectionHandlers(
'system.logs',
'/log',
fetchLogs,
{
[serverSideCollectionHandlers.FETCH]: FETCH_LOGS,
[serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_LOGS_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_LOGS_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_LOGS_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_LOGS_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_LOGS_PAGE,
[serverSideCollectionHandlers.SORT]: SET_LOGS_SORT,
[serverSideCollectionHandlers.FILTER]: SET_LOGS_FILTER
}
),
[RESTART]: function(getState, payload, dispatch) {
const promise = $.ajax({
url: '/system/restart',
method: 'POST'
});
promise.done(() => {
dispatch(setAppValue({ isRestarting: true }));
});
},
[SHUTDOWN]: function() {
$.ajax({
url: '/system/shutdown',
method: 'POST'
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_RESTORE_BACKUP]: function(state, { payload }) {
return {
...state,
backups: {
...state.backups,
isRestoring: false,
restoreError: null
}
};
},
[SET_LOGS_TABLE_OPTION]: createSetTableOptionReducer('logs'),
[CLEAR_LOGS_TABLE]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);
+75
View File
@@ -0,0 +1,75 @@
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';
//
// Variables
export const section = 'tags';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
details: {
isFetching: false,
isPopulated: false,
error: null,
items: []
}
};
//
// Actions Types
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
export const actionHandlers = handleThunks({
[FETCH_TAGS]: createFetchHandler(section, '/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);
dispatch(update({ section, data: tags }));
payload.onTagCreated(data);
});
},
[DELETE_TAG]: createRemoveItemHandler(section, '/tag'),
[FETCH_TAG_DETAILS]: createFetchHandler('tags.details', '/tag/detail')
});
//
// Reducers
export const reducers = createHandleActions({}, defaultState, section);