Initial Commit Rework

This commit is contained in:
Qstick 2017-09-03 22:20:56 -04:00
parent 74a4cc048c
commit 95051cbd63
2483 changed files with 101351 additions and 111396 deletions

View file

@ -0,0 +1,41 @@
import $ from 'jquery';
import updateEpisodes from 'Utilities/Episode/updateEpisodes';
function createBatchToggleEpisodeMonitoredHandler(section, getFromState) {
return function(payload) {
return function(dispatch, getState) {
const {
episodeIds,
monitored
} = payload;
const state = getFromState(getState());
updateEpisodes(dispatch, section, state.items, episodeIds, {
isSaving: true
});
const promise = $.ajax({
url: '/episode/monitor',
method: 'PUT',
data: JSON.stringify({ episodeIds, monitored }),
dataType: 'json'
});
promise.done(() => {
updateEpisodes(dispatch, section, state.items, episodeIds, {
isSaving: false,
monitored
});
});
promise.fail(() => {
updateEpisodes(dispatch, section, state.items, episodeIds, {
isSaving: false
});
});
};
};
}
export default createBatchToggleEpisodeMonitoredHandler;

View file

@ -0,0 +1,46 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { set, update, updateItem } from '../baseActions';
function createFetchHandler(section, url) {
return function(payload = {}) {
return function(dispatch, getState) {
dispatch(set({ section, isFetching: true }));
const {
id,
...otherPayload
} = payload;
const promise = $.ajax({
url: id == null ? url : `${url}/${id}`,
data: otherPayload,
traditional: true
});
promise.done((data) => {
dispatch(batchActions([
id == null ? update({ section, data }) : updateItem({ section, ...data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
};
};
}
export default createFetchHandler;

View file

@ -0,0 +1,35 @@
import $ from 'jquery';
import { set } from '../baseActions';
function createFetchSchemaHandler(section, url) {
return function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isFetchingSchema: true }));
const promise = $.ajax({
url
});
promise.done((data) => {
dispatch(set({
section,
isFetchingSchema: false,
schemaPopulated: true,
schemaError: null,
schema: data
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetchingSchema: false,
schemaPopulated: true,
schemaError: xhr
}));
});
};
};
}
export default createFetchSchemaHandler;

View file

@ -0,0 +1,54 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url, getFromState) {
return function(payload = {}) {
return function(dispatch, getState) {
dispatch(set({ section, isFetching: true }));
const state = getFromState(getState());
const sectionState = state.hasOwnProperty(section) ? state[section] : state;
const page = payload.page || sectionState.page || 1;
const data = Object.assign({ page },
_.pick(sectionState, [
'pageSize',
'sortDirection',
'sortKey',
'filterKey',
'filterValue'
]));
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;

View file

@ -0,0 +1,47 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { set, removeItem } from '../baseActions';
function createRemoveItemHandler(section, url) {
return function(payload) {
return function(dispatch, getState) {
const {
id,
...queryParms
} = payload;
dispatch(set({ section, isDeleting: true }));
const ajaxOptions = {
url: `${url}/${id}?${$.param(queryParms, true)}`,
method: 'DELETE'
};
const promise = $.ajax(ajaxOptions);
promise.done((data) => {
dispatch(batchActions([
removeItem({ section, id }),
set({
section,
isDeleting: false,
deleteError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
return promise;
};
};
}
export default createRemoveItemHandler;

View file

@ -0,0 +1,44 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { set, update } from '../baseActions';
function createSaveHandler(section, url, getFromState) {
return function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isSaving: true }));
const state = getFromState(getState());
const saveData = Object.assign({}, state.item, state.pendingChanges);
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;

View file

@ -0,0 +1,53 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import getProviderState from 'Utilities/State/getProviderState';
import { set, updateItem } from '../baseActions';
function createSaveProviderHandler(section, url, getFromState) {
return function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isSaving: true }));
const id = payload.id;
const saveData = getProviderState(payload, getState, getFromState);
const ajaxOptions = {
url,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(saveData)
};
if (id) {
ajaxOptions.url = `${url}/${id}`;
ajaxOptions.method = 'PUT';
}
const promise = $.ajax(ajaxOptions);
promise.done((data) => {
dispatch(batchActions([
updateItem({ section, ...data }),
set({
section,
isSaving: false,
saveError: null,
pendingChanges: {}
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
};
};
}
export default createSaveProviderHandler;

View file

@ -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, getFromState, handlers) {
const actionHandlers = {};
const fetchHandlerType = handlers[serverSideCollectionHandlers.FETCH];
const fetchHandler = createFetchServerSideCollectionHandler(section, url, getFromState);
actionHandlers[fetchHandlerType] = fetchHandler;
if (handlers.hasOwnProperty(serverSideCollectionHandlers.FIRST_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.FIRST_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.FIRST, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.PREVIOUS_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.PREVIOUS_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.PREVIOUS, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.NEXT_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.NEXT_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.NEXT, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.LAST_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.LAST_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.LAST, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.EXACT_PAGE)) {
const handlerType = handlers[serverSideCollectionHandlers.EXACT_PAGE];
actionHandlers[handlerType] = createSetServerSideCollectionPageHandler(section, pages.EXACT, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.SORT)) {
const handlerType = handlers[serverSideCollectionHandlers.SORT];
actionHandlers[handlerType] = createSetServerSideCollectionSortHandler(section, getFromState, fetchHandler);
}
if (handlers.hasOwnProperty(serverSideCollectionHandlers.FILTER)) {
const handlerType = handlers[serverSideCollectionHandlers.FILTER];
actionHandlers[handlerType] = createSetServerSideCollectionFilterHandler(section, getFromState, fetchHandler);
}
return actionHandlers;
}
export default createServerSideCollectionHandlers;

View file

@ -0,0 +1,12 @@
import { set } from '../baseActions';
function createSetServerSideCollectionFilterHandler(section, getFromState, fetchHandler) {
return function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, ...payload }));
dispatch(fetchHandler({ page: 1 }));
};
};
}
export default createSetServerSideCollectionFilterHandler;

View file

@ -0,0 +1,37 @@
import pages from 'Utilities/pages';
function createSetServerSideCollectionPageHandler(section, page, getFromState, fetchHandler) {
return function(payload) {
return function(dispatch, getState) {
const state = getFromState(getState());
const sectionState = state.hasOwnProperty(section) ? state[section] : state;
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;

View file

@ -0,0 +1,28 @@
import { sortDirections } from 'Helpers/Props';
import { set } from '../baseActions';
function createSetServerSideCollectionSortHandler(section, getFromState, fetchHandler) {
return function(payload) {
return function(dispatch, getState) {
const state = getFromState(getState());
const sectionState = state.hasOwnProperty(section) ? state[section] : state;
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;

View file

@ -0,0 +1,41 @@
import $ from 'jquery';
import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions';
function createTestProviderHandler(section, url, getFromState) {
return function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, getFromState);
const ajaxOptions = {
url: `${url}/test`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(testData)
};
const promise = $.ajax(ajaxOptions);
promise.done((data) => {
dispatch(set({
section,
isTesting: false,
saveError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isTesting: false,
saveError: xhr
}));
});
};
};
}
export default createTestProviderHandler;

View file

@ -0,0 +1,41 @@
import $ from 'jquery';
import updateEpisodes from 'Utilities/Episode/updateEpisodes';
function createToggleEpisodeMonitoredHandler(section, getFromState) {
return function(payload) {
return function(dispatch, getState) {
const {
episodeId,
monitored
} = payload;
const state = getFromState(getState());
updateEpisodes(dispatch, section, state.items, [episodeId], {
isSaving: true
});
const promise = $.ajax({
url: `/episode/${episodeId}`,
method: 'PUT',
data: JSON.stringify({ monitored }),
dataType: 'json'
});
promise.done(() => {
updateEpisodes(dispatch, section, state.items, [episodeId], {
isSaving: false,
monitored
});
});
promise.fail(() => {
updateEpisodes(dispatch, section, state.items, [episodeId], {
isSaving: false
});
});
};
};
}
export default createToggleEpisodeMonitoredHandler;

View file

@ -0,0 +1,394 @@
//
// BASE
export const SET = 'SET';
export const UPDATE = 'UPDATE';
export const UPDATE_ITEM = 'UPDATE_ITEM';
export const UPDATE_SERVER_SIDE_COLLECTION = 'UPDATE_SERVER_SIDE_COLLECTION';
export const SET_SETTING_VALUE = 'SET_SETTING_VALUE';
export const CLEAR_PENDING_CHANGES = 'CLEAR_PENDING_CHANGES';
export const SAVE_SETTINGS = 'SAVE_SETTINGS';
export const REMOVE_ITEM = 'REMOVE_ITEM';
//
// 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';
//
// Add Series
export const LOOKUP_SERIES = 'LOOKUP_SERIES';
export const ADD_SERIES = 'ADD_SERIES';
export const SET_ADD_SERIES_VALUE = 'SET_ADD_SERIES_VALUE';
export const CLEAR_ADD_SERIES = 'CLEAR_ADD_SERIES';
export const SET_ADD_SERIES_DEFAULT = 'SET_ADD_SERIES_DEFAULT';
//
// Import Series
export const QUEUE_LOOKUP_SERIES = 'QUEUE_LOOKUP_SERIES';
export const START_LOOKUP_SERIES = 'START_LOOKUP_SERIES';
export const CLEAR_IMPORT_SERIES = 'CLEAR_IMPORT_SERIES';
export const SET_IMPORT_SERIES_VALUE = 'SET_IMPORT_SERIES_VALUE';
export const IMPORT_SERIES = 'IMPORT_SERIES';
//
// Series
export const FETCH_ARTIST = 'FETCH_ARTIST';
export const SET_ARTIST_VALUE = 'SET_ARTIST_VALUE';
export const SAVE_ARTIST = 'SAVE_ARTIST';
export const DELETE_ARTIST = 'DELETE_ARTIST';
export const SET_ARTIST_SORT = 'SET_ARTIST_SORT';
export const SET_ARTIST_FILTER = 'SET_ARTIST_FILTER';
export const SET_ARTIST_VIEW = 'SET_ARTIST_VIEW';
export const SET_ARTIST_TABLE_OPTION = 'SET_ARTIST_TABLE_OPTION';
export const SET_ARTIST_POSTER_OPTION = 'SET_ARTIST_POSTER_OPTION';
export const TOGGLE_ARTIST_MONITORED = 'TOGGLE_ARTIST_MONITORED';
export const TOGGLE_ALBUM_MONITORED = 'TOGGLE_ALBUM_MONITORED';
//
// Series Editor
export const SET_SERIES_EDITOR_SORT = 'SET_SERIES_EDITOR_SORT';
export const SET_SERIES_EDITOR_FILTER = 'SET_SERIES_EDITOR_FILTER';
export const SAVE_ARTIST_EDITOR = 'SAVE_ARTIST_EDITOR';
export const BULK_DELETE_ARTIST = 'BULK_DELETE_ARTIST';
//
// Season Pass
export const SET_SEASON_PASS_SORT = 'SET_SEASON_PASS_SORT';
export const SET_SEASON_PASS_FILTER = 'SET_SEASON_PASS_FILTER';
export const SAVE_SEASON_PASS = 'SAVE_SEASON_PASS';
//
// Episodes
export const FETCH_EPISODES = 'FETCH_EPISODES';
export const SET_EPISODES_SORT = 'SET_EPISODES_SORT';
export const SET_EPISODES_TABLE_OPTION = 'SET_EPISODES_TABLE_OPTION';
export const CLEAR_EPISODES = 'CLEAR_EPISODES';
export const TOGGLE_EPISODE_MONITORED = 'TOGGLE_EPISODE_MONITORED';
export const TOGGLE_EPISODES_MONITORED = 'TOGGLE_EPISODES_MONITORED';
//
// Episode Files
export const FETCH_EPISODE_FILES = 'FETCH_EPISODE_FILES';
export const CLEAR_EPISODE_FILES = 'CLEAR_EPISODE_FILES';
export const DELETE_EPISODE_FILE = 'DELETE_EPISODE_FILE';
export const DELETE_EPISODE_FILES = 'DELETE_EPISODE_FILES';
export const UPDATE_EPISODE_FILES = 'UPDATE_EPISODE_FILES';
//
// Episode History
export const FETCH_EPISODE_HISTORY = 'FETCH_EPISODE_HISTORY';
export const CLEAR_EPISODE_HISTORY = 'CLEAR_EPISODE_HISTORY';
export const EPISODE_HISTORY_MARK_AS_FAILED = 'EPISODE_HISTORY_MARK_AS_FAILED';
//
// Releases
export const FETCH_RELEASES = 'FETCH_RELEASES';
export const SET_RELEASES_SORT = 'SET_RELEASES_SORT';
export const CLEAR_RELEASES = 'CLEAR_RELEASES';
export const GRAB_RELEASE = 'GRAB_RELEASE';
export const UPDATE_RELEASE = 'UPDATE_RELEASE';
//
// Calendar
export const FETCH_CALENDAR = 'FETCH_CALENDAR';
export const SET_CALENDAR_DAYS_COUNT = 'SET_CALENDAR_DAYS_COUNT';
export const SET_CALENDAR_INCLUDE_UNMONITORED = 'SET_CALENDAR_INCLUDE_UNMONITORED';
export const SET_CALENDAR_VIEW = 'SET_CALENDAR_VIEW';
export const GOTO_CALENDAR_TODAY = 'GOTO_CALENDAR_TODAY';
export const GOTO_CALENDAR_PREVIOUS_RANGE = 'GOTO_CALENDAR_PREVIOUS_RANGE';
export const GOTO_CALENDAR_NEXT_RANGE = 'GOTO_CALENDAR_NEXT_RANGE';
export const CLEAR_CALENDAR = 'CLEAR_CALENDAR';
//
// History
export const FETCH_HISTORY = 'FETCH_HISTORY';
export const GOTO_FIRST_HISTORY_PAGE = 'GOTO_FIRST_HISTORY_PAGE';
export const GOTO_PREVIOUS_HISTORY_PAGE = 'GOTO_PREVIOUS_HISTORY_PAGE';
export const GOTO_NEXT_HISTORY_PAGE = 'GOTO_NEXT_HISTORY_PAGE';
export const GOTO_LAST_HISTORY_PAGE = 'GOTO_LAST_HISTORY_PAGE';
export const GOTO_HISTORY_PAGE = 'GOTO_HISTORY_PAGE';
export const SET_HISTORY_SORT = 'SET_HISTORY_SORT';
export const SET_HISTORY_FILTER = 'SET_HISTORY_FILTER';
export const SET_HISTORY_TABLE_OPTION = 'SET_HISTORY_TABLE_OPTION';
export const CLEAR_HISTORY = 'CLEAR_HISTORY';
export const MARK_AS_FAILED = 'MARK_AS_FAILED';
//
// Queue
export const FETCH_QUEUE_STATUS = 'FETCH_QUEUE_STATUS';
export const FETCH_QUEUE_DETAILS = 'FETCH_QUEUE_DETAILS';
export const CLEAR_QUEUE_DETAILS = 'CLEAR_QUEUE_DETAILS';
export const FETCH_QUEUE = 'FETCH_QUEUE';
export const GOTO_FIRST_QUEUE_PAGE = 'GOTO_FIRST_QUEUE_PAGE';
export const GOTO_PREVIOUS_QUEUE_PAGE = 'GOTO_PREVIOUS_QUEUE_PAGE';
export const GOTO_NEXT_QUEUE_PAGE = 'GOTO_NEXT_QUEUE_PAGE';
export const GOTO_LAST_QUEUE_PAGE = 'GOTO_LAST_QUEUE_PAGE';
export const GOTO_QUEUE_PAGE = 'GOTO_QUEUE_PAGE';
export const SET_QUEUE_SORT = 'SET_QUEUE_SORT';
export const SET_QUEUE_TABLE_OPTION = 'SET_QUEUE_TABLE_OPTION';
export const CLEAR_QUEUE = 'CLEAR_QUEUE';
export const SET_QUEUE_EPISODES = 'SET_QUEUE_EPISODES';
export const GRAB_QUEUE_ITEM = 'GRAB_QUEUE_ITEM';
export const GRAB_QUEUE_ITEMS = 'GRAB_QUEUE_ITEMS';
export const REMOVE_QUEUE_ITEM = 'REMOVE_QUEUE_ITEM';
export const REMOVE_QUEUE_ITEMS = 'REMOVE_QUEUE_ITEMS';
//
// Blacklist
export const FETCH_BLACKLIST = 'FETCH_BLACKLIST';
export const GOTO_FIRST_BLACKLIST_PAGE = 'GOTO_FIRST_BLACKLIST_PAGE';
export const GOTO_PREVIOUS_BLACKLIST_PAGE = 'GOTO_PREVIOUS_BLACKLIST_PAGE';
export const GOTO_NEXT_BLACKLIST_PAGE = 'GOTO_NEXT_BLACKLIST_PAGE';
export const GOTO_LAST_BLACKLIST_PAGE = 'GOTO_LAST_BLACKLIST_PAGE';
export const GOTO_BLACKLIST_PAGE = 'GOTO_BLACKLIST_PAGE';
export const SET_BLACKLIST_SORT = 'SET_BLACKLIST_SORT';
export const SET_BLACKLIST_TABLE_OPTION = 'SET_BLACKLIST_TABLE_OPTION';
//
// Wanted
export const FETCH_MISSING = 'FETCH_MISSING';
export const GOTO_FIRST_MISSING_PAGE = 'GOTO_FIRST_MISSING_PAGE';
export const GOTO_PREVIOUS_MISSING_PAGE = 'GOTO_PREVIOUS_MISSING_PAGE';
export const GOTO_NEXT_MISSING_PAGE = 'GOTO_NEXT_MISSING_PAGE';
export const GOTO_LAST_MISSING_PAGE = 'GOTO_LAST_MISSING_PAGE';
export const GOTO_MISSING_PAGE = 'GOTO_MISSING_PAGE';
export const SET_MISSING_SORT = 'SET_MISSING_SORT';
export const SET_MISSING_FILTER = 'SET_MISSING_FILTER';
export const SET_MISSING_TABLE_OPTION = 'SET_MISSING_TABLE_OPTION';
export const CLEAR_MISSING = 'CLEAR_MISSING';
export const BATCH_TOGGLE_MISSING_EPISODES = 'BATCH_TOGGLE_MISSING_EPISODES';
export const FETCH_CUTOFF_UNMET = 'FETCH_CUTOFF_UNMET';
export const GOTO_FIRST_CUTOFF_UNMET_PAGE = 'GOTO_FIRST_CUTOFF_UNMET_PAGE';
export const GOTO_PREVIOUS_CUTOFF_UNMET_PAGE = 'GOTO_PREVIOUS_CUTOFF_UNMET_PAGE';
export const GOTO_NEXT_CUTOFF_UNMET_PAGE = 'GOTO_NEXT_CUTOFF_UNMET_PAGE';
export const GOTO_LAST_CUTOFF_UNMET_PAGE = 'GOTO_LAST_CUTOFF_UNMET_PAGE';
export const GOTO_CUTOFF_UNMET_PAGE = 'GOTO_CUTOFF_UNMET_PAGE';
export const SET_CUTOFF_UNMET_SORT = 'SET_CUTOFF_UNMET_SORT';
export const SET_CUTOFF_UNMET_FILTER = 'SET_CUTOFF_UNMET_FILTER';
export const SET_CUTOFF_UNMET_TABLE_OPTION = 'SET_CUTOFF_UNMET_TABLE_OPTION';
export const CLEAR_CUTOFF_UNMET = 'CLEAR_CUTOFF_UNMET';
export const BATCH_TOGGLE_CUTOFF_UNMET_EPISODES = 'BATCH_TOGGLE_CUTOFF_UNMET_EPISODES';
//
// Settings
export const TOGGLE_ADVANCED_SETTINGS = 'TOGGLE_ADVANCED_SETTINGS';
export const FETCH_UI_SETTINGS = 'FETCH_UI_SETTINGS';
export const SET_UI_SETTINGS_VALUE = 'SET_UI_SETTINGS_VALUE';
export const SAVE_UI_SETTINGS = 'SAVE_UI_SETTINGS';
export const FETCH_MEDIA_MANAGEMENT_SETTINGS = 'FETCH_MEDIA_MANAGEMENT_SETTINGS';
export const SET_MEDIA_MANAGEMENT_SETTINGS_VALUE = 'SET_MEDIA_MANAGEMENT_SETTINGS_VALUE';
export const SAVE_MEDIA_MANAGEMENT_SETTINGS = 'SAVE_MEDIA_MANAGEMENT_SETTINGS';
export const FETCH_NAMING_SETTINGS = 'FETCH_NAMING_SETTINGS';
export const SET_NAMING_SETTINGS_VALUE = 'SET_NAMING_SETTINGS_VALUE';
export const SAVE_NAMING_SETTINGS = 'SAVE_NAMING_SETTINGS';
export const FETCH_NAMING_EXAMPLES = 'FETCH_NAMING_EXAMPLES';
export const FETCH_QUALITY_PROFILES = 'FETCH_QUALITY_PROFILES';
export const FETCH_QUALITY_PROFILE_SCHEMA = 'FETCH_QUALITY_PROFILE_SCHEMA';
export const SET_QUALITY_PROFILE_VALUE = 'SET_QUALITY_PROFILE_VALUE';
export const SAVE_QUALITY_PROFILE = 'SAVE_QUALITY_PROFILE';
export const DELETE_QUALITY_PROFILE = 'DELETE_QUALITY_PROFILE';
export const FETCH_LANGUAGE_PROFILES = 'FETCH_LANGUAGE_PROFILES';
export const FETCH_LANGUAGE_PROFILE_SCHEMA = 'FETCH_LANGUAGE_PROFILE_SCHEMA';
export const SET_LANGUAGE_PROFILE_VALUE = 'SET_LANGUAGE_PROFILE_VALUE';
export const SAVE_LANGUAGE_PROFILE = 'SAVE_LANGUAGE_PROFILE';
export const DELETE_LANGUAGE_PROFILE = 'DELETE_LANGUAGE_PROFILE';
export const FETCH_DELAY_PROFILES = 'FETCH_DELAY_PROFILES';
export const SET_DELAY_PROFILE_VALUE = 'SET_DELAY_PROFILE_VALUE';
export const SAVE_DELAY_PROFILE = 'SAVE_DELAY_PROFILE';
export const DELETE_DELAY_PROFILE = 'DELETE_DELAY_PROFILE';
export const REORDER_DELAY_PROFILE = 'REORDER_DELAY_PROFILE';
export const FETCH_QUALITY_DEFINITIONS = 'FETCH_QUALITY_DEFINITIONS';
export const SET_QUALITY_DEFINITION_VALUE = 'SET_QUALITY_DEFINITION_VALUE';
export const SAVE_QUALITY_DEFINITIONS = 'SAVE_QUALITY_DEFINITIONS';
export const FETCH_INDEXERS = 'FETCH_INDEXERS';
export const FETCH_INDEXER_SCHEMA = 'FETCH_INDEXER_SCHEMA';
export const SELECT_INDEXER_SCHEMA = 'SELECT_INDEXER_SCHEMA';
export const SET_INDEXER_VALUE = 'SET_INDEXER_VALUE';
export const SET_INDEXER_FIELD_VALUE = 'SET_INDEXER_FIELD_VALUE';
export const SAVE_INDEXER = 'SAVE_INDEXER';
export const DELETE_INDEXER = 'DELETE_INDEXER';
export const TEST_INDEXER = 'TEST_INDEXER';
export const FETCH_INDEXER_OPTIONS = 'FETCH_INDEXER_OPTIONS';
export const SET_INDEXER_OPTIONS_VALUE = 'SET_INDEXER_OPTIONS_VALUE';
export const SAVE_INDEXER_OPTIONS = 'SAVE_INDEXER_OPTIONS';
export const FETCH_RESTRICTIONS = 'FETCH_RESTRICTIONS';
export const SET_RESTRICTION_VALUE = 'SET_RESTRICTION_VALUE';
export const SAVE_RESTRICTION = 'SAVE_RESTRICTION';
export const DELETE_RESTRICTION = 'DELETE_RESTRICTION';
export const FETCH_DOWNLOAD_CLIENTS = 'FETCH_DOWNLOAD_CLIENTS';
export const FETCH_DOWNLOAD_CLIENT_SCHEMA = 'FETCH_DOWNLOAD_CLIENT_SCHEMA';
export const SELECT_DOWNLOAD_CLIENT_SCHEMA = 'SELECT_DOWNLOAD_CLIENT_SCHEMA';
export const SET_DOWNLOAD_CLIENT_VALUE = 'SET_DOWNLOAD_CLIENT_VALUE';
export const SET_DOWNLOAD_CLIENT_FIELD_VALUE = 'SET_DOWNLOAD_CLIENT_FIELD_VALUE';
export const SAVE_DOWNLOAD_CLIENT = 'SAVE_DOWNLOAD_CLIENT';
export const DELETE_DOWNLOAD_CLIENT = 'DELETE_DOWNLOAD_CLIENT';
export const TEST_DOWNLOAD_CLIENT = 'TEST_DOWNLOAD_CLIENT';
export const FETCH_DOWNLOAD_CLIENT_OPTIONS = 'FETCH_DOWNLOAD_CLIENT_OPTIONS';
export const SET_DOWNLOAD_CLIENT_OPTIONS_VALUE = 'SET_DOWNLOAD_CLIENT_OPTIONS_VALUE';
export const SAVE_DOWNLOAD_CLIENT_OPTIONS = 'SAVE_DOWNLOAD_CLIENT_OPTIONS';
export const FETCH_REMOTE_PATH_MAPPINGS = 'FETCH_REMOTE_PATH_MAPPINGS';
export const SET_REMOTE_PATH_MAPPING_VALUE = 'SET_REMOTE_PATH_MAPPING_VALUE';
export const SAVE_REMOTE_PATH_MAPPING = 'SAVE_REMOTE_PATH_MAPPING';
export const DELETE_REMOTE_PATH_MAPPING = 'DELETE_REMOTE_PATH_MAPPING';
export const FETCH_NOTIFICATIONS = 'FETCH_NOTIFICATIONS';
export const FETCH_NOTIFICATION_SCHEMA = 'FETCH_NOTIFICATION_SCHEMA';
export const SELECT_NOTIFICATION_SCHEMA = 'SELECT_NOTIFICATION_SCHEMA';
export const SET_NOTIFICATION_VALUE = 'SET_NOTIFICATION_VALUE';
export const SET_NOTIFICATION_FIELD_VALUE = 'SET_NOTIFICATION_FIELD_VALUE';
export const SAVE_NOTIFICATION = 'SAVE_NOTIFICATION';
export const DELETE_NOTIFICATION = 'DELETE_NOTIFICATION';
export const TEST_NOTIFICATION = 'TEST_NOTIFICATION';
export const FETCH_METADATA = 'FETCH_METADATA';
export const SET_METADATA_VALUE = 'SET_METADATA_VALUE';
export const SET_METADATA_FIELD_VALUE = 'SET_METADATA_FIELD_VALUE';
export const SAVE_METADATA = 'SAVE_METADATA';
//
// System
export const FETCH_STATUS = 'FETCH_STATUS';
export const FETCH_HEALTH = 'FETCH_HEALTH';
export const FETCH_DISK_SPACE = 'FETCH_DISK_SPACE';
export const FETCH_TASK = 'FETCH_TASK';
export const FETCH_TASKS = 'FETCH_TASKS';
export const FETCH_BACKUPS = 'FETCH_BACKUPS';
export const FETCH_UPDATES = 'FETCH_UPDATES';
export const FETCH_LOGS = 'FETCH_LOGS';
export const GOTO_FIRST_LOGS_PAGE = 'GOTO_FIRST_LOGS_PAGE';
export const GOTO_PREVIOUS_LOGS_PAGE = 'GOTO_PREVIOUS_LOGS_PAGE';
export const GOTO_NEXT_LOGS_PAGE = 'GOTO_NEXT_LOGS_PAGE';
export const GOTO_LAST_LOGS_PAGE = 'GOTO_LAST_LOGS_PAGE';
export const GOTO_LOGS_PAGE = 'GOTO_LOGS_PAGE';
export const SET_LOGS_SORT = 'SET_LOGS_SORT';
export const SET_LOGS_FILTER = 'SET_LOGS_FILTER';
export const SET_LOGS_TABLE_OPTION = 'SET_LOGS_TABLE_OPTION';
export const FETCH_LOG_FILES = 'FETCH_LOG_FILES';
export const FETCH_UPDATE_LOG_FILES = 'FETCH_UPDATE_LOG_FILES';
export const FETCH_GENERAL_SETTINGS = 'FETCH_GENERAL_SETTINGS';
export const SET_GENERAL_SETTINGS_VALUE = 'SET_GENERAL_SETTINGS_VALUE';
export const SAVE_GENERAL_SETTINGS = 'SAVE_GENERAL_SETTINGS';
export const RESTART = 'RESTART';
export const SHUTDOWN = 'SHUTDOWN';
//
// Commands
export const FETCH_COMMANDS = 'FETCH_COMMANDS';
export const EXECUTE_COMMAND = 'EXECUTE_COMMAND';
export const ADD_COMMAND = 'ADD_COMMAND';
export const UPDATE_COMMAND = 'UPDATE_COMMAND';
export const FINISH_COMMAND = 'FINISH_COMMAND';
export const REMOVE_COMMAND = 'REMOVE_COMMAND';
export const REGISTER_FINISH_COMMAND_HANDLER = 'REGISTER_FINISH_COMMAND_HANDLER';
export const UNREGISTER_FINISH_COMMAND_HANDLER = 'UNREGISTER_FINISH_COMMAND_HANDLER';
//
// Paths
export const FETCH_PATHS = 'FETCH_PATHS';
export const UPDATE_PATHS = 'UPDATE_PATHS';
export const CLEAR_PATHS = 'CLEAR_PATHS';
//
// Languages
export const FETCH_LANGUAGES = 'FETCH_LANGUAGES';
//
// Tags
export const FETCH_TAGS = 'FETCH_TAGS';
export const ADD_TAG = 'ADD_TAG';
//
// Captcha
export const REFRESH_CAPTCHA = 'REFRESH_CAPTCHA';
export const GET_CAPTCHA_COOKIE = 'GET_CAPTCHA_COOKIE';
export const SET_CAPTCHA_VALUE = 'SET_CAPTCHA_VALUE';
export const RESET_CAPTCHA = 'RESET_CAPTCHA';
//
// OAuth
export const START_OAUTH = 'START_OAUTH';
export const GET_OAUTH_TOKEN = 'GET_OAUTH_TOKEN';
export const SET_OAUTH_VALUE = 'SET_OAUTH_VALUE';
export const RESET_OAUTH = 'RESET_OAUTH';
//
// Interactive Import
export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'FETCH_INTERACTIVE_IMPORT_ITEMS';
export const UPDATE_INTERACTIVE_IMPORT_ITEM = 'UPDATE_INTERACTIVE_IMPORT_ITEM';
export const SET_INTERACTIVE_IMPORT_SORT = 'SET_INTERACTIVE_IMPORT_SORT';
export const CLEAR_INTERACTIVE_IMPORT = 'CLEAR_INTERACTIVE_IMPORT';
export const ADD_RECENT_FOLDER = 'ADD_RECENT_FOLDER';
export const REMOVE_RECENT_FOLDER = 'REMOVE_RECENT_FOLDER';
export const SET_INTERACTIVE_IMPORT_MODE = 'SET_INTERACTIVE_IMPORT_MODE';
//
// Root Folders
export const FETCH_ROOT_FOLDERS = 'FETCH_ROOT_FOLDERS';
export const ADD_ROOT_FOLDER = 'ADD_ROOT_FOLDER';
export const DELETE_ROOT_FOLDER = 'DELETE_ROOT_FOLDER';
//
// Organize Preview
export const FETCH_ORGANIZE_PREVIEW = 'FETCH_ORGANIZE_PREVIEW';
export const CLEAR_ORGANIZE_PREVIEW = 'CLEAR_ORGANIZE_PREVIEW';

View file

@ -0,0 +1,98 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import getNewSeries from 'Utilities/Series/getNewSeries';
import * as types from './actionTypes';
import { set, update, updateItem } from './baseActions';
let currentXHR = null;
let xhrCancelled = false;
const section = 'addSeries';
const addSeriesActionHandlers = {
[types.LOOKUP_SERIES]: function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isFetching: true }));
if (currentXHR) {
xhrCancelled = true;
currentXHR.abort();
currentXHR = null;
}
currentXHR = new window.XMLHttpRequest();
xhrCancelled = false;
const promise = $.ajax({
url: '/artist/lookup',
xhr: () => currentXHR,
data: {
term: payload.term
}
});
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: xhrCancelled ? null : xhr
}));
});
};
},
[types.ADD_SERIES]: function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isAdding: true }));
const foreignArtistId = payload.foreignArtistId;
const items = getState().addSeries.items;
const newSeries = getNewSeries(_.cloneDeep(_.find(items, { foreignArtistId })), payload);
const promise = $.ajax({
url: '/artist',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(newSeries)
});
promise.done((data) => {
dispatch(batchActions([
updateItem({ section: 'series', ...data }),
set({
section,
isAdding: false,
isAdded: true,
addError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isAdding: false,
isAdded: false,
addError: xhr
}));
});
};
}
};
export default addSeriesActionHandlers;

View file

@ -0,0 +1,15 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import addSeriesActionHandlers from './addSeriesActionHandlers';
export const lookupSeries = addSeriesActionHandlers[types.LOOKUP_SERIES];
export const addSeries = addSeriesActionHandlers[types.ADD_SERIES];
export const clearAddSeries = createAction(types.CLEAR_ADD_SERIES);
export const setAddSeriesDefault = createAction(types.SET_ADD_SERIES_DEFAULT);
export const setAddSeriesValue = createAction(types.SET_ADD_SERIES_VALUE, (payload) => {
return {
section: 'addSeries',
...payload
};
});

View file

@ -0,0 +1,27 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
export const saveDimensions = createAction(types.SAVE_DIMENSIONS);
export const setVersion = createAction(types.SET_VERSION);
export const setIsSidebarVisible = createAction(types.SET_IS_SIDEBAR_VISIBLE);
export const setAppValue = createAction(types.SET_APP_VALUE, (payload) => {
return {
section: 'app',
...payload
};
});
export const showMessage = createAction(types.SHOW_MESSAGE, (payload) => {
return {
section: 'messages',
...payload
};
});
export const hideMessage = createAction(types.HIDE_MESSAGE, (payload) => {
return {
section: 'messages',
...payload
};
});

View file

@ -0,0 +1,130 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes';
import createFetchHandler from './Creators/createFetchHandler';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import { updateItem } from './baseActions';
const section = 'series';
const artistActionHandlers = {
[types.FETCH_ARTIST]: createFetchHandler(section, '/artist'),
[types.SAVE_ARTIST]: createSaveProviderHandler(section,
'/artist',
(state) => state.series),
[types.DELETE_ARTIST]: createRemoveItemHandler(section,
'/artist',
(state) => state.series),
[types.TOGGLE_ARTIST_MONITORED]: function(payload) {
return function(dispatch, getState) {
const {
artistId: id,
monitored
} = payload;
const series = _.find(getState().series.items, { id });
dispatch(updateItem({
id,
section,
isSaving: true
}));
const promise = $.ajax({
url: `/artist/${id}`,
method: 'PUT',
data: JSON.stringify({
...series,
monitored
}),
dataType: 'json'
});
promise.done((data) => {
dispatch(updateItem({
id,
section,
isSaving: false,
monitored
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
id,
section,
isSaving: false
}));
});
};
},
[types.TOGGLE_ALBUM_MONITORED]: function(payload) {
return function(dispatch, getState) {
const {
artistId: id,
seasonNumber,
monitored
} = payload;
const series = _.find(getState().series.items, { id });
const seasons = _.cloneDeep(series.seasons);
const season = _.find(seasons, { seasonNumber });
season.isSaving = true;
dispatch(updateItem({
id,
section,
seasons
}));
season.monitored = monitored;
const promise = $.ajax({
url: `/artist/${id}`,
method: 'PUT',
data: JSON.stringify({
...series,
seasons
}),
dataType: 'json'
});
promise.done((data) => {
const episodes = _.filter(getState().episodes.items, { artistId: id, seasonNumber });
dispatch(batchActions([
updateItem({
id,
section,
...data
}),
...episodes.map((episode) => {
return updateItem({
id: episode.id,
section: 'episodes',
monitored
});
})
]));
});
promise.fail((xhr) => {
dispatch(updateItem({
id,
section,
seasons: series.seasons
}));
});
};
}
};
export default artistActionHandlers;

View file

@ -0,0 +1,8 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
export const setArtistSort = createAction(types.SET_ARTIST_SORT);
export const setArtistFilter = createAction(types.SET_ARTIST_FILTER);
export const setArtistView = createAction(types.SET_ARTIST_VIEW);
export const setArtistTableOption = createAction(types.SET_ARTIST_TABLE_OPTION);
export const setArtistPosterOption = createAction(types.SET_ARTIST_POSTER_OPTION);

View file

@ -0,0 +1,13 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
export const set = createAction(types.SET);
export const update = createAction(types.UPDATE);
export const updateItem = createAction(types.UPDATE_ITEM);
export const updateServerSideCollection = createAction(types.UPDATE_SERVER_SIDE_COLLECTION);
export const setSettingValue = createAction(types.SET_SETTING_VALUE);
export const clearPendingChanges = createAction(types.CLEAR_PENDING_CHANGES);
export const removeItem = createAction(types.REMOVE_ITEM);

View file

@ -0,0 +1,17 @@
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import * as types from './actionTypes';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
const blacklistActionHandlers = {
...createServerSideCollectionHandlers('blacklist', '/blacklist', (state) => state, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_BLACKLIST,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_BLACKLIST_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_BLACKLIST_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_BLACKLIST_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_BLACKLIST_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_BLACKLIST_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_BLACKLIST_SORT
})
};
export default blacklistActionHandlers;

View file

@ -0,0 +1,12 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import blacklistActionHandlers from './blacklistActionHandlers';
export const fetchBlacklist = blacklistActionHandlers[types.FETCH_BLACKLIST];
export const gotoBlacklistFirstPage = blacklistActionHandlers[types.GOTO_FIRST_BLACKLIST_PAGE];
export const gotoBlacklistPreviousPage = blacklistActionHandlers[types.GOTO_PREVIOUS_BLACKLIST_PAGE];
export const gotoBlacklistNextPage = blacklistActionHandlers[types.GOTO_NEXT_BLACKLIST_PAGE];
export const gotoBlacklistLastPage = blacklistActionHandlers[types.GOTO_LAST_BLACKLIST_PAGE];
export const gotoBlacklistPage = blacklistActionHandlers[types.GOTO_BLACKLIST_PAGE];
export const setBlacklistSort = blacklistActionHandlers[types.SET_BLACKLIST_SORT];
export const setBlacklistTableOption = createAction(types.SET_BLACKLIST_TABLE_OPTION);

View file

@ -0,0 +1,264 @@
import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import { batchActions } from 'redux-batched-actions';
import * as calendarViews from 'Calendar/calendarViews';
import * as types from './actionTypes';
import { set, update } from './baseActions';
import { fetchCalendar } from './calendarActions';
const viewRanges = {
[calendarViews.DAY]: 'day',
[calendarViews.WEEK]: 'week',
[calendarViews.MONTH]: 'month',
[calendarViews.FORECAST]: 'day'
};
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;
}
const section = 'calendar';
const calendarActionHandlers = {
[types.FETCH_CALENDAR]: function(payload) {
return function(dispatch, getState) {
const state = getState();
const unmonitored = state.calendar.unmonitored;
const {
time,
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
}));
});
};
},
[types.SET_CALENDAR_DAYS_COUNT]: function(payload) {
return function(dispatch, getState) {
dispatch(set({
section,
dayCount: payload.dayCount
}));
const state = getState();
const { time, view } = state.calendar;
dispatch(fetchCalendar({
time,
view
}));
};
},
[types.SET_CALENDAR_INCLUDE_UNMONITORED]: function(payload) {
return function(dispatch, getState) {
dispatch(set({
section,
unmonitored: payload.unmonitored
}));
const state = getState();
const { time, view } = state.calendar;
dispatch(fetchCalendar({
time,
view
}));
};
},
[types.SET_CALENDAR_VIEW]: function(payload) {
return function(dispatch, getState) {
const state = getState();
const view = payload.view;
const time = view === calendarViews.FORECAST ? moment() : state.calendar.time;
dispatch(fetchCalendar({ time, view }));
};
},
[types.GOTO_CALENDAR_TODAY]: function(payload) {
return function(dispatch, getState) {
const state = getState();
const view = state.calendar.view;
const time = moment();
dispatch(fetchCalendar({ time, view }));
};
},
[types.GOTO_CALENDAR_PREVIOUS_RANGE]: function(payload) {
return function(dispatch, getState) {
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 }));
};
},
[types.GOTO_CALENDAR_NEXT_RANGE]: function(payload) {
return function(dispatch, getState) {
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 }));
};
}
};
export default calendarActionHandlers;

View file

@ -0,0 +1,12 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import calendarActionHandlers from './calendarActionHandlers';
export const fetchCalendar = calendarActionHandlers[types.FETCH_CALENDAR];
export const setCalendarDaysCount = calendarActionHandlers[types.SET_CALENDAR_DAYS_COUNT];
export const setCalendarIncludeUnmonitored = calendarActionHandlers[types.SET_CALENDAR_INCLUDE_UNMONITORED];
export const setCalendarView = calendarActionHandlers[types.SET_CALENDAR_VIEW];
export const gotoCalendarToday = calendarActionHandlers[types.GOTO_CALENDAR_TODAY];
export const gotoCalendarPreviousRange = calendarActionHandlers[types.GOTO_CALENDAR_PREVIOUS_RANGE];
export const gotoCalendarNextRange = calendarActionHandlers[types.GOTO_CALENDAR_NEXT_RANGE];
export const clearCalendar = createAction(types.CLEAR_CALENDAR);

View file

@ -0,0 +1,67 @@
import requestAction from 'Utilities/requestAction';
import * as types from './actionTypes';
import { setCaptchaValue } from './captchaActions';
const captchaActionHandlers = {
[types.REFRESH_CAPTCHA]: function(payload) {
return (dispatch, getState) => {
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
}));
});
};
},
[types.GET_CAPTCHA_COOKIE]: function(payload) {
return (dispatch, getState) => {
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
}));
});
};
}
};
export default captchaActionHandlers;

View file

@ -0,0 +1,8 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import captchaActionHandlers from './captchaActionHandlers';
export const refreshCaptcha = captchaActionHandlers[types.REFRESH_CAPTCHA];
export const getCaptchaCookie = captchaActionHandlers[types.GET_CAPTCHA_COOKIE];
export const setCaptchaValue = createAction(types.SET_CAPTCHA_VALUE);
export const resetCaptcha = createAction(types.RESET_CAPTCHA);

View file

@ -0,0 +1,141 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { isSameCommand } from 'Utilities/Command';
import { messageTypes } from 'Helpers/Props';
import * as types from './actionTypes';
import createFetchHandler from './Creators/createFetchHandler';
import { showMessage, hideMessage } from './appActions';
import { updateItem } from './baseActions';
import { addCommand, removeCommand } from './commandActions';
let lastCommand = null;
let lastCommandTimeout = null;
const removeCommandTimeoutIds = {};
function showCommandMessage(payload, dispatch) {
const {
id,
name,
manual,
message,
body = {},
state
} = payload;
const {
sendUpdatesToClient,
suppressMessages
} = body;
if (!message || !body || !sendUpdatesToClient || suppressMessages) {
return;
}
let type = messageTypes.INFO;
let hideAfter = 0;
if (state === 'completed') {
type = messageTypes.SUCCESS;
hideAfter = 4;
} else if (state === 'failed') {
type = messageTypes.ERROR;
hideAfter = manual ? 10 : 4;
}
dispatch(showMessage({
id,
name,
message,
type,
hideAfter
}));
}
function scheduleRemoveCommand(command, dispatch) {
const {
id,
state
} = command;
if (state === 'queued') {
return;
}
const timeoutId = removeCommandTimeoutIds[id];
if (timeoutId) {
clearTimeout(timeoutId);
}
removeCommandTimeoutIds[id] = setTimeout(() => {
dispatch(batchActions([
removeCommand({ section: 'commands', id }),
hideMessage({ id })
]));
delete removeCommandTimeoutIds[id];
}, 30000);
}
const commandActionHandlers = {
[types.FETCH_COMMANDS]: createFetchHandler('commands', '/command'),
[types.EXECUTE_COMMAND](payload) {
return (dispatch, getState) => {
// TODO: show a message for the user
if (lastCommand && isSameCommand(lastCommand, payload)) {
console.warn('Please wait at least 5 seconds before running this command again');
}
lastCommand = payload;
// clear last command after 5 seconds.
if (lastCommandTimeout) {
clearTimeout(lastCommandTimeout);
}
lastCommandTimeout = setTimeout(() => {
lastCommand = null;
}, 5000);
const promise = $.ajax({
url: '/command',
method: 'POST',
data: JSON.stringify(payload)
});
promise.done((data) => {
dispatch(addCommand(data));
});
};
},
[types.UPDATE_COMMAND](payload) {
return (dispatch, getState) => {
dispatch(updateItem({ section: 'commands', ...payload }));
showCommandMessage(payload, dispatch);
scheduleRemoveCommand(payload, dispatch);
};
},
[types.FINISH_COMMAND](payload) {
return (dispatch, getState) => {
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(removeCommand({ section: 'commands', ...payload }));
showCommandMessage(payload, dispatch);
};
}
};
export default commandActionHandlers;

View file

@ -0,0 +1,14 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import commandActionHandlers from './commandActionHandlers';
export const fetchCommands = commandActionHandlers[types.FETCH_COMMANDS];
export const executeCommand = commandActionHandlers[types.EXECUTE_COMMAND];
export const updateCommand = commandActionHandlers[types.UPDATE_COMMAND];
export const finishCommand = commandActionHandlers[types.FINISH_COMMAND];
export const addCommand = createAction(types.ADD_COMMAND);
export const removeCommand = createAction(types.REMOVE_COMMAND);
export const registerFinishCommandHandler = createAction(types.REGISTER_FINISH_COMMAND_HANDLER);
export const unregisterFinishCommandHandler = createAction(types.UNREGISTER_FINISH_COMMAND_HANDLER);

View file

@ -0,0 +1,111 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import episodeEntities from 'Episode/episodeEntities';
import createFetchHandler from './Creators/createFetchHandler';
import * as types from './actionTypes';
import { updateItem } from './baseActions';
const section = 'episodes';
const episodeActionHandlers = {
[types.FETCH_EPISODES]: createFetchHandler(section, '/episode'),
[types.TOGGLE_EPISODE_MONITORED]: function(payload) {
return function(dispatch, getState) {
const {
episodeId: id,
episodeEntity = episodeEntities.EPISODES,
monitored
} = payload;
const episodeSection = _.last(episodeEntity.split('.'));
dispatch(updateItem({
id,
section: episodeSection,
isSaving: true
}));
const promise = $.ajax({
url: `/episode/${id}`,
method: 'PUT',
data: JSON.stringify({ monitored }),
dataType: 'json'
});
promise.done((data) => {
dispatch(updateItem({
id,
section: episodeSection,
isSaving: false,
monitored
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
id,
section: episodeSection,
isSaving: false
}));
});
};
},
[types.TOGGLE_EPISODES_MONITORED]: function(payload) {
return function(dispatch, getState) {
const {
episodeIds,
episodeEntity = episodeEntities.EPISODES,
monitored
} = payload;
const episodeSection = _.last(episodeEntity.split('.'));
dispatch(batchActions(
episodeIds.map((episodeId) => {
return updateItem({
id: episodeId,
section: episodeSection,
isSaving: true
});
})
));
const promise = $.ajax({
url: '/episode/monitor',
method: 'PUT',
data: JSON.stringify({ episodeIds, monitored }),
dataType: 'json'
});
promise.done((data) => {
dispatch(batchActions(
episodeIds.map((episodeId) => {
return updateItem({
id: episodeId,
section: episodeSection,
isSaving: false,
monitored
});
})
));
});
promise.fail((xhr) => {
dispatch(batchActions(
episodeIds.map((episodeId) => {
return updateItem({
id: episodeId,
section: episodeSection,
isSaving: false
});
})
));
});
};
}
};
export default episodeActionHandlers;

View file

@ -0,0 +1,10 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import episodeActionHandlers from './episodeActionHandlers';
export const fetchEpisodes = episodeActionHandlers[types.FETCH_EPISODES];
export const setEpisodesSort = createAction(types.SET_EPISODES_SORT);
export const setEpisodesTableOption = createAction(types.SET_EPISODES_TABLE_OPTION);
export const clearEpisodes = createAction(types.CLEAR_EPISODES);
export const toggleEpisodeMonitored = episodeActionHandlers[types.TOGGLE_EPISODE_MONITORED];
export const toggleEpisodesMonitored = episodeActionHandlers[types.TOGGLE_EPISODES_MONITORED];

View file

@ -0,0 +1,164 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import episodeEntities from 'Episode/episodeEntities';
import createFetchHandler from './Creators/createFetchHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import * as types from './actionTypes';
import { set, removeItem, updateItem } from './baseActions';
const section = 'episodeFiles';
const deleteEpisodeFile = createRemoveItemHandler(section, '/episodeFile');
const episodeFileActionHandlers = {
[types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/episodeFile'),
[types.DELETE_EPISODE_FILE]: function(payload) {
return function(dispatch, getState) {
const {
id: episodeFileId,
episodeEntity = episodeEntities.EPISODES
} = payload;
const episodeSection = _.last(episodeEntity.split('.'));
const deletePromise = deleteEpisodeFile(payload)(dispatch, getState);
deletePromise.done(() => {
const episodes = getState().episodes.items;
const episodesWithRemovedFiles = _.filter(episodes, { episodeFileId });
dispatch(batchActions([
...episodesWithRemovedFiles.map((episode) => {
return updateItem({
section: episodeSection,
...episode,
episodeFileId: 0,
hasFile: false
});
})
]));
});
};
},
[types.DELETE_EPISODE_FILES]: function(payload) {
return function(dispatch, getState) {
const {
episodeFileIds
} = payload;
dispatch(set({ section, isDeleting: true }));
const promise = $.ajax({
url: '/episodeFile/bulk',
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ episodeFileIds })
});
promise.done(() => {
const episodes = getState().episodes.items;
const episodesWithRemovedFiles = episodeFileIds.reduce((acc, episodeFileId) => {
acc.push(..._.filter(episodes, { episodeFileId }));
return acc;
}, []);
dispatch(batchActions([
...episodeFileIds.map((id) => {
return removeItem({ section, id });
}),
...episodesWithRemovedFiles.map((episode) => {
return updateItem({
section: 'episodes',
...episode,
episodeFileId: 0,
hasFile: false
});
}),
set({
section,
isDeleting: false,
deleteError: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
};
},
[types.UPDATE_EPISODE_FILES]: function(payload) {
return function(dispatch, getState) {
const {
episodeFileIds,
language,
quality
} = payload;
dispatch(set({ section, isSaving: true }));
const data = {
episodeFileIds
};
if (language) {
data.language = language;
}
if (quality) {
data.quality = quality;
}
const promise = $.ajax({
url: '/episodeFile/editor',
method: 'PUT',
dataType: 'json',
data: JSON.stringify(data)
});
promise.done(() => {
dispatch(batchActions([
...episodeFileIds.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
}));
});
};
}
};
export default episodeFileActionHandlers;

View file

@ -0,0 +1,9 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import episodeFileActionHandlers from './episodeFileActionHandlers';
export const fetchEpisodeFiles = episodeFileActionHandlers[types.FETCH_EPISODE_FILES];
export const deleteEpisodeFile = episodeFileActionHandlers[types.DELETE_EPISODE_FILE];
export const deleteEpisodeFiles = episodeFileActionHandlers[types.DELETE_EPISODE_FILES];
export const updateEpisodeFiles = episodeFileActionHandlers[types.UPDATE_EPISODE_FILES];
export const clearEpisodeFiles = createAction(types.CLEAR_EPISODE_FILES);

View file

@ -0,0 +1,75 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import { sortDirections } from 'Helpers/Props';
import * as types from './actionTypes';
import { set, update } from './baseActions';
import { fetchEpisodeHistory } from './episodeHistoryActions';
const episodeHistoryActionHandlers = {
[types.FETCH_EPISODE_HISTORY]: function(payload) {
const section = 'episodeHistory';
return function(dispatch, getState) {
dispatch(set({ section, isFetching: true }));
const queryParams = {
pageSize: 1000,
page: 1,
filterKey: 'episodeId',
filterValue: payload.episodeId,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING
};
const promise = $.ajax({
url: '/history',
data: queryParams
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data: data.records }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
};
},
[types.EPISODE_HISTORY_MARK_AS_FAILED]: function(payload) {
return function(dispatch, getState) {
const {
historyId,
episodeId
} = payload;
const promise = $.ajax({
url: '/history/failed',
method: 'POST',
data: {
id: historyId
}
});
promise.done(() => {
dispatch(fetchEpisodeHistory({ episodeId }));
});
};
}
};
export default episodeHistoryActionHandlers;

View file

@ -0,0 +1,7 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import episodeHistoryActionHandlers from './episodeHistoryActionHandlers';
export const fetchEpisodeHistory = episodeHistoryActionHandlers[types.FETCH_EPISODE_HISTORY];
export const clearEpisodeHistory = createAction(types.CLEAR_EPISODE_HISTORY);
export const episodeHistoryMarkAsFailed = episodeHistoryActionHandlers[types.EPISODE_HISTORY_MARK_AS_FAILED];

View file

@ -0,0 +1,60 @@
import $ from 'jquery';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import * as types from './actionTypes';
import { updateItem } from './baseActions';
const section = 'history';
const historyActionHandlers = {
...createServerSideCollectionHandlers('history', '/history', (state) => state.history, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_HISTORY,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_HISTORY_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_HISTORY_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_HISTORY_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_HISTORY_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_HISTORY_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_HISTORY_SORT,
[serverSideCollectionHandlers.FILTER]: types.SET_HISTORY_FILTER
}),
[types.MARK_AS_FAILED]: function(payload) {
return function(dispatch, getState) {
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
}));
});
};
}
};
export default historyActionHandlers;

View file

@ -0,0 +1,16 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import historyActionHandlers from './historyActionHandlers';
export const fetchHistory = historyActionHandlers[types.FETCH_HISTORY];
export const gotoHistoryFirstPage = historyActionHandlers[types.GOTO_FIRST_HISTORY_PAGE];
export const gotoHistoryPreviousPage = historyActionHandlers[types.GOTO_PREVIOUS_HISTORY_PAGE];
export const gotoHistoryNextPage = historyActionHandlers[types.GOTO_NEXT_HISTORY_PAGE];
export const gotoHistoryLastPage = historyActionHandlers[types.GOTO_LAST_HISTORY_PAGE];
export const gotoHistoryPage = historyActionHandlers[types.GOTO_HISTORY_PAGE];
export const setHistorySort = historyActionHandlers[types.SET_HISTORY_SORT];
export const setHistoryFilter = historyActionHandlers[types.SET_HISTORY_FILTER];
export const setHistoryTableOption = createAction(types.SET_HISTORY_TABLE_OPTION);
export const clearHistory = createAction(types.CLEAR_HISTORY);
export const markAsFailed = historyActionHandlers[types.MARK_AS_FAILED];

View file

@ -0,0 +1,172 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import getNewSeries from 'Utilities/Series/getNewSeries';
import * as types from './actionTypes';
import { set, updateItem, removeItem } from './baseActions';
import { startLookupSeries } from './importSeriesActions';
import { fetchRootFolders } from './rootFolderActions';
const section = 'importSeries';
let concurrentLookups = 0;
const importSeriesActionHandlers = {
[types.QUEUE_LOOKUP_SERIES]: function(payload) {
return function(dispatch, getState) {
const {
name,
path,
term
} = payload;
const state = getState().importSeries;
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: []
}));
if (term && term.length > 2) {
dispatch(startLookupSeries());
}
};
},
[types.START_LOOKUP_SERIES]: function(payload) {
return function(dispatch, getState) {
if (concurrentLookups >= 1) {
return;
}
const state = getState().importSeries;
const queued = _.find(state.items, { queued: true });
if (!queued) {
return;
}
concurrentLookups++;
dispatch(updateItem({
section,
id: queued.id,
isFetching: true
}));
const promise = $.ajax({
url: '/series/lookup',
data: {
term: queued.term
}
});
promise.done((data) => {
dispatch(updateItem({
section,
id: queued.id,
isFetching: false,
isPopulated: true,
error: null,
items: data,
queued: false,
selectedSeries: queued.selectedSeries || data[0]
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
section,
id: queued.id,
isFetching: false,
isPopulated: false,
error: xhr,
queued: false
}));
});
promise.always(() => {
concurrentLookups--;
dispatch(startLookupSeries());
});
};
},
[types.IMPORT_SERIES]: function(payload) {
return function(dispatch, getState) {
dispatch(set({ section, isImporting: true }));
const ids = payload.ids;
const items = getState().importSeries.items;
const addedIds = [];
const allNewSeries = ids.reduce((acc, id) => {
const item = _.find(items, { id });
const selectedSeries = item.selectedSeries;
// Make sure we have a selected series and
// the same series hasn't been added yet.
if (selectedSeries && !_.some(acc, { tvdbId: selectedSeries.tvdbId })) {
const newSeries = getNewSeries(_.cloneDeep(selectedSeries), item);
newSeries.path = item.path;
addedIds.push(id);
acc.push(newSeries);
}
return acc;
}, []);
const promise = $.ajax({
url: '/series/import',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(allNewSeries)
});
promise.done((data) => {
dispatch(batchActions([
set({
section,
isImporting: false,
isImported: true
}),
...data.map((series) => updateItem({ section: 'series', ...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
}))
));
});
};
}
};
export default importSeriesActionHandlers;

View file

@ -0,0 +1,16 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import importSeriesActionHandlers from './importSeriesActionHandlers';
export const queueLookupSeries = importSeriesActionHandlers[types.QUEUE_LOOKUP_SERIES];
export const startLookupSeries = importSeriesActionHandlers[types.START_LOOKUP_SERIES];
export const importSeries = importSeriesActionHandlers[types.IMPORT_SERIES];
export const clearImportSeries = createAction(types.CLEAR_IMPORT_SERIES);
export const setImportSeriesValue = createAction(types.SET_IMPORT_SERIES_VALUE, (payload) => {
return {
section: 'importSeries',
...payload
};
});

View file

@ -0,0 +1,48 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes';
import { set, update } from './baseActions';
const section = 'interactiveImport';
const interactiveImportActionHandlers = {
[types.FETCH_INTERACTIVE_IMPORT_ITEMS]: function(payload) {
return function(dispatch, getState) {
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
}));
});
};
}
};
export default interactiveImportActionHandlers;

View file

@ -0,0 +1,11 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import interactiveImportActionHandlers from './interactiveImportActionHandlers';
export const fetchInteractiveImportItems = interactiveImportActionHandlers[types.FETCH_INTERACTIVE_IMPORT_ITEMS];
export const setInteractiveImportSort = createAction(types.SET_INTERACTIVE_IMPORT_SORT);
export const updateInteractiveImportItem = createAction(types.UPDATE_INTERACTIVE_IMPORT_ITEM);
export const clearInteractiveImport = createAction(types.CLEAR_INTERACTIVE_IMPORT);
export const addRecentFolder = createAction(types.ADD_RECENT_FOLDER);
export const removeRecentFolder = createAction(types.REMOVE_RECENT_FOLDER);
export const setInteractiveImportMode = createAction(types.SET_INTERACTIVE_IMPORT_MODE);

View file

@ -0,0 +1,80 @@
import _ from 'lodash';
import $ from 'jquery';
import requestAction from 'Utilities/requestAction';
import * as types from './actionTypes';
import { setOAuthValue } from './oAuthActions';
function showOAuthWindow(url) {
const deferred = $.Deferred();
const selfWindow = window;
window.open(url);
selfWindow.onCompleteOauth = function(query, callback) {
delete selfWindow.onCompleteOauth;
const queryParams = {};
const splitQuery = query.substring(1).split('&');
_.each(splitQuery, (param) => {
const paramSplit = param.split('=');
queryParams[paramSplit[0]] = paramSplit[1];
});
callback();
deferred.resolve(queryParams);
};
return deferred.promise();
}
const oAuthActionHandlers = {
[types.START_OAUTH]: function(payload) {
return (dispatch, getState) => {
const actionPayload = {
action: 'startOAuth',
queryParams: { callbackUrl: `${window.location.origin}/oauth.html` },
...payload
};
dispatch(setOAuthValue({
authorizing: true
}));
const promise = requestAction(actionPayload)
.then((response) => {
return showOAuthWindow(response.oauthUrl);
})
.then((queryParams) => {
return requestAction({
action: 'getOAuthToken',
queryParams,
...payload
});
})
.then((response) => {
const {
accessToken,
accessTokenSecret
} = response;
dispatch(setOAuthValue({
authorizing: false,
accessToken,
accessTokenSecret
}));
});
promise.fail(() => {
dispatch(setOAuthValue({
authorizing: false
}));
});
};
}
};
export default oAuthActionHandlers;

View file

@ -0,0 +1,7 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import oAuthActionHandlers from './oAuthActionHandlers';
export const startOAuth = oAuthActionHandlers[types.START_OAUTH];
export const setOAuthValue = createAction(types.SET_OAUTH_VALUE);
export const resetOAuth = createAction(types.RESET_OAUTH);

View file

@ -0,0 +1,8 @@
import createFetchHandler from './Creators/createFetchHandler';
import * as types from './actionTypes';
const organizePreviewActionHandlers = {
[types.FETCH_ORGANIZE_PREVIEW]: createFetchHandler('organizePreview', '/rename')
};
export default organizePreviewActionHandlers;

View file

@ -0,0 +1,6 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import organizePreviewActionHandlers from './organizePreviewActionHandlers';
export const fetchOrganizePreview = organizePreviewActionHandlers[types.FETCH_ORGANIZE_PREVIEW];
export const clearOrganizePreview = createAction(types.CLEAR_ORGANIZE_PREVIEW);

View file

@ -0,0 +1,43 @@
import $ from 'jquery';
import * as types from './actionTypes';
import { set } from './baseActions';
import { updatePaths } from './pathActions';
const section = 'paths';
const pathActionHandlers = {
[types.FETCH_PATHS](payload) {
return (dispatch, getState) => {
dispatch(set({ section, isFetching: true }));
const promise = $.ajax({
url: '/filesystem',
data: {
path: payload.path
}
});
promise.done((data) => {
dispatch(updatePaths({ path: payload.path, ...data }));
dispatch(set({
section,
isFetching: false,
isPopulated: true,
error: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
};
}
};
export default pathActionHandlers;

View file

@ -0,0 +1,7 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import pathActionHandlers from './pathActionHandlers';
export const fetchPaths = pathActionHandlers[types.FETCH_PATHS];
export const updatePaths = createAction(types.UPDATE_PATHS);
export const clearPaths = createAction(types.CLEAR_PATHS);

View file

@ -0,0 +1,230 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import createFetchHandler from './Creators/createFetchHandler';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import * as types from './actionTypes';
import { set, updateItem } from './baseActions';
import { fetchQueue } from './queueActions';
const fetchQueueDetailsHandler = createFetchHandler('details', '/queue/details');
const queueActionHandlers = {
[types.FETCH_QUEUE_STATUS]: createFetchHandler('queueStatus', '/queue/status'),
[types.FETCH_QUEUE_DETAILS]: function(payload) {
return function(dispatch, getState) {
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)) {
const fetchFunction = fetchQueueDetailsHandler(params);
fetchFunction(dispatch, getState);
}
};
},
...createServerSideCollectionHandlers('paged', '/queue', (state) => state.queue, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_QUEUE,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_QUEUE_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_QUEUE_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_QUEUE_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_QUEUE_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_QUEUE_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_QUEUE_SORT
}),
[types.GRAB_QUEUE_ITEM]: function(payload) {
const section = 'paged';
const {
id
} = payload;
return function(dispatch, getState) {
dispatch(updateItem({ section, id, isGrabbing: true }));
const promise = $.ajax({
url: `/queue/grab/${id}`,
method: 'POST'
});
promise.done((data) => {
dispatch(batchActions([
dispatch(fetchQueue()),
set({
section,
isGrabbing: false,
grabError: null
})
]));
});
promise.fail((xhr) => {
dispatch(updateItem({
section,
id,
isGrabbing: false,
grabError: xhr
}));
});
};
},
[types.GRAB_QUEUE_ITEMS]: function(payload) {
const section = 'paged';
const {
ids
} = payload;
return function(dispatch, getState) {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isGrabbing: true
});
}),
set({
section,
isGrabbing: true
})
]));
const promise = $.ajax({
url: '/queue/grab/bulk',
method: 'POST',
dataType: 'json',
data: JSON.stringify(payload)
});
promise.done((data) => {
dispatch(batchActions([
dispatch(fetchQueue()),
...ids.map((id) => {
return updateItem({
section,
id,
isGrabbing: false,
grabError: null
});
}),
set({
section,
isGrabbing: false,
grabError: null
})
]));
});
promise.fail((xhr) => {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isGrabbing: false,
grabError: null
});
}),
set({ section, isGrabbing: false })
]));
});
};
},
[types.REMOVE_QUEUE_ITEM]: function(payload) {
const section = 'paged';
const {
id,
blacklist
} = payload;
return function(dispatch, getState) {
dispatch(updateItem({ section, id, isRemoving: true }));
const promise = $.ajax({
url: `/queue/${id}?blacklist=${blacklist}`,
method: 'DELETE'
});
promise.done((data) => {
dispatch(fetchQueue());
});
promise.fail((xhr) => {
dispatch(updateItem({ section, id, isRemoving: false }));
});
};
},
[types.REMOVE_QUEUE_ITEMS]: function(payload) {
const section = 'paged';
const {
ids,
blacklist
} = payload;
return function(dispatch, getState) {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isRemoving: true
});
}),
set({ section, isRemoving: true })
]));
const promise = $.ajax({
url: `/queue/bulk?blacklist=${blacklist}`,
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ ids })
});
promise.done((data) => {
dispatch(fetchQueue());
});
promise.fail((xhr) => {
dispatch(batchActions([
...ids.map((id) => {
return updateItem({
section,
id,
isRemoving: false
});
}),
set({ section, isRemoving: false })
]));
});
};
}
};
export default queueActionHandlers;

View file

@ -0,0 +1,24 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import queueActionHandlers from './queueActionHandlers';
export const fetchQueueStatus = queueActionHandlers[types.FETCH_QUEUE_STATUS];
export const fetchQueueDetails = queueActionHandlers[types.FETCH_QUEUE_DETAILS];
export const clearQueueDetails = createAction(types.CLEAR_QUEUE_DETAILS);
export const fetchQueue = queueActionHandlers[types.FETCH_QUEUE];
export const gotoQueueFirstPage = queueActionHandlers[types.GOTO_FIRST_QUEUE_PAGE];
export const gotoQueuePreviousPage = queueActionHandlers[types.GOTO_PREVIOUS_QUEUE_PAGE];
export const gotoQueueNextPage = queueActionHandlers[types.GOTO_NEXT_QUEUE_PAGE];
export const gotoQueueLastPage = queueActionHandlers[types.GOTO_LAST_QUEUE_PAGE];
export const gotoQueuePage = queueActionHandlers[types.GOTO_QUEUE_PAGE];
export const setQueueSort = queueActionHandlers[types.SET_QUEUE_SORT];
export const setQueueTableOption = createAction(types.SET_QUEUE_TABLE_OPTION);
export const clearQueue = createAction(types.CLEAR_QUEUE);
export const setQueueEpisodes = createAction(types.SET_QUEUE_EPISODES);
export const grabQueueItem = queueActionHandlers[types.GRAB_QUEUE_ITEM];
export const grabQueueItems = queueActionHandlers[types.GRAB_QUEUE_ITEMS];
export const removeQueueItem = queueActionHandlers[types.REMOVE_QUEUE_ITEM];
export const removeQueueItems = queueActionHandlers[types.REMOVE_QUEUE_ITEMS];

View file

@ -0,0 +1,47 @@
import $ from 'jquery';
import createFetchHandler from './Creators/createFetchHandler';
import * as types from './actionTypes';
import { updateRelease } from './releaseActions';
const section = 'releases';
const releaseActionHandlers = {
[types.FETCH_RELEASES]: createFetchHandler(section, '/release'),
[types.GRAB_RELEASE]: function(payload) {
return function(dispatch, getState) {
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
}));
});
};
}
};
export default releaseActionHandlers;

View file

@ -0,0 +1,9 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import releaseActionHandlers from './releaseActionHandlers';
export const fetchReleases = releaseActionHandlers[types.FETCH_RELEASES];
export const setReleasesSort = createAction(types.SET_RELEASES_SORT);
export const clearReleases = createAction(types.CLEAR_RELEASES);
export const grabRelease = releaseActionHandlers[types.GRAB_RELEASE];
export const updateRelease = createAction(types.UPDATE_RELEASE);

View file

@ -0,0 +1,59 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes';
import createFetchHandler from './Creators/createFetchHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import { set, updateItem } from './baseActions';
const section = 'rootFolders';
const rootFolderActionHandlers = {
[types.FETCH_ROOT_FOLDERS]: createFetchHandler('rootFolders', '/rootFolder'),
[types.DELETE_ROOT_FOLDER]: createRemoveItemHandler('rootFolders',
'/rootFolder',
(state) => state.rootFolders),
[types.ADD_ROOT_FOLDER]: function(payload) {
return function(dispatch, getState) {
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
}));
});
};
}
};
export default rootFolderActionHandlers;

View file

@ -0,0 +1,6 @@
import * as types from './actionTypes';
import rootFolderActionHandlers from './rootFolderActionHandlers';
export const fetchRootFolders = rootFolderActionHandlers[types.FETCH_ROOT_FOLDERS];
export const addRootFolder = rootFolderActionHandlers[types.ADD_ROOT_FOLDER];
export const deleteRootFolder = rootFolderActionHandlers[types.DELETE_ROOT_FOLDER];

View file

@ -0,0 +1,83 @@
import _ from 'lodash';
import $ from 'jquery';
import getMonitoringOptions from 'Utilities/Series/getMonitoringOptions';
import * as types from './actionTypes';
import { set } from './baseActions';
import { fetchArtist } from './seriesActions';
const section = 'seasonPass';
const seasonPassActionHandlers = {
[types.SAVE_SEASON_PASS]: function(payload) {
return function(dispatch, getState) {
const {
artistIds,
monitored,
monitor
} = payload;
let monitoringOptions = null;
const series = [];
const allSeries = getState().series.items;
artistIds.forEach((id) => {
const s = _.find(allSeries, { id });
const seriesToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
seriesToUpdate.monitored = monitored;
}
if (monitor) {
const {
seasons,
options: seriesMonitoringOptions
} = getMonitoringOptions(_.cloneDeep(s.seasons), monitor);
if (!monitoringOptions) {
monitoringOptions = seriesMonitoringOptions;
}
seriesToUpdate.seasons = seasons;
}
series.push(seriesToUpdate);
});
dispatch(set({
section,
isSaving: true
}));
const promise = $.ajax({
url: '/seasonPass',
method: 'POST',
data: JSON.stringify({
series,
monitoringOptions
}),
dataType: 'json'
});
promise.done((data) => {
dispatch(fetchArtist());
dispatch(set({
section,
isSaving: false,
saveError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
};
}
};
export default seasonPassActionHandlers;

View file

@ -0,0 +1,7 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import seasonPassActionHandlers from './seasonPassActionHandlers';
export const setSeasonPassSort = createAction(types.SET_SEASON_PASS_SORT);
export const setSeasonPassFilter = createAction(types.SET_SEASON_PASS_FILTER);
export const saveSeasonPass = seasonPassActionHandlers[types.SAVE_SEASON_PASS];

View file

@ -0,0 +1,16 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import artistActionHandlers from './artistActionHandlers';
export const fetchArtist = artistActionHandlers[types.FETCH_ARTIST];
export const saveArtist = artistActionHandlers[types.SAVE_ARTIST];
export const deleteArtist = artistActionHandlers[types.DELETE_ARTIST];
export const toggleSeriesMonitored = artistActionHandlers[types.TOGGLE_ARTIST_MONITORED];
export const toggleSeasonMonitored = artistActionHandlers[types.TOGGLE_ALBUM_MONITORED];
export const setSeriesValue = createAction(types.SET_ARTIST_VALUE, (payload) => {
return {
section: 'series',
...payload
};
});

View file

@ -0,0 +1,86 @@
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes';
import { set, updateItem } from './baseActions';
const section = 'seriesEditor';
const seriesEditorActionHandlers = {
[types.SAVE_ARTIST_EDITOR]: function(payload) {
return function(dispatch, getState) {
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
}));
});
};
},
[types.BULK_DELETE_ARTIST]: function(payload) {
return function(dispatch, getState) {
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 serires from the collection
dispatch(set({
section,
isDeleting: false,
deleteError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isDeleting: false,
deleteError: xhr
}));
});
};
}
};
export default seriesEditorActionHandlers;

View file

@ -0,0 +1,8 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import seriesEditorActionHandlers from './seriesEditorActionHandlers';
export const setSeriesEditorSort = createAction(types.SET_SERIES_EDITOR_SORT);
export const setSeriesEditorFilter = createAction(types.SET_SERIES_EDITOR_FILTER);
export const saveArtistEditor = seriesEditorActionHandlers[types.SAVE_ARTIST_EDITOR];
export const bulkDeleteArtist = seriesEditorActionHandlers[types.BULK_DELETE_ARTIST];

View file

@ -0,0 +1,238 @@
import _ from 'lodash';
import $ from 'jquery';
import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes';
import createFetchHandler from './Creators/createFetchHandler';
import createFetchSchemaHandler from './Creators/createFetchSchemaHandler';
import createSaveHandler from './Creators/createSaveHandler';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
import createTestProviderHandler from './Creators/createTestProviderHandler';
import { set, update, clearPendingChanges } from './baseActions';
const settingsActionHandlers = {
[types.FETCH_UI_SETTINGS]: createFetchHandler('ui', '/config/ui'),
[types.SAVE_UI_SETTINGS]: createSaveHandler('ui', '/config/ui', (state) => state.settings.ui),
[types.FETCH_MEDIA_MANAGEMENT_SETTINGS]: createFetchHandler('mediaManagement', '/config/mediamanagement'),
[types.SAVE_MEDIA_MANAGEMENT_SETTINGS]: createSaveHandler('mediaManagement', '/config/mediamanagement', (state) => state.settings.mediaManagement),
[types.FETCH_NAMING_SETTINGS]: createFetchHandler('naming', '/config/naming'),
[types.SAVE_NAMING_SETTINGS]: createSaveHandler('naming', '/config/naming', (state) => state.settings.naming),
[types.FETCH_NAMING_EXAMPLES]: function(payload) {
const section = 'namingExamples';
return function(dispatch, getState) {
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
}));
});
};
},
[types.REORDER_DELAY_PROFILE]: function(payload) {
const section = 'delayProfiles';
return function(dispatch, getState) {
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 }));
});
};
},
[types.FETCH_QUALITY_PROFILES]: createFetchHandler('qualityProfiles', '/qualityprofile'),
[types.FETCH_QUALITY_PROFILE_SCHEMA]: createFetchSchemaHandler('qualityProfiles', '/qualityprofile/schema'),
[types.SAVE_QUALITY_PROFILE]: createSaveProviderHandler('qualityProfiles',
'/qualityprofile',
(state) => state.settings.qualityProfiles),
[types.DELETE_QUALITY_PROFILE]: createRemoveItemHandler('qualityProfiles',
'/qualityprofile',
(state) => state.settings.qualityProfiles),
[types.FETCH_LANGUAGE_PROFILES]: createFetchHandler('languageProfiles', '/languageprofile'),
[types.FETCH_LANGUAGE_PROFILE_SCHEMA]: createFetchSchemaHandler('languageProfiles', '/languageprofile/schema'),
[types.SAVE_LANGUAGE_PROFILE]: createSaveProviderHandler('languageProfiles',
'/languageprofile',
(state) => state.settings.languageProfiles),
[types.DELETE_LANGUAGE_PROFILE]: createRemoveItemHandler('languageProfiles',
'/languageprofile',
(state) => state.settings.languageProfiles),
[types.FETCH_DELAY_PROFILES]: createFetchHandler('delayProfiles', '/delayprofile'),
[types.SAVE_DELAY_PROFILE]: createSaveProviderHandler('delayProfiles',
'/delayprofile',
(state) => state.settings.delayProfiles),
[types.DELETE_DELAY_PROFILE]: createRemoveItemHandler('delayProfiles',
'/delayprofile',
(state) => state.settings.delayProfiles),
[types.FETCH_QUALITY_DEFINITIONS]: createFetchHandler('qualityDefinitions', '/qualitydefinition'),
[types.SAVE_QUALITY_DEFINITIONS]: createSaveHandler('qualityDefinitions', '/qualitydefinition', (state) => state.settings.qualitydefinitions),
[types.SAVE_QUALITY_DEFINITIONS]: function() {
const section = 'qualityDefinitions';
return function(dispatch, getState) {
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;
}
const promise = $.ajax({
method: 'PUT',
url: '/qualityDefinition/update',
data: JSON.stringify(upatedDefinitions)
});
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
clearPendingChanges({ section: 'qualityDefinitions' })
]));
});
};
},
[types.FETCH_INDEXERS]: createFetchHandler('indexers', '/indexer'),
[types.FETCH_INDEXER_SCHEMA]: createFetchSchemaHandler('indexers', '/indexer/schema'),
[types.SAVE_INDEXER]: createSaveProviderHandler('indexers',
'/indexer',
(state) => state.settings.indexers),
[types.DELETE_INDEXER]: createRemoveItemHandler('indexers',
'/indexer',
(state) => state.settings.indexers),
[types.TEST_INDEXER]: createTestProviderHandler('indexers',
'/indexer',
(state) => state.settings.indexers),
[types.FETCH_INDEXER_OPTIONS]: createFetchHandler('indexerOptions', '/config/indexer'),
[types.SAVE_INDEXER_OPTIONS]: createSaveHandler('indexerOptions', '/config/indexer', (state) => state.settings.indexerOptions),
[types.FETCH_RESTRICTIONS]: createFetchHandler('restrictions', '/restriction'),
[types.SAVE_RESTRICTION]: createSaveProviderHandler('restrictions',
'/restriction',
(state) => state.settings.restrictions),
[types.DELETE_RESTRICTION]: createRemoveItemHandler('restrictions',
'/restriction',
(state) => state.settings.restrictions),
[types.FETCH_DOWNLOAD_CLIENTS]: createFetchHandler('downloadClients', '/downloadclient'),
[types.FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler('downloadClients', '/downloadclient/schema'),
[types.SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler('downloadClients',
'/downloadclient',
(state) => state.settings.downloadClients),
[types.DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler('downloadClients',
'/downloadclient',
(state) => state.settings.downloadClients),
[types.TEST_DOWNLOAD_CLIENT]: createTestProviderHandler('downloadClients',
'/downloadclient',
(state) => state.settings.downloadClients),
[types.FETCH_DOWNLOAD_CLIENT_OPTIONS]: createFetchHandler('downloadClientOptions', '/config/downloadclient'),
[types.SAVE_DOWNLOAD_CLIENT_OPTIONS]: createSaveHandler('downloadClientOptions', '/config/downloadclient', (state) => state.settings.downloadClientOptions),
[types.FETCH_REMOTE_PATH_MAPPINGS]: createFetchHandler('remotePathMappings', '/remotepathmapping'),
[types.SAVE_REMOTE_PATH_MAPPING]: createSaveProviderHandler('remotePathMappings',
'/remotepathmapping',
(state) => state.settings.remotePathMappings),
[types.DELETE_REMOTE_PATH_MAPPING]: createRemoveItemHandler('remotePathMappings',
'/remotepathmapping',
(state) => state.settings.remotePathMappings),
[types.FETCH_NOTIFICATIONS]: createFetchHandler('notifications', '/notification'),
[types.FETCH_NOTIFICATION_SCHEMA]: createFetchSchemaHandler('notifications', '/notification/schema'),
[types.SAVE_NOTIFICATION]: createSaveProviderHandler('notifications',
'/notification',
(state) => state.settings.notifications),
[types.DELETE_NOTIFICATION]: createRemoveItemHandler('notifications',
'/notification',
(state) => state.settings.notifications),
[types.TEST_NOTIFICATION]: createTestProviderHandler('notifications',
'/notification',
(state) => state.settings.notifications),
[types.FETCH_METADATA]: createFetchHandler('metadata', '/metadata'),
[types.SAVE_METADATA]: createSaveProviderHandler('metadata',
'/metadata',
(state) => state.settings.metadata),
[types.FETCH_GENERAL_SETTINGS]: createFetchHandler('general', '/config/host'),
[types.SAVE_GENERAL_SETTINGS]: createSaveHandler('general', '/config/host', (state) => state.settings.general)
};
export default settingsActionHandlers;

View file

@ -0,0 +1,207 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import settingsActionHandlers from './settingsActionHandlers';
export const toggleAdvancedSettings = createAction(types.TOGGLE_ADVANCED_SETTINGS);
export const fetchUISettings = settingsActionHandlers[types.FETCH_UI_SETTINGS];
export const saveUISettings = settingsActionHandlers[types.SAVE_UI_SETTINGS];
export const setUISettingsValue = createAction(types.SET_UI_SETTINGS_VALUE, (payload) => {
return {
section: 'ui',
...payload
};
});
export const fetchMediaManagementSettings = settingsActionHandlers[types.FETCH_MEDIA_MANAGEMENT_SETTINGS];
export const saveMediaManagementSettings = settingsActionHandlers[types.SAVE_MEDIA_MANAGEMENT_SETTINGS];
export const setMediaManagementSettingsValue = createAction(types.SET_MEDIA_MANAGEMENT_SETTINGS_VALUE, (payload) => {
return {
section: 'mediaManagement',
...payload
};
});
export const fetchNamingSettings = settingsActionHandlers[types.FETCH_NAMING_SETTINGS];
export const saveNamingSettings = settingsActionHandlers[types.SAVE_NAMING_SETTINGS];
export const setNamingSettingsValue = createAction(types.SET_NAMING_SETTINGS_VALUE, (payload) => {
return {
section: 'naming',
...payload
};
});
export const fetchNamingExamples = settingsActionHandlers[types.FETCH_NAMING_EXAMPLES];
export const fetchQualityProfiles = settingsActionHandlers[types.FETCH_QUALITY_PROFILES];
export const fetchQualityProfileSchema = settingsActionHandlers[types.FETCH_QUALITY_PROFILE_SCHEMA];
export const saveQualityProfile = settingsActionHandlers[types.SAVE_QUALITY_PROFILE];
export const deleteQualityProfile = settingsActionHandlers[types.DELETE_QUALITY_PROFILE];
export const setQualityProfileValue = createAction(types.SET_QUALITY_PROFILE_VALUE, (payload) => {
return {
section: 'qualityProfiles',
...payload
};
});
export const fetchLanguageProfiles = settingsActionHandlers[types.FETCH_LANGUAGE_PROFILES];
export const fetchLanguageProfileSchema = settingsActionHandlers[types.FETCH_LANGUAGE_PROFILE_SCHEMA];
export const saveLanguageProfile = settingsActionHandlers[types.SAVE_LANGUAGE_PROFILE];
export const deleteLanguageProfile = settingsActionHandlers[types.DELETE_LANGUAGE_PROFILE];
export const setLanguageProfileValue = createAction(types.SET_LANGUAGE_PROFILE_VALUE, (payload) => {
return {
section: 'languageProfiles',
...payload
};
});
export const fetchDelayProfiles = settingsActionHandlers[types.FETCH_DELAY_PROFILES];
export const saveDelayProfile = settingsActionHandlers[types.SAVE_DELAY_PROFILE];
export const deleteDelayProfile = settingsActionHandlers[types.DELETE_DELAY_PROFILE];
export const reorderDelayProfile = settingsActionHandlers[types.REORDER_DELAY_PROFILE];
export const setDelayProfileValue = createAction(types.SET_DELAY_PROFILE_VALUE, (payload) => {
return {
section: 'delayProfiles',
...payload
};
});
export const fetchQualityDefinitions = settingsActionHandlers[types.FETCH_QUALITY_DEFINITIONS];
export const saveQualityDefinitions = settingsActionHandlers[types.SAVE_QUALITY_DEFINITIONS];
export const setQualityDefinitionValue = createAction(types.SET_QUALITY_DEFINITION_VALUE);
export const fetchIndexers = settingsActionHandlers[types.FETCH_INDEXERS];
export const fetchIndexerSchema = settingsActionHandlers[types.FETCH_INDEXER_SCHEMA];
export const selectIndexerSchema = createAction(types.SELECT_INDEXER_SCHEMA);
export const saveIndexer = settingsActionHandlers[types.SAVE_INDEXER];
export const deleteIndexer = settingsActionHandlers[types.DELETE_INDEXER];
export const testIndexer = settingsActionHandlers[types.TEST_INDEXER];
export const setIndexerValue = createAction(types.SET_INDEXER_VALUE, (payload) => {
return {
section: 'indexers',
...payload
};
});
export const setIndexerFieldValue = createAction(types.SET_INDEXER_FIELD_VALUE, (payload) => {
return {
section: 'indexers',
...payload
};
});
export const fetchIndexerOptions = settingsActionHandlers[types.FETCH_INDEXER_OPTIONS];
export const saveIndexerOptions = settingsActionHandlers[types.SAVE_INDEXER_OPTIONS];
export const setIndexerOptionsValue = createAction(types.SET_INDEXER_OPTIONS_VALUE, (payload) => {
return {
section: 'indexerOptions',
...payload
};
});
export const fetchRestrictions = settingsActionHandlers[types.FETCH_RESTRICTIONS];
export const saveRestriction = settingsActionHandlers[types.SAVE_RESTRICTION];
export const deleteRestriction = settingsActionHandlers[types.DELETE_RESTRICTION];
export const setRestrictionValue = createAction(types.SET_RESTRICTION_VALUE, (payload) => {
return {
section: 'restrictions',
...payload
};
});
export const fetchDownloadClients = settingsActionHandlers[types.FETCH_DOWNLOAD_CLIENTS];
export const fetchDownloadClientSchema = settingsActionHandlers[types.FETCH_DOWNLOAD_CLIENT_SCHEMA];
export const selectDownloadClientSchema = createAction(types.SELECT_DOWNLOAD_CLIENT_SCHEMA);
export const saveDownloadClient = settingsActionHandlers[types.SAVE_DOWNLOAD_CLIENT];
export const deleteDownloadClient = settingsActionHandlers[types.DELETE_DOWNLOAD_CLIENT];
export const testDownloadClient = settingsActionHandlers[types.TEST_DOWNLOAD_CLIENT];
export const setDownloadClientValue = createAction(types.SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return {
section: 'downloadClients',
...payload
};
});
export const setDownloadClientFieldValue = createAction(types.SET_DOWNLOAD_CLIENT_FIELD_VALUE, (payload) => {
return {
section: 'downloadClients',
...payload
};
});
export const fetchDownloadClientOptions = settingsActionHandlers[types.FETCH_DOWNLOAD_CLIENT_OPTIONS];
export const saveDownloadClientOptions = settingsActionHandlers[types.SAVE_DOWNLOAD_CLIENT_OPTIONS];
export const setDownloadClientOptionsValue = createAction(types.SET_DOWNLOAD_CLIENT_OPTIONS_VALUE, (payload) => {
return {
section: 'downloadClientOptions',
...payload
};
});
export const fetchRemotePathMappings = settingsActionHandlers[types.FETCH_REMOTE_PATH_MAPPINGS];
export const saveRemotePathMapping = settingsActionHandlers[types.SAVE_REMOTE_PATH_MAPPING];
export const deleteRemotePathMapping = settingsActionHandlers[types.DELETE_REMOTE_PATH_MAPPING];
export const setRemotePathMappingValue = createAction(types.SET_REMOTE_PATH_MAPPING_VALUE, (payload) => {
return {
section: 'remotePathMappings',
...payload
};
});
export const fetchNotifications = settingsActionHandlers[types.FETCH_NOTIFICATIONS];
export const fetchNotificationSchema = settingsActionHandlers[types.FETCH_NOTIFICATION_SCHEMA];
export const selectNotificationSchema = createAction(types.SELECT_NOTIFICATION_SCHEMA);
export const saveNotification = settingsActionHandlers[types.SAVE_NOTIFICATION];
export const deleteNotification = settingsActionHandlers[types.DELETE_NOTIFICATION];
export const testNotification = settingsActionHandlers[types.TEST_NOTIFICATION];
export const setNotificationValue = createAction(types.SET_NOTIFICATION_VALUE, (payload) => {
return {
section: 'notifications',
...payload
};
});
export const setNotificationFieldValue = createAction(types.SET_NOTIFICATION_FIELD_VALUE, (payload) => {
return {
section: 'notifications',
...payload
};
});
export const fetchMetadata = settingsActionHandlers[types.FETCH_METADATA];
export const saveMetadata = settingsActionHandlers[types.SAVE_METADATA];
export const setMetadataValue = createAction(types.SET_METADATA_VALUE, (payload) => {
return {
section: 'metadata',
...payload
};
});
export const setMetadataFieldValue = createAction(types.SET_METADATA_FIELD_VALUE, (payload) => {
return {
section: 'metadata',
...payload
};
});
export const fetchGeneralSettings = settingsActionHandlers[types.FETCH_GENERAL_SETTINGS];
export const saveGeneralSettings = settingsActionHandlers[types.SAVE_GENERAL_SETTINGS];
export const setGeneralSettingsValue = createAction(types.SET_GENERAL_SETTINGS_VALUE, (payload) => {
return {
section: 'general',
...payload
};
});

View file

@ -0,0 +1,48 @@
import $ from 'jquery';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import * as types from './actionTypes';
import createFetchHandler from './Creators/createFetchHandler';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
const systemActionHandlers = {
[types.FETCH_STATUS]: createFetchHandler('status', '/system/status'),
[types.FETCH_HEALTH]: createFetchHandler('health', '/health'),
[types.FETCH_DISK_SPACE]: createFetchHandler('diskSpace', '/diskspace'),
[types.FETCH_TASK]: createFetchHandler('tasks', '/system/task'),
[types.FETCH_TASKS]: createFetchHandler('tasks', '/system/task'),
[types.FETCH_BACKUPS]: createFetchHandler('backups', '/system/backup'),
[types.FETCH_UPDATES]: createFetchHandler('updates', '/update'),
[types.FETCH_LOG_FILES]: createFetchHandler('logFiles', '/log/file'),
[types.FETCH_UPDATE_LOG_FILES]: createFetchHandler('updateLogFiles', '/log/file/update'),
...createServerSideCollectionHandlers('logs', '/log', (state) => state.system, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_LOGS,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_LOGS_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_LOGS_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_LOGS_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_LOGS_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_LOGS_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_LOGS_SORT,
[serverSideCollectionHandlers.FILTER]: types.SET_LOGS_FILTER
}),
[types.RESTART]: function() {
return function() {
$.ajax({
url: '/system/restart',
method: 'POST'
});
};
},
[types.SHUTDOWN]: function() {
return function() {
$.ajax({
url: '/system/shutdown',
method: 'POST'
});
};
}
};
export default systemActionHandlers;

View file

@ -0,0 +1,28 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import systemActionHandlers from './systemActionHandlers';
export const fetchStatus = systemActionHandlers[types.FETCH_STATUS];
export const fetchHealth = systemActionHandlers[types.FETCH_HEALTH];
export const fetchDiskSpace = systemActionHandlers[types.FETCH_DISK_SPACE];
export const fetchTask = systemActionHandlers[types.FETCH_TASK];
export const fetchTasks = systemActionHandlers[types.FETCH_TASKS];
export const fetchBackups = systemActionHandlers[types.FETCH_BACKUPS];
export const fetchUpdates = systemActionHandlers[types.FETCH_UPDATES];
export const fetchLogs = systemActionHandlers[types.FETCH_LOGS];
export const gotoLogsFirstPage = systemActionHandlers[types.GOTO_FIRST_LOGS_PAGE];
export const gotoLogsPreviousPage = systemActionHandlers[types.GOTO_PREVIOUS_LOGS_PAGE];
export const gotoLogsNextPage = systemActionHandlers[types.GOTO_NEXT_LOGS_PAGE];
export const gotoLogsLastPage = systemActionHandlers[types.GOTO_LAST_LOGS_PAGE];
export const gotoLogsPage = systemActionHandlers[types.GOTO_LOGS_PAGE];
export const setLogsSort = systemActionHandlers[types.SET_LOGS_SORT];
export const setLogsFilter = systemActionHandlers[types.SET_LOGS_FILTER];
export const setLogsTableOption = createAction(types.SET_LOGS_TABLE_OPTION);
export const fetchLogFiles = systemActionHandlers[types.FETCH_LOG_FILES];
export const fetchUpdateLogFiles = systemActionHandlers[types.FETCH_UPDATE_LOG_FILES];
export const restart = systemActionHandlers[types.RESTART];
export const shutdown = systemActionHandlers[types.SHUTDOWN];

View file

@ -0,0 +1,28 @@
import $ from 'jquery';
import * as types from './actionTypes';
import { update } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler';
const tagActionHandlers = {
[types.FETCH_TAGS]: createFetchHandler('tags', '/tag'),
[types.ADD_TAG]: function(payload) {
return (dispatch, getState) => {
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: 'tags', data: tags }));
payload.onTagCreated(data);
});
};
}
};
export default tagActionHandlers;

View file

@ -0,0 +1,5 @@
import * as types from './actionTypes';
import tagActionHandlers from './tagActionHandlers';
export const fetchTags = tagActionHandlers[types.FETCH_TAGS];
export const addTag = tagActionHandlers[types.ADD_TAG];

View file

@ -0,0 +1,34 @@
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
import createBatchToggleEpisodeMonitoredHandler from './Creators/createBatchToggleEpisodeMonitoredHandler';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import * as types from './actionTypes';
const wantedActionHandlers = {
...createServerSideCollectionHandlers('missing', '/wanted/missing', (state) => state.wanted, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_MISSING,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_MISSING_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_MISSING_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_MISSING_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_MISSING_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_MISSING_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_MISSING_SORT,
[serverSideCollectionHandlers.FILTER]: types.SET_MISSING_FILTER
}),
[types.BATCH_TOGGLE_MISSING_EPISODES]: createBatchToggleEpisodeMonitoredHandler('missing', (state) => state.wanted.missing),
...createServerSideCollectionHandlers('cutoffUnmet', '/wanted/cutoff', (state) => state.wanted, {
[serverSideCollectionHandlers.FETCH]: types.FETCH_CUTOFF_UNMET,
[serverSideCollectionHandlers.FIRST_PAGE]: types.GOTO_FIRST_CUTOFF_UNMET_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: types.GOTO_PREVIOUS_CUTOFF_UNMET_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: types.GOTO_NEXT_CUTOFF_UNMET_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: types.GOTO_LAST_CUTOFF_UNMET_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: types.GOTO_CUTOFF_UNMET_PAGE,
[serverSideCollectionHandlers.SORT]: types.SET_CUTOFF_UNMET_SORT,
[serverSideCollectionHandlers.FILTER]: types.SET_CUTOFF_UNMET_FILTER
}),
[types.BATCH_TOGGLE_CUTOFF_UNMET_EPISODES]: createBatchToggleEpisodeMonitoredHandler('cutoffUnmet', (state) => state.wanted.cutoffUnmet)
};
export default wantedActionHandlers;

View file

@ -0,0 +1,35 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import wantedActionHandlers from './wantedActionHandlers';
//
// Missing
export const fetchMissing = wantedActionHandlers[types.FETCH_MISSING];
export const gotoMissingFirstPage = wantedActionHandlers[types.GOTO_FIRST_MISSING_PAGE];
export const gotoMissingPreviousPage = wantedActionHandlers[types.GOTO_PREVIOUS_MISSING_PAGE];
export const gotoMissingNextPage = wantedActionHandlers[types.GOTO_NEXT_MISSING_PAGE];
export const gotoMissingLastPage = wantedActionHandlers[types.GOTO_LAST_MISSING_PAGE];
export const gotoMissingPage = wantedActionHandlers[types.GOTO_MISSING_PAGE];
export const setMissingSort = wantedActionHandlers[types.SET_MISSING_SORT];
export const setMissingFilter = wantedActionHandlers[types.SET_MISSING_FILTER];
export const setMissingTableOption = createAction(types.SET_MISSING_TABLE_OPTION);
export const clearMissing = createAction(types.CLEAR_MISSING);
export const batchToggleMissingEpisodes = wantedActionHandlers[types.BATCH_TOGGLE_MISSING_EPISODES];
//
// Cutoff Unmet
export const fetchCutoffUnmet = wantedActionHandlers[types.FETCH_CUTOFF_UNMET];
export const gotoCutoffUnmetFirstPage = wantedActionHandlers[types.GOTO_FIRST_CUTOFF_UNMET_PAGE];
export const gotoCutoffUnmetPreviousPage = wantedActionHandlers[types.GOTO_PREVIOUS_CUTOFF_UNMET_PAGE];
export const gotoCutoffUnmetNextPage = wantedActionHandlers[types.GOTO_NEXT_CUTOFF_UNMET_PAGE];
export const gotoCutoffUnmetLastPage = wantedActionHandlers[types.GOTO_LAST_CUTOFF_UNMET_PAGE];
export const gotoCutoffUnmetPage = wantedActionHandlers[types.GOTO_CUTOFF_UNMET_PAGE];
export const setCutoffUnmetSort = wantedActionHandlers[types.SET_CUTOFF_UNMET_SORT];
export const setCutoffUnmetFilter = wantedActionHandlers[types.SET_CUTOFF_UNMET_FILTER];
export const setCutoffUnmetTableOption= createAction(types.SET_CUTOFF_UNMET_TABLE_OPTION);
export const clearCutoffUnmet= createAction(types.CLEAR_CUTOFF_UNMET);
export const batchToggleCutoffUnmetEpisodes = wantedActionHandlers[types.BATCH_TOGGLE_CUTOFF_UNMET_EPISODES];

View file

@ -0,0 +1,39 @@
import { applyMiddleware, compose } from 'redux';
import ravenMiddleware from 'redux-raven-middleware';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import persistState from './persistState';
export default function(history) {
const {
analytics,
branch,
version,
release,
isProduction
} = window.Sonarr;
const dsn = isProduction ? 'https://b80ca60625b443c38b242e0d21681eb7@sentry.sonarr.tv/13' :
'https://8dbaacdfe2ff4caf97dc7945aecf9ace@sentry.sonarr.tv/12';
const middlewares = [];
if (analytics) {
middlewares.push(ravenMiddleware(dsn, {
environment: isProduction ? 'production' : 'development',
release,
tags: {
branch,
version
}
}));
}
middlewares.push(routerMiddleware(history));
middlewares.push(thunk);
return compose(
applyMiddleware(...middlewares),
persistState
);
}

View file

@ -0,0 +1,119 @@
import _ from 'lodash';
import persistState from 'redux-localstorage';
import * as addSeriesReducers from 'Store/Reducers/addSeriesReducers';
import * as episodeReducers from 'Store/Reducers/episodeReducers';
import * as artistIndexReducers from 'Store/Reducers/artistIndexReducers';
import * as seriesEditorReducers from 'Store/Reducers/seriesEditorReducers';
import * as seasonPassReducers from 'Store/Reducers/seasonPassReducers';
import * as calendarReducers from 'Store/Reducers/calendarReducers';
import * as historyReducers from 'Store/Reducers/historyReducers';
import * as blacklistReducers from 'Store/Reducers/blacklistReducers';
import * as wantedReducers from 'Store/Reducers/wantedReducers';
import * as settingsReducers from 'Store/Reducers/settingsReducers';
import * as systemReducers from 'Store/Reducers/systemReducers';
import * as interactiveImportReducers from 'Store/Reducers/interactiveImportReducers';
import * as queueReducers from 'Store/Reducers/queueReducers';
const reducers = [
addSeriesReducers,
episodeReducers,
artistIndexReducers,
seriesEditorReducers,
seasonPassReducers,
calendarReducers,
historyReducers,
blacklistReducers,
wantedReducers,
settingsReducers,
systemReducers,
interactiveImportReducers,
queueReducers
];
const columnPaths = [];
const paths = _.reduce(reducers, (acc, reducer) => {
reducer.persistState.forEach((path) => {
if (path.match(/\.columns$/)) {
columnPaths.push(path);
}
acc.push(path);
});
return acc;
}, []);
function mergeColumns(path, initialState, persistedState, computedState) {
const initialColumns = _.get(initialState, path);
const persistedColumns = _.get(persistedState, path);
if (!persistedColumns || !persistedColumns.length) {
return;
}
const columns = [];
initialColumns.forEach((initialColumn) => {
const persistedColumnIndex = _.findIndex(persistedColumns, { name: initialColumn.name });
const column = Object.assign({}, initialColumn);
const persistedColumn = persistedColumnIndex > -1 ? persistedColumns[persistedColumnIndex] : undefined;
if (persistedColumn) {
column.isVisible = persistedColumn.isVisible;
}
// If there is a persisted column, it's index doesn't exceed the column list
// and it's modifiable, insert it in the proper position.
if (persistedColumn && columns.length - 1 > persistedColumnIndex && persistedColumn.isModifiable !== false) {
columns.splice(persistedColumnIndex, 0, column);
} else {
columns.push(column);
}
// Set the columns in the persisted state
_.set(computedState, path, columns);
});
}
function slicer(paths_) {
return (state) => {
const subset = {};
paths_.forEach((path) => {
_.set(subset, path, _.get(state, path));
});
return subset;
};
}
function serialize(obj) {
return JSON.stringify(obj, null, 2);
}
function merge(initialState, persistedState) {
if (!persistedState) {
return initialState;
}
const computedState = {};
_.merge(computedState, initialState, persistedState);
columnPaths.forEach((columnPath) => {
mergeColumns(columnPath, initialState, persistedState, computedState);
});
return computedState;
}
const config = {
slicer,
serialize,
merge,
key: 'sonarr'
};
export default persistState(paths, config);

View file

@ -0,0 +1,23 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createAddItemReducer(section) {
return (state, { payload }) => {
const {
section: payloadSection,
...otherProps
} = payload;
if (section === payloadSection) {
const newState = getSectionState(state, section);
newState.items = [...newState.items, { ...otherProps }];
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createAddItemReducer;

View file

@ -0,0 +1,21 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createClearPendingChangesReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const newState = getSectionState(state, section);
newState.pendingChanges = {};
if (newState.hasOwnProperty('saveError')) {
newState.saveError = null;
}
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createClearPendingChangesReducer;

View file

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

View file

@ -0,0 +1,20 @@
function createReducers(sections, createReducer) {
const reducers = {};
sections.forEach((section) => {
reducers[section] = createReducer(section);
});
return (state, action) => {
const section = action.payload.section;
const reducer = reducers[section];
if (reducer) {
return reducer(state, action);
}
return state;
};
}
export default createReducers;

View file

@ -0,0 +1,20 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createRemoveItemReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const newState = getSectionState(state, section);
newState.items = [...newState.items];
_.remove(newState.items, { id: payload.id });
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createRemoveItemReducer;

View file

@ -0,0 +1,17 @@
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import { filterTypes } from 'Helpers/Props';
function createSetClientSideCollectionFilterReducer(section) {
return (state, { payload }) => {
const newState = getSectionState(state, section);
newState.filterKey = payload.filterKey;
newState.filterValue = payload.filterValue;
newState.filterType = payload.filterType || filterTypes.EQUAL;
return updateSectionState(state, section, newState);
};
}
export default createSetClientSideCollectionFilterReducer;

View file

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

View file

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

View file

@ -0,0 +1,45 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
const whitelistedProperties = [
'isFetching',
'isPopulated',
'error',
'isFetchingSchema',
'schemaPopulated',
'schemaError',
'schema',
'selectedSchema',
'isSaving',
'saveError',
'isTesting',
'isDeleting',
'deleteError',
'pendingChanges',
'filterKey',
'filterValue',
'page',
'sortKey',
'sortDirection'
];
const blacklistedProperties = [
'section',
'id'
];
function createSetReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const newState = Object.assign(getSectionState(state, section),
_.omit(payload, blacklistedProperties));
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createSetReducer;

View file

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

View file

@ -0,0 +1,19 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
const whitelistedProperties = [
'pageSize',
'columns'
];
function createSetTableOptionReducer(section) {
return (state, { payload }) => {
const newState = Object.assign(getSectionState(state, section),
_.pick(payload, whitelistedProperties));
return updateSectionState(state, section, newState);
};
}
export default createSetTableOptionReducer;

View file

@ -0,0 +1,36 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createUpdateItemReducer(section, idProp = 'id') {
return (state, { payload }) => {
const {
section: payloadSection,
updateOnly = false,
...otherProps
} = payload;
if (section === payloadSection) {
const newState = getSectionState(state, section);
const items = newState.items;
const index = _.findIndex(items, { [idProp]: payload[idProp] });
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, section, newState);
}
return state;
};
}
export default createUpdateItemReducer;

View file

@ -0,0 +1,23 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createUpdateReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const newState = getSectionState(state, section);
if (_.isArray(payload.data)) {
newState.items = payload.data;
} else {
newState.item = payload.data;
}
return updateSectionState(state, section, newState);
}
return state;
};
}
export default createUpdateReducer;

View file

@ -0,0 +1,24 @@
import _ from 'lodash';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
function createUpdateServerSideCollectionReducer(section) {
return (state, { payload }) => {
if (section === payload.section) {
const data = payload.data;
const newState = getSectionState(state, section);
const serverState = _.omit(data, ['records']);
const calculatedState = {
totalPages: Math.max(Math.ceil(data.totalRecords / data.pageSize), 1),
items: data.records
};
return updateSectionState(state, section, Object.assign(newState, serverState, calculatedState));
}
return state;
};
}
export default createUpdateServerSideCollectionReducer;

View file

@ -0,0 +1,68 @@
import { handleActions } from 'redux-actions';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createSetSettingValueReducer from './Creators/createSetSettingValueReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createRemoveItemReducer from './Creators/createRemoveItemReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isAdding: false,
isAdded: false,
addError: null,
items: [],
defaults: {
rootFolderPath: '',
monitor: 'allEpisodes',
qualityProfileId: 0,
languageProfileId: 0,
seriesType: 'standard',
albumFolder: true,
tags: []
}
};
export const persistState = [
'addSeries.defaults'
];
const reducerSection = 'addSeries';
const addSeriesReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
[types.REMOVE_ITEM]: createRemoveItemReducer(reducerSection),
[types.SET_ADD_SERIES_VALUE]: createSetSettingValueReducer(reducerSection),
[types.SET_ADD_SERIES_DEFAULT]: function(state, { payload }) {
const newState = getSectionState(state, reducerSection);
newState.defaults = {
...newState.defaults,
...payload
};
return updateSectionState(state, reducerSection, newState);
},
[types.CLEAR_ADD_SERIES]: function(state) {
const {
defaults,
...otherDefaultState
} = defaultState;
return Object.assign({}, state, otherDefaultState);
}
}, defaultState);
export default addSeriesReducers;

View file

@ -0,0 +1,74 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createRemoveItemReducer from './Creators/createRemoveItemReducer';
function getDimensions(width, height) {
const dimensions = {
width,
height,
isExtraSmallScreen: width <= 480,
isSmallScreen: width <= 768,
isMediumScreen: width <= 992,
isLargeScreen: width <= 1200
};
return dimensions;
}
export const defaultState = {
dimensions: getDimensions(window.innerWidth, window.innerHeight),
messages: {
items: []
},
version: window.Sonarr.version,
isUpdated: false,
isConnected: true,
isReconnecting: false,
isDisconnected: false,
isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen
};
const appReducers = handleActions({
[types.SAVE_DIMENSIONS]: function(state, { payload }) {
const {
width,
height
} = payload;
const dimensions = getDimensions(width, height);
return Object.assign({}, state, { dimensions });
},
[types.SHOW_MESSAGE]: createUpdateItemReducer('messages'),
[types.HIDE_MESSAGE]: createRemoveItemReducer('messages'),
[types.SET_APP_VALUE]: createSetReducer('app'),
[types.SET_VERSION]: function(state, { payload }) {
const version = payload.version;
const newState = {
version
};
if (state.version !== version) {
newState.isUpdated = true;
}
return Object.assign({}, state, newState);
},
[types.SET_IS_SIDEBAR_VISIBLE]: function(state, { payload }) {
const newState = {
isSidebarVisible: payload.isSidebarVisible
};
return Object.assign({}, state, newState);
}
}, defaultState);
export default appReducers;

View file

@ -0,0 +1,213 @@
import moment from 'moment';
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { filterTypes, sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
import createSetClientSideCollectionFilterReducer from './Creators/createSetClientSideCollectionFilterReducer';
export const defaultState = {
sortKey: 'sortTitle',
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortTitle',
secondarySortDirection: sortDirections.ASCENDING,
filterKey: null,
filterValue: null,
filterType: filterTypes.EQUAL,
view: 'posters',
posterOptions: {
detailedProgressBar: false,
size: 'large',
showTitle: false,
showQualityProfile: false
},
columns: [
{
name: 'status',
columnLabel: 'Status',
isVisible: true,
isModifiable: false
},
{
name: 'sortName',
label: 'Artist Name',
isSortable: true,
isVisible: true,
isModifiable: false
},
{
name: 'network',
label: 'Network',
isSortable: true,
isVisible: true
},
{
name: 'qualityProfileId',
label: 'Quality Profile',
isSortable: true,
isVisible: true
},
{
name: 'languageProfileId',
label: 'Language Profile',
isSortable: true,
isVisible: false
},
{
name: 'nextAiring',
label: 'Next Airing',
isSortable: true,
isVisible: true
},
{
name: 'previousAiring',
label: 'Previous Airing',
isSortable: true,
isVisible: false
},
{
name: 'added',
label: 'Added',
isSortable: true,
isVisible: false
},
{
name: 'albumCount',
label: 'Albums',
isSortable: true,
isVisible: true
},
{
name: 'trackProgress',
label: 'Tracks',
isSortable: true,
isVisible: true
},
{
name: 'trackCount',
label: 'Track Count',
isSortable: true,
isVisible: false
},
{
name: 'latestSeason',
label: 'Latest Season',
isSortable: true,
isVisible: false
},
{
name: 'path',
label: 'Path',
isSortable: true,
isVisible: false
},
{
name: 'sizeOnDisk',
label: 'Size on Disk',
isSortable: true,
isVisible: false
},
{
name: 'tags',
label: 'Tags',
isSortable: false,
isVisible: false
},
{
name: 'useSceneNumbering',
label: 'Scene Numbering',
isSortable: true,
isVisible: false
},
{
name: 'actions',
columnLabel: 'Actions',
isVisible: true,
isModifiable: false
}
],
sortPredicates: {
network: function(item) {
const network = item.network;
return network ? network.toLowerCase() : '';
},
nextAiring: function(item, direction) {
const nextAiring = item.nextAiring;
if (nextAiring) {
return moment(nextAiring).unix();
}
if (direction === sortDirections.DESCENDING) {
return 0;
}
return Number.MAX_VALUE;
},
episodeProgress: function(item) {
const {
episodeCount = 0,
episodeFileCount
} = item;
const progress = episodeCount ? episodeFileCount / episodeCount * 100 : 100;
return progress + episodeCount / 1000000;
}
},
filterPredicates: {
missing: function(item) {
return item.episodeCount - item.episodeFileCount > 0;
}
}
};
export const persistState = [
'seriesIndex.sortKey',
'seriesIndex.sortDirection',
'seriesIndex.filterKey',
'seriesIndex.filterValue',
'seriesIndex.filterType',
'seriesIndex.view',
'seriesIndex.columns',
'seriesIndex.posterOptions'
];
const reducerSection = 'seriesIndex';
const artistIndexReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.SET_ARTIST_SORT]: createSetClientSideCollectionSortReducer(reducerSection),
[types.SET_ARTIST_FILTER]: createSetClientSideCollectionFilterReducer(reducerSection),
[types.SET_ARTIST_VIEW]: function(state, { payload }) {
return Object.assign({}, state, { view: payload.view });
},
[types.SET_ARTIST_TABLE_OPTION]: createSetTableOptionReducer(reducerSection),
[types.SET_ARTIST_POSTER_OPTION]: function(state, { payload }) {
const posterOptions = state.posterOptions;
return {
...state,
posterOptions: {
...posterOptions,
...payload
}
};
}
}, defaultState);
export default artistIndexReducers;

View file

@ -0,0 +1,80 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateServerSideCollectionReducer from './Creators/createUpdateServerSideCollectionReducer';
const reducerSection = 'blacklist';
export const defaultState = {
isFetching: false,
isPopulated: false,
pageSize: 20,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING,
error: null,
items: [],
columns: [
{
name: 'series.sortTitle',
label: 'Series Title',
isSortable: true,
isVisible: true
},
{
name: 'sourceTitle',
label: 'Source Title',
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: 'indexer',
label: 'Indexer',
isSortable: true,
isVisible: false
},
{
name: 'details',
columnLabel: 'Details',
isVisible: true,
isModifiable: false
}
]
};
export const persistState = [
'blacklist.pageSize',
'blacklist.sortKey',
'blacklist.sortDirection',
'blacklist.columns'
];
const blacklistReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_SERVER_SIDE_COLLECTION]: createUpdateServerSideCollectionReducer(reducerSection),
[types.SET_BLACKLIST_TABLE_OPTION]: createSetTableOptionReducer(reducerSection)
}, defaultState);
export default blacklistReducers;

View file

@ -0,0 +1,48 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
start: null,
end: null,
dates: [],
dayCount: 7,
view: window.innerWidth > 768 ? 'week' : 'day',
unmonitored: false,
showUpcoming: true,
error: null,
items: []
};
export const persistState = [
'calendar.view',
'calendar.unmonitored',
'calendar.showUpcoming'
];
const section = 'calendar';
const calendarReducers = handleActions({
[types.SET]: createSetReducer(section),
[types.UPDATE]: createUpdateReducer(section),
[types.UPDATE_ITEM]: createUpdateItemReducer(section),
[types.CLEAR_CALENDAR]: (state) => {
const {
view,
unmonitored,
showUpcoming,
...otherDefaultState
} = defaultState;
return Object.assign({}, state, otherDefaultState);
}
}, defaultState);
export default calendarReducers;

View file

@ -0,0 +1,32 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
export const defaultState = {
refreshing: false,
token: null,
siteKey: null,
secretToken: null,
ray: null,
stoken: null,
responseUrl: null
};
const section = 'captcha';
const captchaReducers = handleActions({
[types.SET_CAPTCHA_VALUE]: function(state, { payload }) {
const newState = Object.assign(getSectionState(state, section), payload);
return updateSectionState(state, section, newState);
},
[types.RESET_CAPTCHA]: function(state) {
return updateSectionState(state, section, defaultState);
}
}, defaultState);
export default captchaReducers;

View file

@ -0,0 +1,64 @@
import _ from 'lodash';
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
handlers: {}
};
const reducerSection = 'commands';
const commandReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
[types.ADD_COMMAND]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.items = [...state.items, payload];
return newState;
},
[types.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;
},
[types.REGISTER_FINISH_COMMAND_HANDLER]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.handlers[payload.key] = {
name: payload.name,
handler: payload.handler
};
return newState;
},
[types.UNREGISTER_FINISH_COMMAND_HANDLER]: (state, { payload }) => {
const newState = Object.assign({}, state);
delete newState.handlers[payload.key];
return newState;
}
}, defaultState);
export default commandReducers;

View file

@ -0,0 +1,34 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createRemoveItemReducer from './Creators/createRemoveItemReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isDeleting: false,
deleteError: null,
isSaving: false,
saveError: null,
items: []
};
const reducerSection = 'episodeFiles';
const episodeFileReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
[types.REMOVE_ITEM]: createRemoveItemReducer(reducerSection),
[types.CLEAR_EPISODE_FILES]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState);
export default episodeFileReducers;

View file

@ -0,0 +1,26 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: []
};
const reducerSection = 'episodeHistory';
const episodeHistoryReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.CLEAR_EPISODE_HISTORY]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState);
export default episodeHistoryReducers;

View file

@ -0,0 +1,106 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
sortKey: 'episodeNumber',
sortDirection: sortDirections.DESCENDING,
items: [],
columns: [
{
name: 'monitored',
columnLabel: 'Monitored',
isVisible: true,
isModifiable: false
},
{
name: 'episodeNumber',
label: '#',
isVisible: true
},
{
name: 'title',
label: 'Title',
isVisible: true
},
{
name: 'path',
label: 'Path',
isVisible: false
},
{
name: 'relativePath',
label: 'Relative Path',
isVisible: false
},
{
name: 'airDateUtc',
label: 'Air Date',
isVisible: true
},
{
name: 'language',
label: 'Language',
isVisible: false
},
{
name: 'audioInfo',
label: 'Audio Info',
isVisible: false
},
{
name: 'videoCodec',
label: 'Video Codec',
isVisible: false
},
{
name: 'status',
label: 'Status',
isVisible: true
},
{
name: 'actions',
columnLabel: 'Actions',
isVisible: true,
isModifiable: false
}
]
};
export const persistState = [
'episodes.columns'
];
const reducerSection = 'episodes';
const episodeReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
[types.SET_EPISODES_TABLE_OPTION]: createSetTableOptionReducer(reducerSection),
[types.CLEAR_EPISODES]: (state) => {
return Object.assign({}, state, {
isFetching: false,
isPopulated: false,
error: null,
items: []
});
},
[types.SET_EPISODES_SORT]: createSetClientSideCollectionSortReducer(reducerSection)
}, defaultState);
export default episodeReducers;

View file

@ -0,0 +1,113 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createClearReducer from './Creators/createClearReducer';
import createSetReducer from './Creators/createSetReducer';
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createUpdateServerSideCollectionReducer from './Creators/createUpdateServerSideCollectionReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
pageSize: 20,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING,
filterKey: null,
filterValue: null,
items: [],
columns: [
{
name: 'eventType',
columnLabel: 'Event Type',
isVisible: true,
isModifiable: false
},
{
name: 'series.sortTitle',
label: 'Series',
isSortable: true,
isVisible: true
},
{
name: 'episode',
label: 'Episode',
isVisible: true
},
{
name: 'episodeTitle',
label: 'Episode Title',
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
}
]
};
export const persistState = [
'history.pageSize',
'history.sortKey',
'history.sortDirection',
'history.filterKey',
'history.filterValue'
];
const serverSideCollectionName = 'history';
const historyReducers = handleActions({
[types.SET]: createSetReducer(serverSideCollectionName),
[types.UPDATE]: createUpdateReducer(serverSideCollectionName),
[types.UPDATE_ITEM]: createUpdateItemReducer(serverSideCollectionName),
[types.UPDATE_SERVER_SIDE_COLLECTION]: createUpdateServerSideCollectionReducer(serverSideCollectionName),
[types.SET_HISTORY_TABLE_OPTION]: createSetTableOptionReducer(serverSideCollectionName),
[types.CLEAR_HISTORY]: createClearReducer('history', {
isFetching: false,
isPopulated: false,
error: null,
items: []
})
}, defaultState);
export default historyReducers;

View file

@ -0,0 +1,35 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createRemoveItemReducer from './Creators/createRemoveItemReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isImporting: false,
isImported: false,
importError: null,
items: []
};
const reducerSection = 'importSeries';
const importSeriesReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_ITEM]: createUpdateItemReducer(reducerSection),
[types.REMOVE_ITEM]: createRemoveItemReducer(reducerSection),
[types.CLEAR_IMPORT_SERIES]: function(state) {
return Object.assign({}, state, defaultState);
},
[types.SET_IMPORT_SERIES_VALUE]: createUpdateItemReducer(reducerSection)
}, defaultState);
export default importSeriesReducers;

View file

@ -0,0 +1,88 @@
import { combineReducers } from 'redux';
import { enableBatching } from 'redux-batched-actions';
import { routerReducer } from 'react-router-redux';
import app, { defaultState as defaultappState } from './appReducers';
import addSeries, { defaultState as defaultAddSeriesState } from './addSeriesReducers';
import importSeries, { defaultState as defaultImportSeriesState } from './importSeriesReducers';
import series, { defaultState as defaultSeriesState } from './seriesReducers';
import seriesIndex, { defaultState as defaultSeriesIndexState } from './artistIndexReducers';
import seriesEditor, { defaultState as defaultSeriesEditorState } from './seriesEditorReducers';
import seasonPass, { defaultState as defaultSeasonPassState } from './seasonPassReducers';
import calendar, { defaultState as defaultCalendarState } from './calendarReducers';
import history, { defaultState as defaultHistoryState } from './historyReducers';
import queue, { defaultState as defaultQueueState } from './queueReducers';
import blacklist, { defaultState as defaultBlacklistState } from './blacklistReducers';
import episodes, { defaultState as defaultEpisodesState } from './episodeReducers';
import episodeFiles, { defaultState as defaultEpisodeFilesState } from './episodeFileReducers';
import episodeHistory, { defaultState as defaultEpisodeHistoryState } from './episodeHistoryReducers';
import releases, { defaultState as defaultReleasesState } from './releaseReducers';
import wanted, { defaultState as defaultWantedState } from './wantedReducers';
import settings, { defaultState as defaultSettingsState } from './settingsReducers';
import system, { defaultState as defaultSystemState } from './systemReducers';
import commands, { defaultState as defaultCommandsState } from './commandReducers';
import paths, { defaultState as defaultPathsState } from './pathReducers';
import tags, { defaultState as defaultTagsState } from './tagReducers';
import captcha, { defaultState as defaultCaptchaState } from './captchaReducers';
import oAuth, { defaultState as defaultOAuthState } from './oAuthReducers';
import interactiveImport, { defaultState as defaultInteractiveImportState } from './interactiveImportReducers';
import rootFolders, { defaultState as defaultRootFoldersState } from './rootFolderReducers';
import organizePreview, { defaultState as defaultOrganizePreviewState } from './organizePreviewReducers';
export const defaultState = {
app: defaultappState,
addSeries: defaultAddSeriesState,
importSeries: defaultImportSeriesState,
series: defaultSeriesState,
seriesIndex: defaultSeriesIndexState,
seriesEditor: defaultSeriesEditorState,
seasonPass: defaultSeasonPassState,
calendar: defaultCalendarState,
history: defaultHistoryState,
queue: defaultQueueState,
blacklist: defaultBlacklistState,
episodes: defaultEpisodesState,
episodeFiles: defaultEpisodeFilesState,
episodeHistory: defaultEpisodeHistoryState,
releases: defaultReleasesState,
wanted: defaultWantedState,
settings: defaultSettingsState,
system: defaultSystemState,
commands: defaultCommandsState,
paths: defaultPathsState,
tags: defaultTagsState,
captcha: defaultCaptchaState,
oAuth: defaultOAuthState,
interactiveImport: defaultInteractiveImportState,
rootFolders: defaultRootFoldersState,
organizePreview: defaultOrganizePreviewState
};
export default enableBatching(combineReducers({
app,
addSeries,
importSeries,
series,
seriesIndex,
seriesEditor,
seasonPass,
calendar,
history,
queue,
blacklist,
episodes,
episodeFiles,
episodeHistory,
releases,
wanted,
settings,
system,
commands,
paths,
tags,
captcha,
oAuth,
interactiveImport,
rootFolders,
organizePreview,
routing: routerReducer
}));

View file

@ -0,0 +1,97 @@
import _ from 'lodash';
import moment from 'moment';
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: [],
sortKey: 'quality',
sortDirection: sortDirections.DESCENDING,
recentFolders: [],
importMode: 'move',
sortPredicates: {
series: function(item, direction) {
const series = item.series;
return series ? series.sortTitle : '';
},
quality: function(item, direction) {
return item.quality.qualityWeight;
}
}
};
export const persistState = [
'interactiveImport.recentFolders',
'interactiveImport.importMode'
];
const reducerSection = 'interactiveImport';
const interactiveImportReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.UPDATE_INTERACTIVE_IMPORT_ITEM]: (state, { payload }) => {
const id = payload.id;
const newState = Object.assign({}, state);
const items = newState.items;
const index = _.findIndex(items, { id });
const item = Object.assign({}, items[index], payload);
newState.items = [...items];
newState.items.splice(index, 1, item);
return newState;
},
[types.ADD_RECENT_FOLDER]: function(state, { payload }) {
const folder = payload.folder;
const recentFolder = { folder, lastUsed: moment().toISOString() };
const recentFolders = [...state.recentFolders];
const index = _.findIndex(recentFolders, { folder });
if (index > -1) {
recentFolders.splice(index, 1, recentFolder);
} else {
recentFolders.push(recentFolder);
}
return Object.assign({}, state, { recentFolders });
},
[types.REMOVE_RECENT_FOLDER]: function(state, { payload }) {
const folder = payload.folder;
const recentFolders = _.remove([...state.recentFolders], { folder });
return Object.assign({}, state, { recentFolders });
},
[types.CLEAR_INTERACTIVE_IMPORT]: function(state) {
const newState = {
...defaultState,
recentFolders: state.recentFolders,
importMode: state.importMode
};
return newState;
},
[types.SET_INTERACTIVE_IMPORT_SORT]: createSetClientSideCollectionSortReducer(reducerSection),
[types.SET_INTERACTIVE_IMPORT_MODE]: function(state, { payload }) {
return Object.assign({}, state, { importMode: payload.importMode });
}
}, defaultState);
export default interactiveImportReducers;

View file

@ -0,0 +1,28 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
export const defaultState = {
authorizing: false,
accessToken: null,
accessTokenSecret: null
};
const section = 'oAuth';
const oAuthReducers = handleActions({
[types.SET_OAUTH_VALUE]: function(state, { payload }) {
const newState = Object.assign(getSectionState(state, section), payload);
return updateSectionState(state, section, newState);
},
[types.RESET_OAUTH]: function(state) {
return updateSectionState(state, section, defaultState);
}
}, defaultState);
export default oAuthReducers;

View file

@ -0,0 +1,26 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: []
};
const reducerSection = 'organizePreview';
const organizePreviewReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.CLEAR_ORGANIZE_PREVIEW]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState);
export default organizePreviewReducers;

View file

@ -0,0 +1,45 @@
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import createSetReducer from './Creators/createSetReducer';
export const defaultState = {
currentPath: '',
isPopulated: false,
isFetching: false,
error: null,
directories: [],
files: [],
parent: null
};
const reducerSection = 'paths';
const pathReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.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;
},
[types.CLEAR_PATHS]: (state, { payload }) => {
const newState = Object.assign({}, state);
newState.path = '';
newState.directories = [];
newState.files = [];
newState.parent = '';
return newState;
}
}, defaultState);
export default pathReducers;

View file

@ -0,0 +1,165 @@
import { handleActions } from 'redux-actions';
import updateSectionState from 'Utilities/State/updateSectionState';
import { sortDirections } from 'Helpers/Props';
import * as types from 'Store/Actions/actionTypes';
import createClearReducer from './Creators/createClearReducer';
import createSetReducer from './Creators/createSetReducer';
import createSetTableOptionReducer from './Creators/createSetTableOptionReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createUpdateItemReducer from './Creators/createUpdateItemReducer';
import createReducers from './Creators/createReducers';
import createUpdateServerSideCollectionReducer from './Creators/createUpdateServerSideCollectionReducer';
export const defaultState = {
queueStatus: {
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',
isVisible: true,
isModifiable: false
},
{
name: 'series.sortTitle',
label: 'Series',
isSortable: true,
isVisible: true
},
{
name: 'episode',
label: 'Episode',
isVisible: true
},
{
name: 'episodeTitle',
label: 'Episode Title',
isVisible: true
},
{
name: 'quality',
label: 'Quality',
isSortable: true,
isVisible: true
},
{
name: 'protocol',
label: 'Protocol',
isVisible: false
},
{
name: 'indexer',
label: 'Indexer',
isVisible: false
},
{
name: 'downloadClient',
label: 'Download Client',
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
}
]
},
queueEpisodes: {
isPopulated: false,
items: []
}
};
export const persistState = [
'queue.paged.pageSize',
'queue.paged.sortKey',
'queue.paged.sortDirection',
'queue.paged.columns'
];
const propertyNames = [
'queueStatus',
'details',
'episodes'
];
const paged = 'paged';
const queueReducers = handleActions({
[types.SET]: createReducers([...propertyNames, paged], createSetReducer),
[types.UPDATE]: createReducers([...propertyNames, paged], createUpdateReducer),
[types.UPDATE_ITEM]: createReducers(['queueEpisodes', paged], createUpdateItemReducer),
[types.CLEAR_QUEUE_DETAILS]: createClearReducer('details', defaultState.details),
[types.UPDATE_SERVER_SIDE_COLLECTION]: createUpdateServerSideCollectionReducer(paged),
[types.SET_QUEUE_TABLE_OPTION]: createSetTableOptionReducer(paged),
[types.CLEAR_QUEUE]: createClearReducer('paged', {
isFetching: false,
isPopulated: false,
error: null,
items: []
}),
[types.SET_QUEUE_EPISODES]: function(state, { payload }) {
const section = 'queueEpisodes';
return updateSectionState(state, section, {
isPopulated: true,
items: payload.episodes
});
},
[types.CLEAR_EPISODES]: (state) => {
const section = 'queueEpisodes';
return updateSectionState(state, section, {
isPopulated: false,
items: []
});
}
}, defaultState);
export default queueReducers;

View file

@ -0,0 +1,65 @@
import _ from 'lodash';
import { handleActions } from 'redux-actions';
import * as types from 'Store/Actions/actionTypes';
import { sortDirections } from 'Helpers/Props';
import createSetReducer from './Creators/createSetReducer';
import createUpdateReducer from './Creators/createUpdateReducer';
import createSetClientSideCollectionSortReducer from './Creators/createSetClientSideCollectionSortReducer';
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;
}
}
};
const reducerSection = 'releases';
const releaseReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
[types.CLEAR_RELEASES]: (state) => {
return Object.assign({}, state, defaultState);
},
[types.UPDATE_RELEASE]: (state, { payload }) => {
const guid = payload.guid;
const newState = Object.assign({}, state);
const items = newState.items;
const index = _.findIndex(items, { guid });
const item = Object.assign({}, items[index], payload);
newState.items = [...items];
newState.items.splice(index, 1, item);
return newState;
},
[types.SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(reducerSection)
}, defaultState);
export default releaseReducers;

Some files were not shown because too many files have changed in this diff Show more