mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Indexer and Search page work
This commit is contained in:
@@ -1,147 +0,0 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
|
||||
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.indexers';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_INDEXERS = 'settings/indexers/fetchIndexers';
|
||||
export const FETCH_INDEXER_SCHEMA = 'settings/indexers/fetchIndexerSchema';
|
||||
export const SELECT_INDEXER_SCHEMA = 'settings/indexers/selectIndexerSchema';
|
||||
export const CLONE_INDEXER = 'settings/indexers/cloneIndexer';
|
||||
export const SET_INDEXER_VALUE = 'settings/indexers/setIndexerValue';
|
||||
export const SET_INDEXER_FIELD_VALUE = 'settings/indexers/setIndexerFieldValue';
|
||||
export const SAVE_INDEXER = 'settings/indexers/saveIndexer';
|
||||
export const CANCEL_SAVE_INDEXER = 'settings/indexers/cancelSaveIndexer';
|
||||
export const DELETE_INDEXER = 'settings/indexers/deleteIndexer';
|
||||
export const TEST_INDEXER = 'settings/indexers/testIndexer';
|
||||
export const CANCEL_TEST_INDEXER = 'settings/indexers/cancelTestIndexer';
|
||||
export const TEST_ALL_INDEXERS = 'settings/indexers/testAllIndexers';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchIndexers = createThunk(FETCH_INDEXERS);
|
||||
export const fetchIndexerSchema = createThunk(FETCH_INDEXER_SCHEMA);
|
||||
export const selectIndexerSchema = createAction(SELECT_INDEXER_SCHEMA);
|
||||
export const cloneIndexer = createAction(CLONE_INDEXER);
|
||||
|
||||
export const saveIndexer = createThunk(SAVE_INDEXER);
|
||||
export const cancelSaveIndexer = createThunk(CANCEL_SAVE_INDEXER);
|
||||
export const deleteIndexer = createThunk(DELETE_INDEXER);
|
||||
export const testIndexer = createThunk(TEST_INDEXER);
|
||||
export const cancelTestIndexer = createThunk(CANCEL_TEST_INDEXER);
|
||||
export const testAllIndexers = createThunk(TEST_ALL_INDEXERS);
|
||||
|
||||
export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const setIndexerFieldValue = createAction(SET_INDEXER_FIELD_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSchemaFetching: false,
|
||||
isSchemaPopulated: false,
|
||||
schemaError: null,
|
||||
schema: [],
|
||||
selectedSchema: {},
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isTesting: false,
|
||||
isTestingAll: false,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_INDEXERS]: createFetchHandler(section, '/indexer'),
|
||||
[FETCH_INDEXER_SCHEMA]: createFetchSchemaHandler(section, '/indexer/schema'),
|
||||
|
||||
[SAVE_INDEXER]: createSaveProviderHandler(section, '/indexer'),
|
||||
[CANCEL_SAVE_INDEXER]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_INDEXER]: createRemoveItemHandler(section, '/indexer'),
|
||||
[TEST_INDEXER]: createTestProviderHandler(section, '/indexer'),
|
||||
[CANCEL_TEST_INDEXER]: createCancelTestProviderHandler(section),
|
||||
[TEST_ALL_INDEXERS]: createTestAllProvidersHandler(section, '/indexer')
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_INDEXER_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_INDEXER_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
|
||||
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
selectedSchema.enableRss = selectedSchema.supportsRss;
|
||||
selectedSchema.enableAutomaticSearch = selectedSchema.supportsSearch;
|
||||
selectedSchema.enableInteractiveSearch = selectedSchema.supportsSearch;
|
||||
|
||||
return selectedSchema;
|
||||
});
|
||||
},
|
||||
|
||||
[CLONE_INDEXER]: function(state, { payload }) {
|
||||
const id = payload.id;
|
||||
const newState = getSectionState(state, section);
|
||||
const item = newState.items.find((i) => i.id === id);
|
||||
|
||||
// Use selectedSchema so `createProviderSettingsSelector` works properly
|
||||
const selectedSchema = { ...item };
|
||||
delete selectedSchema.id;
|
||||
delete selectedSchema.name;
|
||||
|
||||
selectedSchema.fields = selectedSchema.fields.map((field) => {
|
||||
return { ...field };
|
||||
});
|
||||
|
||||
newState.selectedSchema = selectedSchema;
|
||||
|
||||
// Set the name in pendingChanges
|
||||
newState.pendingChanges = {
|
||||
name: `${item.name} - Copy`
|
||||
};
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
@@ -3,8 +3,9 @@ import * as captcha from './captchaActions';
|
||||
import * as commands from './commandActions';
|
||||
import * as customFilters from './customFilterActions';
|
||||
import * as history from './historyActions';
|
||||
import * as indexers from './indexerActions';
|
||||
import * as indexerIndex from './indexerIndexActions';
|
||||
import * as movies from './movieActions';
|
||||
import * as movieIndex from './movieIndexActions';
|
||||
import * as oAuth from './oAuthActions';
|
||||
import * as paths from './pathActions';
|
||||
import * as providerOptions from './providerOptionActions';
|
||||
@@ -24,7 +25,8 @@ export default [
|
||||
providerOptions,
|
||||
releases,
|
||||
movies,
|
||||
movieIndex,
|
||||
indexers,
|
||||
indexerIndex,
|
||||
settings,
|
||||
system,
|
||||
tags
|
||||
|
141
frontend/src/Store/Actions/indexerActions.js
Normal file
141
frontend/src/Store/Actions/indexerActions.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
|
||||
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'indexers';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSchemaFetching: false,
|
||||
isSchemaPopulated: false,
|
||||
schemaError: null,
|
||||
schema: [],
|
||||
selectedSchema: {},
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isTesting: false,
|
||||
isTestingAll: false,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
};
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_INDEXERS = 'indexers/fetchIndexers';
|
||||
export const FETCH_INDEXER_SCHEMA = 'indexers/fetchIndexerSchema';
|
||||
export const SELECT_INDEXER_SCHEMA = 'indexers/selectIndexerSchema';
|
||||
export const CLONE_INDEXER = 'indexers/cloneIndexer';
|
||||
export const SET_INDEXER_VALUE = 'indexers/setIndexerValue';
|
||||
export const SET_INDEXER_FIELD_VALUE = 'indexers/setIndexerFieldValue';
|
||||
export const SAVE_INDEXER = 'indexers/saveIndexer';
|
||||
export const CANCEL_SAVE_INDEXER = 'indexers/cancelSaveIndexer';
|
||||
export const DELETE_INDEXER = 'indexers/deleteIndexer';
|
||||
export const TEST_INDEXER = 'indexers/testIndexer';
|
||||
export const CANCEL_TEST_INDEXER = 'indexers/cancelTestIndexer';
|
||||
export const TEST_ALL_INDEXERS = 'indexers/testAllIndexers';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchIndexers = createThunk(FETCH_INDEXERS);
|
||||
export const fetchIndexerSchema = createThunk(FETCH_INDEXER_SCHEMA);
|
||||
export const selectIndexerSchema = createAction(SELECT_INDEXER_SCHEMA);
|
||||
export const cloneIndexer = createAction(CLONE_INDEXER);
|
||||
|
||||
export const saveIndexer = createThunk(SAVE_INDEXER);
|
||||
export const cancelSaveIndexer = createThunk(CANCEL_SAVE_INDEXER);
|
||||
export const deleteIndexer = createThunk(DELETE_INDEXER);
|
||||
export const testIndexer = createThunk(TEST_INDEXER);
|
||||
export const cancelTestIndexer = createThunk(CANCEL_TEST_INDEXER);
|
||||
export const testAllIndexers = createThunk(TEST_ALL_INDEXERS);
|
||||
|
||||
export const setIndexerValue = createAction(SET_INDEXER_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const setIndexerFieldValue = createAction(SET_INDEXER_FIELD_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[FETCH_INDEXERS]: createFetchHandler(section, '/indexer'),
|
||||
[FETCH_INDEXER_SCHEMA]: createFetchSchemaHandler(section, '/indexer/schema'),
|
||||
|
||||
[SAVE_INDEXER]: createSaveProviderHandler(section, '/indexer'),
|
||||
[CANCEL_SAVE_INDEXER]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_INDEXER]: createRemoveItemHandler(section, '/indexer'),
|
||||
[TEST_INDEXER]: createTestProviderHandler(section, '/indexer'),
|
||||
[CANCEL_TEST_INDEXER]: createCancelTestProviderHandler(section),
|
||||
[TEST_ALL_INDEXERS]: createTestAllProvidersHandler(section, '/indexer')
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({
|
||||
[SET_INDEXER_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_INDEXER_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
|
||||
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
selectedSchema.enableRss = selectedSchema.supportsRss;
|
||||
selectedSchema.enableAutomaticSearch = selectedSchema.supportsSearch;
|
||||
selectedSchema.enableInteractiveSearch = selectedSchema.supportsSearch;
|
||||
|
||||
return selectedSchema;
|
||||
});
|
||||
},
|
||||
|
||||
[CLONE_INDEXER]: function(state, { payload }) {
|
||||
const id = payload.id;
|
||||
const newState = getSectionState(state, section);
|
||||
const item = newState.items.find((i) => i.id === id);
|
||||
|
||||
// Use selectedSchema so `createProviderSettingsSelector` works properly
|
||||
const selectedSchema = { ...item };
|
||||
delete selectedSchema.id;
|
||||
delete selectedSchema.name;
|
||||
|
||||
selectedSchema.fields = selectedSchema.fields.map((field) => {
|
||||
return { ...field };
|
||||
});
|
||||
|
||||
newState.selectedSchema = selectedSchema;
|
||||
|
||||
// Set the name in pendingChanges
|
||||
newState.pendingChanges = {
|
||||
name: `${item.name} - Copy`
|
||||
};
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
}
|
||||
}, defaultState, section);
|
272
frontend/src/Store/Actions/indexerIndexActions.js
Normal file
272
frontend/src/Store/Actions/indexerIndexActions.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import { filterPredicates, filters, sortPredicates } from './movieActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'indexerIndex';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isDeleting: false,
|
||||
deleteError: null,
|
||||
sortKey: 'sortTitle',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortTitle',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
|
||||
tableOptions: {
|
||||
showSearchAction: false
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
name: 'select',
|
||||
columnLabel: 'Select',
|
||||
isSortable: false,
|
||||
isVisible: true,
|
||||
isModifiable: false,
|
||||
isHidden: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
columnLabel: translate('ReleaseStatus'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Indexer Name',
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'protocol',
|
||||
label: translate('Protocol'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'privacy',
|
||||
label: translate('Privacy'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'capabilities',
|
||||
label: 'Capabilities',
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: translate('Actions'),
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
sortPredicates: {
|
||||
...sortPredicates,
|
||||
|
||||
studio: function(item) {
|
||||
const studio = item.studio;
|
||||
|
||||
return studio ? studio.toLowerCase() : '';
|
||||
},
|
||||
|
||||
collection: function(item) {
|
||||
const { collection ={} } = item;
|
||||
|
||||
return collection.name;
|
||||
},
|
||||
|
||||
ratings: function(item) {
|
||||
const { ratings = {} } = item;
|
||||
|
||||
return ratings.value;
|
||||
}
|
||||
},
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters,
|
||||
filterPredicates,
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: translate('Monitored'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Indexer Name',
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'indexerIndex.sortKey',
|
||||
'indexerIndex.sortDirection',
|
||||
'indexerIndex.selectedFilterKey',
|
||||
'indexerIndex.customFilters',
|
||||
'indexerIndex.view',
|
||||
'indexerIndex.columns',
|
||||
'indexerIndex.tableOptions'
|
||||
];
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const SET_MOVIE_SORT = 'indexerIndex/setMovieSort';
|
||||
export const SET_MOVIE_FILTER = 'indexerIndex/setMovieFilter';
|
||||
export const SET_MOVIE_VIEW = 'indexerIndex/setMovieView';
|
||||
export const SET_MOVIE_TABLE_OPTION = 'indexerIndex/setMovieTableOption';
|
||||
export const SAVE_MOVIE_EDITOR = 'indexerIndex/saveMovieEditor';
|
||||
export const BULK_DELETE_MOVIE = 'indexerIndex/bulkDeleteMovie';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const setMovieSort = createAction(SET_MOVIE_SORT);
|
||||
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
|
||||
export const setMovieView = createAction(SET_MOVIE_VIEW);
|
||||
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
||||
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
||||
export const bulkDeleteMovie = createThunk(BULK_DELETE_MOVIE);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie/editor',
|
||||
method: 'PUT',
|
||||
data: JSON.stringify(payload),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
...data.map((movie) => {
|
||||
return updateItem({
|
||||
id: movie.id,
|
||||
section: 'movies',
|
||||
...movie
|
||||
});
|
||||
}),
|
||||
|
||||
set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
})
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
[BULK_DELETE_MOVIE]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie/editor',
|
||||
method: 'DELETE',
|
||||
data: JSON.stringify(payload),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
// SignaR will take care of removing the movie from the collection
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: false,
|
||||
deleteError: null
|
||||
}));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: false,
|
||||
deleteError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[SET_MOVIE_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_MOVIE_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
[SET_MOVIE_VIEW]: function(state, { payload }) {
|
||||
return Object.assign({}, state, { view: payload.view });
|
||||
},
|
||||
|
||||
[SET_MOVIE_TABLE_OPTION]: createSetTableOptionReducer(section)
|
||||
|
||||
}, defaultState, section);
|
@@ -1,507 +0,0 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
import { filterPredicates, filters, sortPredicates } from './movieActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'movieIndex';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isDeleting: false,
|
||||
deleteError: null,
|
||||
sortKey: 'sortTitle',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortTitle',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
|
||||
tableOptions: {
|
||||
showSearchAction: false
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
name: 'select',
|
||||
columnLabel: 'Select',
|
||||
isSortable: false,
|
||||
isVisible: true,
|
||||
isModifiable: false,
|
||||
isHidden: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
columnLabel: translate('ReleaseStatus'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'sortTitle',
|
||||
label: translate('MovieTitle'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'collection',
|
||||
label: translate('Collection'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'studio',
|
||||
label: translate('Studio'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: translate('QualityProfile'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
label: translate('Year'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'inCinemas',
|
||||
label: translate('InCinemas'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'digitalRelease',
|
||||
label: translate('DigitalRelease'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'physicalRelease',
|
||||
label: translate('PhysicalRelease'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'runtime',
|
||||
label: translate('Runtime'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'minimumAvailability',
|
||||
label: translate('MinAvailability'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
label: translate('Path'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'sizeOnDisk',
|
||||
label: translate('SizeOnDisk'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: translate('Genres'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'movieStatus',
|
||||
label: translate('Status'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'ratings',
|
||||
label: translate('Ratings'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'certification',
|
||||
label: translate('Certification'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: translate('Actions'),
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
sortPredicates: {
|
||||
...sortPredicates,
|
||||
|
||||
studio: function(item) {
|
||||
const studio = item.studio;
|
||||
|
||||
return studio ? studio.toLowerCase() : '';
|
||||
},
|
||||
|
||||
collection: function(item) {
|
||||
const { collection ={} } = item;
|
||||
|
||||
return collection.name;
|
||||
},
|
||||
|
||||
ratings: function(item) {
|
||||
const { ratings = {} } = item;
|
||||
|
||||
return ratings.value;
|
||||
}
|
||||
},
|
||||
|
||||
selectedFilterKey: 'all',
|
||||
|
||||
filters,
|
||||
filterPredicates,
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: translate('Monitored'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: translate('Status'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.MOVIE_STATUS
|
||||
},
|
||||
{
|
||||
name: 'studio',
|
||||
label: translate('Studio'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
optionsSelector: function(items) {
|
||||
const tagList = items.reduce((acc, movie) => {
|
||||
if (movie.studio) {
|
||||
acc.push({
|
||||
id: movie.studio,
|
||||
name: movie.studio
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return tagList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'collection',
|
||||
label: translate('Collection'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const collectionList = items.reduce((acc, movie) => {
|
||||
if (movie.collection) {
|
||||
acc.push({
|
||||
id: movie.collection.name,
|
||||
name: movie.collection.name
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return collectionList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: translate('QualityProfile'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
label: translate('Year'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'inCinemas',
|
||||
label: translate('InCinemas'),
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'physicalRelease',
|
||||
label: translate('PhysicalRelease'),
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'digitalRelease',
|
||||
label: translate('DigitalRelease'),
|
||||
type: filterBuilderTypes.DATE,
|
||||
valueType: filterBuilderValueTypes.DATE
|
||||
},
|
||||
{
|
||||
name: 'runtime',
|
||||
label: translate('Runtime'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
label: translate('Path'),
|
||||
type: filterBuilderTypes.STRING
|
||||
},
|
||||
{
|
||||
name: 'sizeOnDisk',
|
||||
label: translate('SizeOnDisk'),
|
||||
type: filterBuilderTypes.NUMBER,
|
||||
valueType: filterBuilderValueTypes.BYTES
|
||||
},
|
||||
{
|
||||
name: 'genres',
|
||||
label: translate('Genres'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
optionsSelector: function(items) {
|
||||
const genreList = items.reduce((acc, movie) => {
|
||||
movie.genres.forEach((genre) => {
|
||||
acc.push({
|
||||
id: genre,
|
||||
name: genre
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return genreList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ratings',
|
||||
label: translate('Ratings'),
|
||||
type: filterBuilderTypes.NUMBER
|
||||
},
|
||||
{
|
||||
name: 'certification',
|
||||
label: translate('Certification'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
optionsSelector: function(items) {
|
||||
const certificationList = items.reduce((acc, movie) => {
|
||||
if (movie.certification) {
|
||||
acc.push({
|
||||
id: movie.certification,
|
||||
name: movie.certification
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return certificationList.sort(sortByName);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: translate('Tags'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'movieIndex.sortKey',
|
||||
'movieIndex.sortDirection',
|
||||
'movieIndex.selectedFilterKey',
|
||||
'movieIndex.customFilters',
|
||||
'movieIndex.view',
|
||||
'movieIndex.columns',
|
||||
'movieIndex.posterOptions',
|
||||
'movieIndex.overviewOptions',
|
||||
'movieIndex.tableOptions'
|
||||
];
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const SET_MOVIE_SORT = 'movieIndex/setMovieSort';
|
||||
export const SET_MOVIE_FILTER = 'movieIndex/setMovieFilter';
|
||||
export const SET_MOVIE_VIEW = 'movieIndex/setMovieView';
|
||||
export const SET_MOVIE_TABLE_OPTION = 'movieIndex/setMovieTableOption';
|
||||
export const SET_MOVIE_POSTER_OPTION = 'movieIndex/setMoviePosterOption';
|
||||
export const SET_MOVIE_OVERVIEW_OPTION = 'movieIndex/setMovieOverviewOption';
|
||||
export const SAVE_MOVIE_EDITOR = 'movieIndex/saveMovieEditor';
|
||||
export const BULK_DELETE_MOVIE = 'movieIndex/bulkDeleteMovie';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const setMovieSort = createAction(SET_MOVIE_SORT);
|
||||
export const setMovieFilter = createAction(SET_MOVIE_FILTER);
|
||||
export const setMovieView = createAction(SET_MOVIE_VIEW);
|
||||
export const setMovieTableOption = createAction(SET_MOVIE_TABLE_OPTION);
|
||||
export const setMoviePosterOption = createAction(SET_MOVIE_POSTER_OPTION);
|
||||
export const setMovieOverviewOption = createAction(SET_MOVIE_OVERVIEW_OPTION);
|
||||
export const saveMovieEditor = createThunk(SAVE_MOVIE_EDITOR);
|
||||
export const bulkDeleteMovie = createThunk(BULK_DELETE_MOVIE);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
[SAVE_MOVIE_EDITOR]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie/editor',
|
||||
method: 'PUT',
|
||||
data: JSON.stringify(payload),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
...data.map((movie) => {
|
||||
return updateItem({
|
||||
id: movie.id,
|
||||
section: 'movies',
|
||||
...movie
|
||||
});
|
||||
}),
|
||||
|
||||
set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
})
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
[BULK_DELETE_MOVIE]: function(getState, payload, dispatch) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/movie/editor',
|
||||
method: 'DELETE',
|
||||
data: JSON.stringify(payload),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done(() => {
|
||||
// SignaR will take care of removing the movie from the collection
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: false,
|
||||
deleteError: null
|
||||
}));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isDeleting: false,
|
||||
deleteError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[SET_MOVIE_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_MOVIE_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
|
||||
[SET_MOVIE_VIEW]: function(state, { payload }) {
|
||||
return Object.assign({}, state, { view: payload.view });
|
||||
},
|
||||
|
||||
[SET_MOVIE_TABLE_OPTION]: createSetTableOptionReducer(section),
|
||||
|
||||
[SET_MOVIE_POSTER_OPTION]: function(state, { payload }) {
|
||||
const posterOptions = state.posterOptions;
|
||||
|
||||
return {
|
||||
...state,
|
||||
posterOptions: {
|
||||
...posterOptions,
|
||||
...payload
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
[SET_MOVIE_OVERVIEW_OPTION]: function(state, { payload }) {
|
||||
const overviewOptions = state.overviewOptions;
|
||||
|
||||
return {
|
||||
...state,
|
||||
overviewOptions: {
|
||||
...overviewOptions,
|
||||
...payload
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}, defaultState, section);
|
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -7,6 +9,7 @@ import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -25,6 +28,59 @@ export const defaultState = {
|
||||
items: [],
|
||||
sortKey: 'releaseWeight',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
|
||||
columns: [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: translate('Source'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: translate('Age'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: translate('Title'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: translate('Indexer'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: translate('Peers'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexerFlags',
|
||||
label: React.createElement(Icon, { name: icons.FLAG }),
|
||||
columnLabel: 'Indexer Flags',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: translate('Actions'),
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
}
|
||||
],
|
||||
|
||||
sortPredicates: {
|
||||
age: function(item, direction) {
|
||||
return item.ageMinutes;
|
||||
@@ -180,6 +236,7 @@ export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||
export const SET_RELEASES_FILTER = 'releases/setMovieReleasesFilter';
|
||||
export const SET_RELEASES_TABLE_OPTION = 'releases/setReleasesTableOption';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
@@ -191,11 +248,12 @@ export const clearReleases = createAction(CLEAR_RELEASES);
|
||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||
export const setReleasesTableOption = createAction(SET_RELEASES_TABLE_OPTION);
|
||||
|
||||
//
|
||||
// Helpers
|
||||
|
||||
const fetchReleasesHelper = createFetchHandler(section, '/release');
|
||||
const fetchReleasesHelper = createFetchHandler(section, '/search');
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
@@ -275,6 +333,8 @@ export const reducers = createHandleActions({
|
||||
},
|
||||
|
||||
[SET_RELEASES_FILTER]: createSetClientSideCollectionFilterReducer(section),
|
||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section)
|
||||
[SET_RELEASES_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
|
||||
[SET_RELEASES_TABLE_OPTION]: createSetTableOptionReducer(section)
|
||||
|
||||
}, defaultState, section);
|
||||
|
@@ -4,7 +4,6 @@ import createHandleActions from './Creators/createHandleActions';
|
||||
import general from './Settings/general';
|
||||
import indexerFlags from './Settings/indexerFlags';
|
||||
import indexerOptions from './Settings/indexerOptions';
|
||||
import indexers from './Settings/indexers';
|
||||
import languages from './Settings/languages';
|
||||
import notifications from './Settings/notifications';
|
||||
import ui from './Settings/ui';
|
||||
@@ -12,7 +11,6 @@ import ui from './Settings/ui';
|
||||
export * from './Settings/general';
|
||||
export * from './Settings/indexerFlags';
|
||||
export * from './Settings/indexerOptions';
|
||||
export * from './Settings/indexers';
|
||||
export * from './Settings/languages';
|
||||
export * from './Settings/notifications';
|
||||
export * from './Settings/ui';
|
||||
@@ -31,7 +29,6 @@ export const defaultState = {
|
||||
general: general.defaultState,
|
||||
indexerFlags: indexerFlags.defaultState,
|
||||
indexerOptions: indexerOptions.defaultState,
|
||||
indexers: indexers.defaultState,
|
||||
languages: languages.defaultState,
|
||||
notifications: notifications.defaultState,
|
||||
ui: ui.defaultState
|
||||
@@ -58,7 +55,6 @@ export const actionHandlers = handleThunks({
|
||||
...general.actionHandlers,
|
||||
...indexerFlags.actionHandlers,
|
||||
...indexerOptions.actionHandlers,
|
||||
...indexers.actionHandlers,
|
||||
...languages.actionHandlers,
|
||||
...notifications.actionHandlers,
|
||||
...ui.actionHandlers
|
||||
@@ -76,7 +72,6 @@ export const reducers = createHandleActions({
|
||||
...general.reducers,
|
||||
...indexerFlags.reducers,
|
||||
...indexerOptions.reducers,
|
||||
...indexers.reducers,
|
||||
...languages.reducers,
|
||||
...notifications.reducers,
|
||||
...ui.reducers
|
||||
|
Reference in New Issue
Block a user