mirror of
https://github.com/fergalmoran/Readarr.git
synced 2026-01-02 23:04:23 +00:00
New: Combine mass editor and author editor, enable book editor
This commit is contained in:
@@ -5,7 +5,6 @@ import BlocklistConnector from 'Activity/Blocklist/BlocklistConnector';
|
|||||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||||
import AuthorDetailsPageConnector from 'Author/Details/AuthorDetailsPageConnector';
|
import AuthorDetailsPageConnector from 'Author/Details/AuthorDetailsPageConnector';
|
||||||
import AuthorEditorConnector from 'Author/Editor/AuthorEditorConnector';
|
|
||||||
import AuthorIndexConnector from 'Author/Index/AuthorIndexConnector';
|
import AuthorIndexConnector from 'Author/Index/AuthorIndexConnector';
|
||||||
import BookDetailsPageConnector from 'Book/Details/BookDetailsPageConnector';
|
import BookDetailsPageConnector from 'Book/Details/BookDetailsPageConnector';
|
||||||
import BookIndexConnector from 'Book/Index/BookIndexConnector';
|
import BookIndexConnector from 'Book/Index/BookIndexConnector';
|
||||||
@@ -82,11 +81,6 @@ function AppRoutes(props) {
|
|||||||
component={AddNewItemConnector}
|
component={AddNewItemConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/authoreditor"
|
|
||||||
component={AuthorEditorConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact={true}
|
exact={true}
|
||||||
path="/shelf"
|
path="/shelf"
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class AuthorDetails extends Component {
|
|||||||
isDeleteAuthorModalOpen: false,
|
isDeleteAuthorModalOpen: false,
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
isMonitorOptionsModalOpen: false,
|
isMonitorOptionsModalOpen: false,
|
||||||
isBookEditorActive: false,
|
isEditorActive: false,
|
||||||
allExpanded: false,
|
allExpanded: false,
|
||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
expandedState: {},
|
expandedState: {},
|
||||||
@@ -160,7 +160,7 @@ class AuthorDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBookEditorTogglePress = () => {
|
onBookEditorTogglePress = () => {
|
||||||
this.setState({ isBookEditorActive: !this.state.isBookEditorActive });
|
this.setState({ isEditorActive: !this.state.isEditorActive });
|
||||||
}
|
}
|
||||||
|
|
||||||
onExpandAllPress = () => {
|
onExpandAllPress = () => {
|
||||||
@@ -249,7 +249,7 @@ class AuthorDetails extends Component {
|
|||||||
isDeleteAuthorModalOpen,
|
isDeleteAuthorModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
isMonitorOptionsModalOpen,
|
isMonitorOptionsModalOpen,
|
||||||
isBookEditorActive,
|
isEditorActive,
|
||||||
allSelected,
|
allSelected,
|
||||||
selectedState,
|
selectedState,
|
||||||
allExpanded,
|
allExpanded,
|
||||||
@@ -335,7 +335,7 @@ class AuthorDetails extends Component {
|
|||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
{
|
{
|
||||||
isBookEditorActive ?
|
isEditorActive ?
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('BookList')}
|
label={translate('BookList')}
|
||||||
iconName={icons.AUTHOR_CONTINUING}
|
iconName={icons.AUTHOR_CONTINUING}
|
||||||
@@ -349,7 +349,7 @@ class AuthorDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isBookEditorActive ?
|
isEditorActive ?
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
iconName={icons.CHECK_SQUARE}
|
iconName={icons.CHECK_SQUARE}
|
||||||
@@ -487,7 +487,7 @@ class AuthorDetails extends Component {
|
|||||||
onExpandPress={this.onExpandPress}
|
onExpandPress={this.onExpandPress}
|
||||||
setSelectedState={this.setSelectedState}
|
setSelectedState={this.setSelectedState}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
isBookEditorActive={isBookEditorActive}
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
@@ -585,7 +585,7 @@ class AuthorDetails extends Component {
|
|||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
|
|
||||||
{
|
{
|
||||||
isBookEditorActive &&
|
isEditorActive &&
|
||||||
<BookEditorFooter
|
<BookEditorFooter
|
||||||
bookIds={selectedBookIds}
|
bookIds={selectedBookIds}
|
||||||
selectedCount={selectedBookIds.length}
|
selectedCount={selectedBookIds.length}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
|
import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
|
||||||
import { saveBookEditor } from 'Store/Actions/bookEditorActions';
|
|
||||||
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
|
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
|
||||||
|
import { saveBookEditor } from 'Store/Actions/bookIndexActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||||
@@ -22,8 +22,8 @@ import AuthorDetails from './AuthorDetails';
|
|||||||
|
|
||||||
const selectBooks = createSelector(
|
const selectBooks = createSelector(
|
||||||
(state) => state.books,
|
(state) => state.books,
|
||||||
(state) => state.bookEditor,
|
(state) => state.bookIndex,
|
||||||
(books, editor) => {
|
(books, index) => {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -31,6 +31,13 @@ const selectBooks = createSelector(
|
|||||||
error
|
error
|
||||||
} = books;
|
} = books;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError
|
||||||
|
} = index;
|
||||||
|
|
||||||
const hasBooks = !!items.length;
|
const hasBooks = !!items.length;
|
||||||
const hasMonitoredBooks = items.some((e) => e.monitored);
|
const hasMonitoredBooks = items.some((e) => e.monitored);
|
||||||
|
|
||||||
@@ -40,7 +47,10 @@ const selectBooks = createSelector(
|
|||||||
booksError: error,
|
booksError: error,
|
||||||
hasBooks,
|
hasBooks,
|
||||||
hasMonitoredBooks,
|
hasMonitoredBooks,
|
||||||
...editor
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -112,7 +122,11 @@ function createMapStateToProps() {
|
|||||||
isBooksPopulated,
|
isBooksPopulated,
|
||||||
booksError,
|
booksError,
|
||||||
hasBooks,
|
hasBooks,
|
||||||
hasMonitoredBooks
|
hasMonitoredBooks,
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError
|
||||||
} = books;
|
} = books;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -172,6 +186,10 @@ function createMapStateToProps() {
|
|||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
booksError,
|
booksError,
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError,
|
||||||
seriesError,
|
seriesError,
|
||||||
bookFilesError,
|
bookFilesError,
|
||||||
hasBooks,
|
hasBooks,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class AuthorDetailsSeason extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
isBookEditorActive,
|
isEditorActive,
|
||||||
columns,
|
columns,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
@@ -88,7 +88,7 @@ class AuthorDetailsSeason extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let titleColumns = columns;
|
let titleColumns = columns;
|
||||||
if (!isBookEditorActive) {
|
if (!isEditorActive) {
|
||||||
titleColumns = columns.filter((x) => x.name !== 'select');
|
titleColumns = columns.filter((x) => x.name !== 'select');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class AuthorDetailsSeason extends Component {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
{...item}
|
{...item}
|
||||||
onMonitorBookPress={this.onMonitorBookPress}
|
onMonitorBookPress={this.onMonitorBookPress}
|
||||||
isBookEditorActive={isBookEditorActive}
|
isEditorActive={isEditorActive}
|
||||||
isSelected={selectedState[item.id]}
|
isSelected={selectedState[item.id]}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
/>
|
/>
|
||||||
@@ -132,7 +132,7 @@ AuthorDetailsSeason.propTypes = {
|
|||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isBookEditorActive: PropTypes.bool.isRequired,
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
selectedState: PropTypes.object.isRequired,
|
selectedState: PropTypes.object.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class BookRow extends Component {
|
|||||||
authorMonitored,
|
authorMonitored,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
bookFiles,
|
bookFiles,
|
||||||
isBookEditorActive,
|
isEditorActive,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
columns
|
columns
|
||||||
@@ -88,7 +88,7 @@ class BookRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBookEditorActive && name === 'select') {
|
if (isEditorActive && name === 'select') {
|
||||||
return (
|
return (
|
||||||
<TableSelectCell
|
<TableSelectCell
|
||||||
key={name}
|
key={name}
|
||||||
@@ -236,7 +236,7 @@ BookRow.propTypes = {
|
|||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
authorMonitored: PropTypes.bool.isRequired,
|
authorMonitored: PropTypes.bool.isRequired,
|
||||||
bookFiles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
bookFiles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isBookEditorActive: PropTypes.bool.isRequired,
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
onSelectedChange: PropTypes.func.isRequired,
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -1,280 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import NoAuthor from 'Author/NoAuthor';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
|
||||||
import PageContent from 'Components/Page/PageContent';
|
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
|
||||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
|
||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
|
||||||
import RetagAuthorModal from './AudioTags/RetagAuthorModal';
|
|
||||||
import AuthorEditorFilterModalConnector from './AuthorEditorFilterModalConnector';
|
|
||||||
import AuthorEditorFooter from './AuthorEditorFooter';
|
|
||||||
import AuthorEditorRowConnector from './AuthorEditorRowConnector';
|
|
||||||
import OrganizeAuthorModal from './Organize/OrganizeAuthorModal';
|
|
||||||
|
|
||||||
class AuthorEditor extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
allSelected: false,
|
|
||||||
allUnselected: false,
|
|
||||||
lastToggled: null,
|
|
||||||
selectedState: {},
|
|
||||||
isOrganizingAuthorModalOpen: false,
|
|
||||||
isRetaggingAuthorModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
isDeleting,
|
|
||||||
deleteError
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasFinishedDeleting = prevProps.isDeleting &&
|
|
||||||
!isDeleting &&
|
|
||||||
!deleteError;
|
|
||||||
|
|
||||||
if (hasFinishedDeleting) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
getSelectedIds = () => {
|
|
||||||
return getSelectedIds(this.state.selectedState);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
|
||||||
this.setState((state) => {
|
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSaveSelected = (changes) => {
|
|
||||||
this.props.onSaveSelected({
|
|
||||||
authorIds: this.getSelectedIds(),
|
|
||||||
...changes
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onOrganizeAuthorPress = () => {
|
|
||||||
this.setState({ isOrganizingAuthorModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onOrganizeAuthorModalClose = (organized) => {
|
|
||||||
this.setState({ isOrganizingAuthorModalOpen: false });
|
|
||||||
|
|
||||||
if (organized === true) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRetagAuthorPress = () => {
|
|
||||||
this.setState({ isRetaggingAuthorModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onRetagAuthorModalClose = (organized) => {
|
|
||||||
this.setState({ isRetaggingAuthorModalOpen: false });
|
|
||||||
|
|
||||||
if (organized === true) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
totalItems,
|
|
||||||
items,
|
|
||||||
columns,
|
|
||||||
selectedFilterKey,
|
|
||||||
filters,
|
|
||||||
customFilters,
|
|
||||||
sortKey,
|
|
||||||
sortDirection,
|
|
||||||
isSaving,
|
|
||||||
saveError,
|
|
||||||
isDeleting,
|
|
||||||
deleteError,
|
|
||||||
isOrganizingAuthor,
|
|
||||||
isRetaggingAuthor,
|
|
||||||
onTableOptionChange,
|
|
||||||
onSortPress,
|
|
||||||
onFilterSelect
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
allSelected,
|
|
||||||
allUnselected,
|
|
||||||
selectedState
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const selectedAuthorIds = this.getSelectedIds();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent title={translate('AuthorEditor')}>
|
|
||||||
<PageToolbar>
|
|
||||||
<PageToolbarSection />
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT}>
|
|
||||||
<TableOptionsModalWrapper
|
|
||||||
columns={columns}
|
|
||||||
onTableOptionChange={onTableOptionChange}
|
|
||||||
>
|
|
||||||
<PageToolbarButton
|
|
||||||
label={translate('Options')}
|
|
||||||
iconName={icons.TABLE}
|
|
||||||
/>
|
|
||||||
</TableOptionsModalWrapper>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
|
||||||
|
|
||||||
<FilterMenu
|
|
||||||
alignMenu={align.RIGHT}
|
|
||||||
selectedFilterKey={selectedFilterKey}
|
|
||||||
filters={filters}
|
|
||||||
customFilters={customFilters}
|
|
||||||
filterModalConnectorComponent={AuthorEditorFilterModalConnector}
|
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
/>
|
|
||||||
</PageToolbarSection>
|
|
||||||
</PageToolbar>
|
|
||||||
|
|
||||||
<PageContentBody>
|
|
||||||
{
|
|
||||||
isFetching && !isPopulated &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div>{getErrorMessage(error, 'Failed to load author from API')}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !!items.length &&
|
|
||||||
<div>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
sortKey={sortKey}
|
|
||||||
sortDirection={sortDirection}
|
|
||||||
selectAll={true}
|
|
||||||
allSelected={allSelected}
|
|
||||||
allUnselected={allUnselected}
|
|
||||||
onSortPress={onSortPress}
|
|
||||||
onSelectAllChange={this.onSelectAllChange}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<AuthorEditorRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
columns={columns}
|
|
||||||
isSelected={selectedState[item.id]}
|
|
||||||
onSelectedChange={this.onSelectedChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !items.length &&
|
|
||||||
<NoAuthor totalItems={totalItems} />
|
|
||||||
}
|
|
||||||
</PageContentBody>
|
|
||||||
|
|
||||||
<AuthorEditorFooter
|
|
||||||
authorIds={selectedAuthorIds}
|
|
||||||
selectedCount={selectedAuthorIds.length}
|
|
||||||
isSaving={isSaving}
|
|
||||||
saveError={saveError}
|
|
||||||
isDeleting={isDeleting}
|
|
||||||
deleteError={deleteError}
|
|
||||||
isOrganizingAuthor={isOrganizingAuthor}
|
|
||||||
isRetaggingAuthor={isRetaggingAuthor}
|
|
||||||
columns={columns}
|
|
||||||
showMetadataProfile={columns.find((column) => column.name === 'metadataProfileId').isVisible}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
onOrganizeAuthorPress={this.onOrganizeAuthorPress}
|
|
||||||
onRetagAuthorPress={this.onRetagAuthorPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<OrganizeAuthorModal
|
|
||||||
isOpen={this.state.isOrganizingAuthorModalOpen}
|
|
||||||
authorIds={selectedAuthorIds}
|
|
||||||
onModalClose={this.onOrganizeAuthorModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RetagAuthorModal
|
|
||||||
isOpen={this.state.isRetaggingAuthorModalOpen}
|
|
||||||
authorIds={selectedAuthorIds}
|
|
||||||
onModalClose={this.onRetagAuthorModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorEditor.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
totalItems: PropTypes.number.isRequired,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
|
||||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
saveError: PropTypes.object,
|
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
|
||||||
deleteError: PropTypes.object,
|
|
||||||
isOrganizingAuthor: PropTypes.bool.isRequired,
|
|
||||||
isRetaggingAuthor: PropTypes.bool.isRequired,
|
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
|
||||||
onSortPress: PropTypes.func.isRequired,
|
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
|
||||||
onSaveSelected: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorEditor;
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import * as commandNames from 'Commands/commandNames';
|
|
||||||
import { saveAuthorEditor, setAuthorEditorFilter, setAuthorEditorSort, setAuthorEditorTableOption } from 'Store/Actions/authorEditorActions';
|
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import { fetchRootFolders } from 'Store/Actions/settingsActions';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
|
||||||
import AuthorEditor from './AuthorEditor';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createClientSideCollectionSelector('authors', 'authorEditor'),
|
|
||||||
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
|
||||||
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
|
||||||
(author, isOrganizingAuthor, isRetaggingAuthor) => {
|
|
||||||
return {
|
|
||||||
isOrganizingAuthor,
|
|
||||||
isRetaggingAuthor,
|
|
||||||
...author
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchSetAuthorEditorSort: setAuthorEditorSort,
|
|
||||||
dispatchSetAuthorEditorFilter: setAuthorEditorFilter,
|
|
||||||
dispatchSetAuthorEditorTableOption: setAuthorEditorTableOption,
|
|
||||||
dispatchSaveAuthorEditor: saveAuthorEditor,
|
|
||||||
dispatchFetchRootFolders: fetchRootFolders,
|
|
||||||
dispatchExecuteCommand: executeCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
class AuthorEditorConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatchFetchRootFolders();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSortPress = (sortKey) => {
|
|
||||||
this.props.dispatchSetAuthorEditorSort({ sortKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
onFilterSelect = (selectedFilterKey) => {
|
|
||||||
this.props.dispatchSetAuthorEditorFilter({ selectedFilterKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
|
||||||
this.props.dispatchSetAuthorEditorTableOption(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSaveSelected = (payload) => {
|
|
||||||
this.props.dispatchSaveAuthorEditor(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMoveSelected = (payload) => {
|
|
||||||
this.props.dispatchExecuteCommand({
|
|
||||||
name: commandNames.MOVE_AUTHOR,
|
|
||||||
...payload
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<AuthorEditor
|
|
||||||
{...this.props}
|
|
||||||
onSortPress={this.onSortPress}
|
|
||||||
onFilterSelect={this.onFilterSelect}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
onTableOptionChange={this.onTableOptionChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorEditorConnector.propTypes = {
|
|
||||||
dispatchSetAuthorEditorSort: PropTypes.func.isRequired,
|
|
||||||
dispatchSetAuthorEditorFilter: PropTypes.func.isRequired,
|
|
||||||
dispatchSetAuthorEditorTableOption: PropTypes.func.isRequired,
|
|
||||||
dispatchSaveAuthorEditor: PropTypes.func.isRequired,
|
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
|
||||||
dispatchExecuteCommand: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorEditorConnector);
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import FilterModal from 'Components/Filter/FilterModal';
|
|
||||||
import { setAuthorEditorFilter } from 'Store/Actions/authorEditorActions';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.authors.items,
|
|
||||||
(state) => state.authorEditor.filterBuilderProps,
|
|
||||||
(sectionItems, filterBuilderProps) => {
|
|
||||||
return {
|
|
||||||
sectionItems,
|
|
||||||
filterBuilderProps,
|
|
||||||
customFilterType: 'authorEditor'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchSetFilter: setAuthorEditorFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);
|
|
||||||
@@ -139,7 +139,6 @@ class AuthorEditorFooter extends Component {
|
|||||||
isDeleting,
|
isDeleting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
columns,
|
|
||||||
onOrganizeAuthorPress,
|
onOrganizeAuthorPress,
|
||||||
onRetagAuthorPress
|
onRetagAuthorPress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -179,21 +178,7 @@ class AuthorEditorFooter extends Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
className={styles.inputContainer}
|
||||||
>
|
>
|
||||||
<AuthorEditorFooterLabel
|
<AuthorEditorFooterLabel
|
||||||
@@ -209,13 +194,8 @@ class AuthorEditorFooter extends Component {
|
|||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'metadataProfileId') {
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
className={styles.inputContainer}
|
||||||
>
|
>
|
||||||
<AuthorEditorFooterLabel
|
<AuthorEditorFooterLabel
|
||||||
@@ -227,17 +207,13 @@ class AuthorEditorFooter extends Component {
|
|||||||
name="metadataProfileId"
|
name="metadataProfileId"
|
||||||
value={metadataProfileId}
|
value={metadataProfileId}
|
||||||
includeNoChange={true}
|
includeNoChange={true}
|
||||||
|
includeNone={true}
|
||||||
isDisabled={!selectedCount}
|
isDisabled={!selectedCount}
|
||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'path') {
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
className={styles.inputContainer}
|
||||||
>
|
>
|
||||||
<AuthorEditorFooterLabel
|
<AuthorEditorFooterLabel
|
||||||
@@ -254,12 +230,6 @@ class AuthorEditorFooter extends Component {
|
|||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<div className={styles.buttonContainerContent}>
|
<div className={styles.buttonContainerContent}>
|
||||||
@@ -348,7 +318,6 @@ AuthorEditorFooter.propTypes = {
|
|||||||
isOrganizingAuthor: PropTypes.bool.isRequired,
|
isOrganizingAuthor: PropTypes.bool.isRequired,
|
||||||
isRetaggingAuthor: PropTypes.bool.isRequired,
|
isRetaggingAuthor: PropTypes.bool.isRequired,
|
||||||
showMetadataProfile: PropTypes.bool.isRequired,
|
showMetadataProfile: PropTypes.bool.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onSaveSelected: PropTypes.func.isRequired,
|
onSaveSelected: PropTypes.func.isRequired,
|
||||||
onOrganizeAuthorPress: PropTypes.func.isRequired,
|
onOrganizeAuthorPress: PropTypes.func.isRequired,
|
||||||
onRetagAuthorPress: PropTypes.func.isRequired
|
onRetagAuthorPress: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { bulkDeleteAuthor } from 'Store/Actions/authorEditorActions';
|
import { bulkDeleteAuthor } from 'Store/Actions/authorIndexActions';
|
||||||
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
|
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
|
||||||
import DeleteAuthorModalContent from './DeleteAuthorModalContent';
|
import DeleteAuthorModalContent from './DeleteAuthorModalContent';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import RetagAuthorModal from 'Author/Editor/AudioTags/RetagAuthorModal';
|
||||||
|
import AuthorEditorFooter from 'Author/Editor/AuthorEditorFooter';
|
||||||
|
import OrganizeAuthorModal from 'Author/Editor/Organize/OrganizeAuthorModal';
|
||||||
import NoAuthor from 'Author/NoAuthor';
|
import NoAuthor from 'Author/NoAuthor';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
@@ -15,6 +18,9 @@ import { align, icons, sortDirections } from 'Helpers/Props';
|
|||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import AuthorIndexFooterConnector from './AuthorIndexFooterConnector';
|
import AuthorIndexFooterConnector from './AuthorIndexFooterConnector';
|
||||||
import AuthorIndexFilterMenu from './Menus/AuthorIndexFilterMenu';
|
import AuthorIndexFilterMenu from './Menus/AuthorIndexFilterMenu';
|
||||||
import AuthorIndexSortMenu from './Menus/AuthorIndexSortMenu';
|
import AuthorIndexSortMenu from './Menus/AuthorIndexSortMenu';
|
||||||
@@ -52,12 +58,20 @@ class AuthorIndex extends Component {
|
|||||||
jumpBarItems: { order: [] },
|
jumpBarItems: { order: [] },
|
||||||
jumpToCharacter: null,
|
jumpToCharacter: null,
|
||||||
isPosterOptionsModalOpen: false,
|
isPosterOptionsModalOpen: false,
|
||||||
isOverviewOptionsModalOpen: false
|
isOverviewOptionsModalOpen: false,
|
||||||
|
isEditorActive: false,
|
||||||
|
isOrganizingAuthorModalOpen: false,
|
||||||
|
isRetaggingAuthorModalOpen: false,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: false,
|
||||||
|
lastToggled: null,
|
||||||
|
selectedState: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -72,6 +86,7 @@ class AuthorIndex extends Component {
|
|||||||
hasDifferentItemsOrOrder(prevProps.items, items)
|
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||||
) {
|
) {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.jumpToCharacter != null) {
|
if (this.state.jumpToCharacter != null) {
|
||||||
@@ -86,6 +101,48 @@ class AuthorIndex extends Component {
|
|||||||
this.setState({ scroller: ref });
|
this.setState({ scroller: ref });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedIds = () => {
|
||||||
|
if (this.state.allUnselected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getSelectedIds(this.state.selectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedState() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const newSelectedState = {};
|
||||||
|
|
||||||
|
items.forEach((author) => {
|
||||||
|
const isItemSelected = selectedState[author.id];
|
||||||
|
|
||||||
|
if (isItemSelected) {
|
||||||
|
newSelectedState[author.id] = isItemSelected;
|
||||||
|
} else {
|
||||||
|
newSelectedState[author.id] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||||
|
const newStateCount = Object.keys(newSelectedState).length;
|
||||||
|
let isAllSelected = false;
|
||||||
|
let isAllUnselected = false;
|
||||||
|
|
||||||
|
if (selectedCount === 0) {
|
||||||
|
isAllUnselected = true;
|
||||||
|
} else if (selectedCount === newStateCount) {
|
||||||
|
isAllSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||||
|
}
|
||||||
|
|
||||||
setJumpBarItems() {
|
setJumpBarItems() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
@@ -149,10 +206,72 @@ class AuthorIndex extends Component {
|
|||||||
this.setState({ isOverviewOptionsModalOpen: false });
|
this.setState({ isOverviewOptionsModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEditorTogglePress = () => {
|
||||||
|
if (this.state.isEditorActive) {
|
||||||
|
this.setState({ isEditorActive: false });
|
||||||
|
} else {
|
||||||
|
const newState = selectAll(this.state.selectedState, false);
|
||||||
|
newState.isEditorActive = true;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onJumpBarItemPress = (jumpToCharacter) => {
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
this.setState({ jumpToCharacter });
|
this.setState({ jumpToCharacter });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectAllChange = ({ value }) => {
|
||||||
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectAllPress = () => {
|
||||||
|
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveSelected = (changes) => {
|
||||||
|
this.props.onSaveSelected({
|
||||||
|
authorIds: this.getSelectedIds(),
|
||||||
|
...changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onOrganizeAuthorPress = () => {
|
||||||
|
this.setState({ isOrganizingAuthorModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onOrganizeAuthorModalClose = (organized) => {
|
||||||
|
this.setState({ isOrganizingAuthorModalOpen: false });
|
||||||
|
|
||||||
|
if (organized === true) {
|
||||||
|
this.onSelectAllChange({ value: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRetagAuthorPress = () => {
|
||||||
|
this.setState({ isRetaggingAuthorModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onRetagAuthorModalClose = (organized) => {
|
||||||
|
this.setState({ isRetaggingAuthorModalOpen: false });
|
||||||
|
|
||||||
|
if (organized === true) {
|
||||||
|
this.onSelectAllChange({ value: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRefreshAuthorPress = () => {
|
||||||
|
const selectedIds = this.getSelectedIds();
|
||||||
|
const refreshIds = this.state.isEditorActive && selectedIds.length > 0 ? selectedIds : [];
|
||||||
|
|
||||||
|
this.props.onRefreshAuthorPress(refreshIds);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -172,11 +291,16 @@ class AuthorIndex extends Component {
|
|||||||
view,
|
view,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isOrganizingAuthor,
|
||||||
|
isRetaggingAuthor,
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError,
|
||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
onViewSelect,
|
onViewSelect,
|
||||||
onRefreshAuthorPress,
|
|
||||||
onRssSyncPress,
|
onRssSyncPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -186,23 +310,31 @@ class AuthorIndex extends Component {
|
|||||||
jumpBarItems,
|
jumpBarItems,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
isPosterOptionsModalOpen,
|
isPosterOptionsModalOpen,
|
||||||
isOverviewOptionsModalOpen
|
isOverviewOptionsModalOpen,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState,
|
||||||
|
allSelected,
|
||||||
|
allUnselected
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const selectedAuthorIds = this.getSelectedIds();
|
||||||
|
|
||||||
const ViewComponent = getViewComponent(view);
|
const ViewComponent = getViewComponent(view);
|
||||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||||
const hasNoAuthor = !totalItems;
|
const hasNoAuthor = !totalItems;
|
||||||
|
|
||||||
|
const refreshLabel = isEditorActive && selectedAuthorIds.length > 0 ? translate('UpdateSelected') : translate('UpdateAll');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<PageToolbar>
|
<PageToolbar>
|
||||||
<PageToolbarSection>
|
<PageToolbarSection>
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('UpdateAll')}
|
label={refreshLabel}
|
||||||
iconName={icons.REFRESH}
|
iconName={icons.REFRESH}
|
||||||
spinningName={icons.REFRESH}
|
spinningName={icons.REFRESH}
|
||||||
isSpinning={isRefreshingAuthor}
|
isSpinning={isRefreshingAuthor}
|
||||||
onPress={onRefreshAuthorPress}
|
onPress={this.onRefreshAuthorPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -213,6 +345,35 @@ class AuthorIndex extends Component {
|
|||||||
onPress={onRssSyncPress}
|
onPress={onRssSyncPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
{
|
||||||
|
isEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('AuthorIndex')}
|
||||||
|
iconName={icons.AUTHOR_CONTINUING}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onEditorTogglePress}
|
||||||
|
/> :
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('AuthorEditor')}
|
||||||
|
iconName={icons.EDIT}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onEditorTogglePress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
|
iconName={icons.CHECK_SQUARE}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onSelectAllPress}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
<PageToolbarSection
|
<PageToolbarSection
|
||||||
@@ -310,6 +471,12 @@ class AuthorIndex extends Component {
|
|||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
jumpToCharacter={jumpToCharacter}
|
jumpToCharacter={jumpToCharacter}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectedChange={this.onSelectedChange}
|
||||||
|
onSelectAllChange={this.onSelectAllChange}
|
||||||
|
selectedState={selectedState}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -332,6 +499,24 @@ class AuthorIndex extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded && isEditorActive &&
|
||||||
|
<AuthorEditorFooter
|
||||||
|
authorIds={selectedAuthorIds}
|
||||||
|
selectedCount={selectedAuthorIds.length}
|
||||||
|
isSaving={isSaving}
|
||||||
|
saveError={saveError}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
deleteError={deleteError}
|
||||||
|
isOrganizingAuthor={isOrganizingAuthor}
|
||||||
|
isRetaggingAuthor={isRetaggingAuthor}
|
||||||
|
showMetadataProfile={true}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
|
onOrganizeAuthorPress={this.onOrganizeAuthorPress}
|
||||||
|
onRetagAuthorPress={this.onRetagAuthorPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<AuthorIndexPosterOptionsModal
|
<AuthorIndexPosterOptionsModal
|
||||||
isOpen={isPosterOptionsModalOpen}
|
isOpen={isPosterOptionsModalOpen}
|
||||||
onModalClose={this.onPosterOptionsModalClose}
|
onModalClose={this.onPosterOptionsModalClose}
|
||||||
@@ -340,8 +525,20 @@ class AuthorIndex extends Component {
|
|||||||
<AuthorIndexOverviewOptionsModal
|
<AuthorIndexOverviewOptionsModal
|
||||||
isOpen={isOverviewOptionsModalOpen}
|
isOpen={isOverviewOptionsModalOpen}
|
||||||
onModalClose={this.onOverviewOptionsModalClose}
|
onModalClose={this.onOverviewOptionsModalClose}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<OrganizeAuthorModal
|
||||||
|
isOpen={this.state.isOrganizingAuthorModalOpen}
|
||||||
|
authorIds={selectedAuthorIds}
|
||||||
|
onModalClose={this.onOrganizeAuthorModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RetagAuthorModal
|
||||||
|
isOpen={this.state.isRetaggingAuthorModalOpen}
|
||||||
|
authorIds={selectedAuthorIds}
|
||||||
|
onModalClose={this.onRetagAuthorModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -361,14 +558,21 @@ AuthorIndex.propTypes = {
|
|||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
isRefreshingAuthor: PropTypes.bool.isRequired,
|
isRefreshingAuthor: PropTypes.bool.isRequired,
|
||||||
|
isOrganizingAuthor: PropTypes.bool.isRequired,
|
||||||
|
isRetaggingAuthor: PropTypes.bool.isRequired,
|
||||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
deleteError: PropTypes.object,
|
||||||
onSortSelect: PropTypes.func.isRequired,
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onViewSelect: PropTypes.func.isRequired,
|
onViewSelect: PropTypes.func.isRequired,
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
onRefreshAuthorPress: PropTypes.func.isRequired,
|
||||||
onRssSyncPress: PropTypes.func.isRequired,
|
onRssSyncPress: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired
|
onScroll: PropTypes.func.isRequired,
|
||||||
|
onSaveSelected: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorIndex;
|
export default AuthorIndex;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
import { setAuthorFilter, setAuthorSort, setAuthorTableOption, setAuthorView } from 'Store/Actions/authorIndexActions';
|
import { saveAuthorEditor, setAuthorFilter, setAuthorSort, setAuthorTableOption, setAuthorView } from 'Store/Actions/authorIndexActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createAuthorClientSideCollectionItemsSelector from 'Store/Selectors/createAuthorClientSideCollectionItemsSelector';
|
import createAuthorClientSideCollectionItemsSelector from 'Store/Selectors/createAuthorClientSideCollectionItemsSelector';
|
||||||
@@ -18,16 +18,22 @@ function createMapStateToProps() {
|
|||||||
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
|
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
||||||
|
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(
|
(
|
||||||
author,
|
author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isOrganizingAuthor,
|
||||||
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...author,
|
...author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isOrganizingAuthor,
|
||||||
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
@@ -53,9 +59,14 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(setAuthorView({ view }));
|
dispatch(setAuthorView({ view }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onRefreshAuthorPress() {
|
dispatchSaveAuthorEditor(payload) {
|
||||||
|
dispatch(saveAuthorEditor(payload));
|
||||||
|
},
|
||||||
|
|
||||||
|
onRefreshAuthorPress(items) {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.REFRESH_AUTHOR
|
name: commandNames.BULK_REFRESH_AUTHOR,
|
||||||
|
authorIds: items
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -76,6 +87,10 @@ class AuthorIndexConnector extends Component {
|
|||||||
this.props.dispatchSetAuthorView(view);
|
this.props.dispatchSetAuthorView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSaveSelected = (payload) => {
|
||||||
|
this.props.dispatchSaveAuthorEditor(payload);
|
||||||
|
}
|
||||||
|
|
||||||
onScroll = ({ scrollTop }) => {
|
onScroll = ({ scrollTop }) => {
|
||||||
scrollPositions.authorIndex = scrollTop;
|
scrollPositions.authorIndex = scrollTop;
|
||||||
}
|
}
|
||||||
@@ -89,6 +104,7 @@ class AuthorIndexConnector extends Component {
|
|||||||
{...this.props}
|
{...this.props}
|
||||||
onViewSelect={this.onViewSelect}
|
onViewSelect={this.onViewSelect}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -97,7 +113,8 @@ class AuthorIndexConnector extends Component {
|
|||||||
AuthorIndexConnector.propTypes = {
|
AuthorIndexConnector.propTypes = {
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
dispatchSetAuthorView: PropTypes.func.isRequired
|
dispatchSetAuthorView: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveAuthorEditor: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withScrollPosition(
|
export default withScrollPosition(
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ $hoverScale: 1.05;
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
.posterContainer {
|
.posterContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import AuthorPoster from 'Author/AuthorPoster';
|
|||||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||||
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
@@ -67,6 +68,15 @@ class AuthorIndexOverview extends Component {
|
|||||||
this.setState({ isDeleteAuthorModalOpen: false });
|
this.setState({ isDeleteAuthorModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = ({ value, shiftKey }) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
onSelectedChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSelectedChange({ id, value, shiftKey });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -97,6 +107,8 @@ class AuthorIndexOverview extends Component {
|
|||||||
isSearchingAuthor,
|
isSearchingAuthor,
|
||||||
onRefreshAuthorPress,
|
onRefreshAuthorPress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -127,6 +139,18 @@ class AuthorIndexOverview extends Component {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{
|
||||||
|
isEditorActive &&
|
||||||
|
<div className={styles.editorSelect}>
|
||||||
|
<CheckInput
|
||||||
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
status === 'ended' &&
|
status === 'ended' &&
|
||||||
<div
|
<div
|
||||||
@@ -270,7 +294,10 @@ AuthorIndexOverview.propTypes = {
|
|||||||
isRefreshingAuthor: PropTypes.bool.isRequired,
|
isRefreshingAuthor: PropTypes.bool.isRequired,
|
||||||
isSearchingAuthor: PropTypes.bool.isRequired,
|
isSearchingAuthor: PropTypes.bool.isRequired,
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
onRefreshAuthorPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorIndexOverview.defaultProps = {
|
AuthorIndexOverview.defaultProps = {
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ class AuthorIndexOverviews extends Component {
|
|||||||
sortKey,
|
sortKey,
|
||||||
overviewOptions,
|
overviewOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
scrollTop
|
scrollTop,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -91,6 +93,8 @@ class AuthorIndexOverviews extends Component {
|
|||||||
(prevState.width !== width ||
|
(prevState.width !== width ||
|
||||||
prevState.rowHeight !== rowHeight ||
|
prevState.rowHeight !== rowHeight ||
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items) ||
|
hasDifferentItemsOrOrder(prevProps.items, items) ||
|
||||||
|
prevProps.isEditorActive !== isEditorActive ||
|
||||||
|
prevProps.selectedState !== selectedState ||
|
||||||
prevProps.overviewOptions.showTitle !== overviewOptions.showTitle)) {
|
prevProps.overviewOptions.showTitle !== overviewOptions.showTitle)) {
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
@@ -148,7 +152,10 @@ class AuthorIndexOverviews extends Component {
|
|||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
isSmallScreen
|
isSmallScreen,
|
||||||
|
selectedState,
|
||||||
|
isEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -184,6 +191,9 @@ class AuthorIndexOverviews extends Component {
|
|||||||
authorId={author.id}
|
authorId={author.id}
|
||||||
qualityProfileId={author.qualityProfileId}
|
qualityProfileId={author.qualityProfileId}
|
||||||
metadataProfileId={author.metadataProfileId}
|
metadataProfileId={author.metadataProfileId}
|
||||||
|
isSelected={selectedState[author.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -264,7 +274,10 @@ AuthorIndexOverviews.propTypes = {
|
|||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorIndexOverviews;
|
export default AuthorIndexOverviews;
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ $hoverScale: 1.05;
|
|||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AuthorPoster from 'Author/AuthorPoster';
|
|||||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||||
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
@@ -63,6 +64,15 @@ class AuthorIndexPoster extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = ({ value, shiftKey }) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
onSelectedChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSelectedChange({ id, value, shiftKey });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -92,6 +102,9 @@ class AuthorIndexPoster extends Component {
|
|||||||
isSearchingAuthor,
|
isSearchingAuthor,
|
||||||
onRefreshAuthorPress,
|
onRefreshAuthorPress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
|
onSelectedChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -120,6 +133,18 @@ class AuthorIndexPoster extends Component {
|
|||||||
<div>
|
<div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{
|
||||||
|
isEditorActive &&
|
||||||
|
<div className={styles.editorSelect}>
|
||||||
|
<CheckInput
|
||||||
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<Label className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
className={styles.action}
|
className={styles.action}
|
||||||
@@ -282,7 +307,10 @@ AuthorIndexPoster.propTypes = {
|
|||||||
isRefreshingAuthor: PropTypes.bool.isRequired,
|
isRefreshingAuthor: PropTypes.bool.isRequired,
|
||||||
isSearchingAuthor: PropTypes.bool.isRequired,
|
isSearchingAuthor: PropTypes.bool.isRequired,
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
onRefreshAuthorPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorIndexPoster.defaultProps = {
|
AuthorIndexPoster.defaultProps = {
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ class AuthorIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
scrollTop
|
isEditorActive,
|
||||||
|
scrollTop,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -138,6 +140,8 @@ class AuthorIndexPosters extends Component {
|
|||||||
prevState.columnCount !== columnCount ||
|
prevState.columnCount !== columnCount ||
|
||||||
prevState.rowHeight !== rowHeight ||
|
prevState.rowHeight !== rowHeight ||
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items)) ||
|
hasDifferentItemsOrOrder(prevProps.items, items)) ||
|
||||||
|
prevProps.isEditorActive !== isEditorActive ||
|
||||||
|
prevProps.selectedState !== selectedState ||
|
||||||
prevProps.posterOptions.showTitle !== posterOptions.showTitle) {
|
prevProps.posterOptions.showTitle !== posterOptions.showTitle) {
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
@@ -198,7 +202,10 @@ class AuthorIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat
|
timeFormat,
|
||||||
|
selectedState,
|
||||||
|
isEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -246,6 +253,9 @@ class AuthorIndexPosters extends Component {
|
|||||||
authorId={author.id}
|
authorId={author.id}
|
||||||
qualityProfileId={author.qualityProfileId}
|
qualityProfileId={author.qualityProfileId}
|
||||||
metadataProfileId={author.metadataProfileId}
|
metadataProfileId={author.metadataProfileId}
|
||||||
|
isSelected={selectedState[author.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -328,7 +338,10 @@ AuthorIndexPosters.propTypes = {
|
|||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorIndexPosters;
|
export default AuthorIndexPosters;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import AuthorIndexTableOptionsConnector from './AuthorIndexTableOptionsConnector';
|
import AuthorIndexTableOptionsConnector from './AuthorIndexTableOptionsConnector';
|
||||||
import hasGrowableColumns from './hasGrowableColumns';
|
import hasGrowableColumns from './hasGrowableColumns';
|
||||||
@@ -15,6 +16,10 @@ function AuthorIndexHeader(props) {
|
|||||||
showBanners,
|
showBanners,
|
||||||
columns,
|
columns,
|
||||||
onTableOptionChange,
|
onTableOptionChange,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isEditorActive,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -33,6 +38,21 @@ function AuthorIndexHeader(props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'select') {
|
||||||
|
if (isEditorActive) {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectAllHeaderCell
|
||||||
|
key={name}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'actions') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeaderCell
|
<VirtualTableHeaderCell
|
||||||
@@ -80,6 +100,10 @@ function AuthorIndexHeader(props) {
|
|||||||
AuthorIndexHeader.propTypes = {
|
AuthorIndexHeader.propTypes = {
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
showBanners: PropTypes.bool.isRequired
|
showBanners: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
|||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import getProgressBarKind from 'Utilities/Author/getProgressBarKind';
|
import getProgressBarKind from 'Utilities/Author/getProgressBarKind';
|
||||||
@@ -101,8 +102,11 @@ class AuthorIndexRow extends Component {
|
|||||||
columns,
|
columns,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
isSearchingAuthor,
|
isSearchingAuthor,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
onRefreshAuthorPress,
|
onRefreshAuthorPress,
|
||||||
onSearchPress
|
onSearchPress,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -131,6 +135,19 @@ class AuthorIndexRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEditorActive && name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectCell
|
||||||
|
inputClassName={styles.checkInput}
|
||||||
|
id={id}
|
||||||
|
key={name}
|
||||||
|
isSelected={isSelected}
|
||||||
|
isDisabled={false}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'status') {
|
if (name === 'status') {
|
||||||
return (
|
return (
|
||||||
<AuthorStatusCell
|
<AuthorStatusCell
|
||||||
@@ -431,7 +448,10 @@ AuthorIndexRow.propTypes = {
|
|||||||
isRefreshingAuthor: PropTypes.bool.isRequired,
|
isRefreshingAuthor: PropTypes.bool.isRequired,
|
||||||
isSearchingAuthor: PropTypes.bool.isRequired,
|
isSearchingAuthor: PropTypes.bool.isRequired,
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
onRefreshAuthorPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorIndexRow.defaultProps = {
|
AuthorIndexRow.defaultProps = {
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class AuthorIndexTable extends Component {
|
|||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
columns,
|
columns,
|
||||||
|
selectedState,
|
||||||
|
onSelectedChange,
|
||||||
|
isEditorActive,
|
||||||
showBanners,
|
showBanners,
|
||||||
showTitle
|
showTitle
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -67,6 +70,9 @@ class AuthorIndexTable extends Component {
|
|||||||
authorId={author.id}
|
authorId={author.id}
|
||||||
qualityProfileId={author.qualityProfileId}
|
qualityProfileId={author.qualityProfileId}
|
||||||
metadataProfileId={author.metadataProfileId}
|
metadataProfileId={author.metadataProfileId}
|
||||||
|
isSelected={selectedState[author.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
showBanners={showBanners}
|
showBanners={showBanners}
|
||||||
showTitle={showTitle}
|
showTitle={showTitle}
|
||||||
/>
|
/>
|
||||||
@@ -87,7 +93,12 @@ class AuthorIndexTable extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
scroller,
|
scroller,
|
||||||
scrollTop
|
scrollTop,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -108,8 +119,13 @@ class AuthorIndexTable extends Component {
|
|||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onSortPress={onSortPress}
|
onSortPress={onSortPress}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
selectedState={selectedState}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
@@ -129,7 +145,13 @@ AuthorIndexTable.propTypes = {
|
|||||||
scrollTop: PropTypes.number,
|
scrollTop: PropTypes.number,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorIndexTable;
|
export default AuthorIndexTable;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { bulkDeleteBook } from 'Store/Actions/bookEditorActions';
|
import { bulkDeleteBook } from 'Store/Actions/bookIndexActions';
|
||||||
import DeleteBookModalContent from './DeleteBookModalContent';
|
import DeleteBookModalContent from './DeleteBookModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import _ from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import NoAuthor from 'Author/NoAuthor';
|
import NoAuthor from 'Author/NoAuthor';
|
||||||
|
import BookEditorFooter from 'Book/Editor/BookEditorFooter';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
import PageJumpBar from 'Components/Page/PageJumpBar';
|
import PageJumpBar from 'Components/Page/PageJumpBar';
|
||||||
@@ -11,10 +13,13 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
|||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import BookIndexFooterConnector from './BookIndexFooterConnector';
|
import BookIndexFooterConnector from './BookIndexFooterConnector';
|
||||||
import BookIndexFilterMenu from './Menus/BookIndexFilterMenu';
|
import BookIndexFilterMenu from './Menus/BookIndexFilterMenu';
|
||||||
import BookIndexSortMenu from './Menus/BookIndexSortMenu';
|
import BookIndexSortMenu from './Menus/BookIndexSortMenu';
|
||||||
@@ -52,12 +57,19 @@ class BookIndex extends Component {
|
|||||||
jumpBarItems: { order: [] },
|
jumpBarItems: { order: [] },
|
||||||
jumpToCharacter: null,
|
jumpToCharacter: null,
|
||||||
isPosterOptionsModalOpen: false,
|
isPosterOptionsModalOpen: false,
|
||||||
isOverviewOptionsModalOpen: false
|
isOverviewOptionsModalOpen: false,
|
||||||
|
isConfirmSearchModalOpen: false,
|
||||||
|
isEditorActive: false,
|
||||||
|
allSelected: false,
|
||||||
|
allUnselected: false,
|
||||||
|
lastToggled: null,
|
||||||
|
selectedState: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -72,6 +84,7 @@ class BookIndex extends Component {
|
|||||||
hasDifferentItemsOrOrder(prevProps.items, items)
|
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||||
) {
|
) {
|
||||||
this.setJumpBarItems();
|
this.setJumpBarItems();
|
||||||
|
this.setSelectedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.jumpToCharacter != null) {
|
if (this.state.jumpToCharacter != null) {
|
||||||
@@ -86,6 +99,48 @@ class BookIndex extends Component {
|
|||||||
this.setState({ scroller: ref });
|
this.setState({ scroller: ref });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedIds = () => {
|
||||||
|
if (this.state.allUnselected) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getSelectedIds(this.state.selectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedState() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const newSelectedState = {};
|
||||||
|
|
||||||
|
items.forEach((book) => {
|
||||||
|
const isItemSelected = selectedState[book.id];
|
||||||
|
|
||||||
|
if (isItemSelected) {
|
||||||
|
newSelectedState[book.id] = isItemSelected;
|
||||||
|
} else {
|
||||||
|
newSelectedState[book.id] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||||
|
const newStateCount = Object.keys(newSelectedState).length;
|
||||||
|
let isAllSelected = false;
|
||||||
|
let isAllUnselected = false;
|
||||||
|
|
||||||
|
if (selectedCount === 0) {
|
||||||
|
isAllUnselected = true;
|
||||||
|
} else if (selectedCount === newStateCount) {
|
||||||
|
isAllSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||||
|
}
|
||||||
|
|
||||||
setJumpBarItems() {
|
setJumpBarItems() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
@@ -150,10 +205,64 @@ class BookIndex extends Component {
|
|||||||
this.setState({ isOverviewOptionsModalOpen: false });
|
this.setState({ isOverviewOptionsModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEditorTogglePress = () => {
|
||||||
|
if (this.state.isEditorActive) {
|
||||||
|
this.setState({ isEditorActive: false });
|
||||||
|
} else {
|
||||||
|
const newState = selectAll(this.state.selectedState, false);
|
||||||
|
newState.isEditorActive = true;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onJumpBarItemPress = (jumpToCharacter) => {
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
this.setState({ jumpToCharacter });
|
this.setState({ jumpToCharacter });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectAllChange = ({ value }) => {
|
||||||
|
this.setState(selectAll(this.state.selectedState, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectAllPress = () => {
|
||||||
|
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveSelected = (changes) => {
|
||||||
|
this.props.onSaveSelected({
|
||||||
|
bookIds: this.getSelectedIds(),
|
||||||
|
...changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchPress = () => {
|
||||||
|
this.setState({ isConfirmSearchModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onRefreshBookPress = () => {
|
||||||
|
const selectedIds = this.getSelectedIds();
|
||||||
|
const refreshIds = this.state.isEditorActive && selectedIds.length > 0 ? selectedIds : [];
|
||||||
|
|
||||||
|
this.props.onRefreshBookPress(refreshIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchConfirmed = () => {
|
||||||
|
const selectedMovieIds = this.getSelectedIds();
|
||||||
|
const searchIds = this.state.isMovieEditorActive && selectedMovieIds.length > 0 ? selectedMovieIds : this.props.items.map((m) => m.id);
|
||||||
|
|
||||||
|
this.props.onSearchPress(searchIds);
|
||||||
|
this.setState({ isConfirmSearchModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmSearchModalClose = () => {
|
||||||
|
this.setState({ isConfirmSearchModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -173,11 +282,15 @@ class BookIndex extends Component {
|
|||||||
view,
|
view,
|
||||||
isRefreshingBook,
|
isRefreshingBook,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isSearching,
|
||||||
|
isSaving,
|
||||||
|
saveError,
|
||||||
|
isDeleting,
|
||||||
|
deleteError,
|
||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
onViewSelect,
|
onViewSelect,
|
||||||
onRefreshAuthorPress,
|
|
||||||
onRssSyncPress,
|
onRssSyncPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -187,23 +300,35 @@ class BookIndex extends Component {
|
|||||||
jumpBarItems,
|
jumpBarItems,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
isPosterOptionsModalOpen,
|
isPosterOptionsModalOpen,
|
||||||
isOverviewOptionsModalOpen
|
isOverviewOptionsModalOpen,
|
||||||
|
isConfirmSearchModalOpen,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState,
|
||||||
|
allSelected,
|
||||||
|
allUnselected
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const selectedBookIds = this.getSelectedIds();
|
||||||
|
|
||||||
const ViewComponent = getViewComponent(view);
|
const ViewComponent = getViewComponent(view);
|
||||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||||
const hasNoAuthor = !totalItems;
|
const hasNoAuthor = !totalItems;
|
||||||
|
|
||||||
|
const refreshLabel = isEditorActive && selectedBookIds.length > 0 ? translate('UpdateSelected') : translate('UpdateAll');
|
||||||
|
const searchIndexLabel = selectedFilterKey === 'all' ? translate('SearchAll') : translate('SearchFiltered');
|
||||||
|
const searchEditorLabel = selectedBookIds.length > 0 ? translate('SearchSelected') : translate('SearchAll');
|
||||||
|
const searchWarningCount = isEditorActive && selectedBookIds.length > 0 ? selectedBookIds.length : items.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<PageToolbar>
|
<PageToolbar>
|
||||||
<PageToolbarSection>
|
<PageToolbarSection>
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('UpdateAll')}
|
label={refreshLabel}
|
||||||
iconName={icons.REFRESH}
|
iconName={icons.REFRESH}
|
||||||
spinningName={icons.REFRESH}
|
spinningName={icons.REFRESH}
|
||||||
isSpinning={isRefreshingBook}
|
isSpinning={isRefreshingBook}
|
||||||
onPress={onRefreshAuthorPress}
|
onPress={this.onRefreshBookPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -214,6 +339,44 @@ class BookIndex extends Component {
|
|||||||
onPress={onRssSyncPress}
|
onPress={onRssSyncPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label={isEditorActive ? searchEditorLabel : searchIndexLabel}
|
||||||
|
iconName={icons.SEARCH}
|
||||||
|
isDisabled={isSearching || !items.length}
|
||||||
|
onPress={this.onSearchPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
{
|
||||||
|
isEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('BookIndex')}
|
||||||
|
iconName={icons.AUTHOR_CONTINUING}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onEditorTogglePress}
|
||||||
|
/> :
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('BookEditor')}
|
||||||
|
iconName={icons.EDIT}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onEditorTogglePress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isEditorActive ?
|
||||||
|
<PageToolbarButton
|
||||||
|
label={allSelected ? translate('UnselectAll') : translate('SelectAll')}
|
||||||
|
iconName={icons.CHECK_SQUARE}
|
||||||
|
isDisabled={hasNoAuthor}
|
||||||
|
onPress={this.onSelectAllPress}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
<PageToolbarSection
|
<PageToolbarSection
|
||||||
@@ -311,6 +474,12 @@ class BookIndex extends Component {
|
|||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
jumpToCharacter={jumpToCharacter}
|
jumpToCharacter={jumpToCharacter}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectedChange={this.onSelectedChange}
|
||||||
|
onSelectAllChange={this.onSelectAllChange}
|
||||||
|
selectedState={selectedState}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -336,6 +505,19 @@ class BookIndex extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoaded && isEditorActive &&
|
||||||
|
<BookEditorFooter
|
||||||
|
bookIds={selectedBookIds}
|
||||||
|
selectedCount={selectedBookIds.length}
|
||||||
|
isSaving={isSaving}
|
||||||
|
saveError={saveError}
|
||||||
|
isDeleting={isDeleting}
|
||||||
|
deleteError={deleteError}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<BookIndexPosterOptionsModal
|
<BookIndexPosterOptionsModal
|
||||||
isOpen={isPosterOptionsModalOpen}
|
isOpen={isPosterOptionsModalOpen}
|
||||||
onModalClose={this.onPosterOptionsModalClose}
|
onModalClose={this.onPosterOptionsModalClose}
|
||||||
@@ -346,6 +528,25 @@ class BookIndex extends Component {
|
|||||||
onModalClose={this.onOverviewOptionsModalClose}
|
onModalClose={this.onOverviewOptionsModalClose}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isConfirmSearchModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('MassBookSearch')}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{translate('MassBookSearchWarning', [searchWarningCount])}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{translate('ThisCannotBeCancelled')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
confirmLabel={translate('Search')}
|
||||||
|
onConfirm={this.onSearchConfirmed}
|
||||||
|
onCancel={this.onConfirmSearchModalClose}
|
||||||
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -365,14 +566,21 @@ BookIndex.propTypes = {
|
|||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
isRefreshingBook: PropTypes.bool.isRequired,
|
isRefreshingBook: PropTypes.bool.isRequired,
|
||||||
|
isSearching: PropTypes.bool.isRequired,
|
||||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
deleteError: PropTypes.object,
|
||||||
onSortSelect: PropTypes.func.isRequired,
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onViewSelect: PropTypes.func.isRequired,
|
onViewSelect: PropTypes.func.isRequired,
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
onRefreshBookPress: PropTypes.func.isRequired,
|
||||||
onRssSyncPress: PropTypes.func.isRequired,
|
onRssSyncPress: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
onScroll: PropTypes.func.isRequired,
|
||||||
|
onSaveSelected: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookIndex;
|
export default BookIndex;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
import { setBookFilter, setBookSort, setBookTableOption, setBookView } from 'Store/Actions/bookIndexActions';
|
import { saveBookEditor, setBookFilter, setBookSort, setBookTableOption, setBookView } from 'Store/Actions/bookIndexActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createBookClientSideCollectionItemsSelector from 'Store/Selectors/createBookClientSideCollectionItemsSelector';
|
import createBookClientSideCollectionItemsSelector from 'Store/Selectors/createBookClientSideCollectionItemsSelector';
|
||||||
@@ -19,12 +19,16 @@ function createMapStateToProps() {
|
|||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
|
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||||
|
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(
|
(
|
||||||
book,
|
book,
|
||||||
isRefreshingAuthorCommand,
|
isRefreshingAuthorCommand,
|
||||||
isRefreshingBookCommand,
|
isRefreshingBookCommand,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isCutoffBooksSearch,
|
||||||
|
isMissingBooksSearch,
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
const isRefreshingBook = isRefreshingBookCommand || isRefreshingAuthorCommand;
|
const isRefreshingBook = isRefreshingBookCommand || isRefreshingAuthorCommand;
|
||||||
@@ -32,6 +36,7 @@ function createMapStateToProps() {
|
|||||||
...book,
|
...book,
|
||||||
isRefreshingBook,
|
isRefreshingBook,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
|
isSearching: isCutoffBooksSearch || isMissingBooksSearch,
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -56,9 +61,14 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(setBookView({ view }));
|
dispatch(setBookView({ view }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onRefreshAuthorPress() {
|
dispatchSaveBookEditor(payload) {
|
||||||
|
dispatch(saveBookEditor(payload));
|
||||||
|
},
|
||||||
|
|
||||||
|
onRefreshBookPress(items) {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.REFRESH_AUTHOR
|
name: commandNames.BULK_REFRESH_BOOK,
|
||||||
|
bookIds: items
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -66,6 +76,13 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.RSS_SYNC
|
name: commandNames.RSS_SYNC
|
||||||
}));
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchPress(items) {
|
||||||
|
dispatch(executeCommand({
|
||||||
|
name: commandNames.BOOK_SEARCH,
|
||||||
|
bookIds: items
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -79,6 +96,10 @@ class BookIndexConnector extends Component {
|
|||||||
this.props.dispatchSetBookView(view);
|
this.props.dispatchSetBookView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSaveSelected = (payload) => {
|
||||||
|
this.props.dispatchSaveBookEditor(payload);
|
||||||
|
}
|
||||||
|
|
||||||
onScroll = ({ scrollTop }) => {
|
onScroll = ({ scrollTop }) => {
|
||||||
scrollPositions.bookIndex = scrollTop;
|
scrollPositions.bookIndex = scrollTop;
|
||||||
}
|
}
|
||||||
@@ -92,6 +113,7 @@ class BookIndexConnector extends Component {
|
|||||||
{...this.props}
|
{...this.props}
|
||||||
onViewSelect={this.onViewSelect}
|
onViewSelect={this.onViewSelect}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
|
onSaveSelected={this.onSaveSelected}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -100,7 +122,8 @@ class BookIndexConnector extends Component {
|
|||||||
BookIndexConnector.propTypes = {
|
BookIndexConnector.propTypes = {
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
dispatchSetBookView: PropTypes.func.isRequired
|
dispatchSetBookView: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveBookEditor: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withScrollPosition(
|
export default withScrollPosition(
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function createMapStateToProps() {
|
|||||||
executingCommands
|
executingCommands
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
// If an book is deleted this selector may fire before the parent
|
// If a book is deleted this selector may fire before the parent
|
||||||
// selectors, which will result in an undefined book, if that happens
|
// selectors, which will result in an undefined book, if that happens
|
||||||
// we want to return early here and again in the render function to avoid
|
// we want to return early here and again in the render function to avoid
|
||||||
// trying to show an book that has no information available.
|
// trying to show an book that has no information available.
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ $hoverScale: 1.05;
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
.posterContainer {
|
.posterContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import AuthorPoster from 'Author/AuthorPoster';
|
|||||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||||
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
@@ -67,6 +68,15 @@ class BookIndexOverview extends Component {
|
|||||||
this.setState({ isDeleteAuthorModalOpen: false });
|
this.setState({ isDeleteAuthorModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = ({ value, shiftKey }) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
onSelectedChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSelectedChange({ id, value, shiftKey });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -95,6 +105,8 @@ class BookIndexOverview extends Component {
|
|||||||
isSearchingBook,
|
isSearchingBook,
|
||||||
onRefreshBookPress,
|
onRefreshBookPress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -125,6 +137,17 @@ class BookIndexOverview extends Component {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{
|
||||||
|
isEditorActive &&
|
||||||
|
<div className={styles.editorSelect}>
|
||||||
|
<CheckInput
|
||||||
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{
|
{
|
||||||
status === 'ended' &&
|
status === 'ended' &&
|
||||||
<div
|
<div
|
||||||
@@ -264,7 +287,10 @@ BookIndexOverview.propTypes = {
|
|||||||
isRefreshingBook: PropTypes.bool.isRequired,
|
isRefreshingBook: PropTypes.bool.isRequired,
|
||||||
isSearchingBook: PropTypes.bool.isRequired,
|
isSearchingBook: PropTypes.bool.isRequired,
|
||||||
onRefreshBookPress: PropTypes.func.isRequired,
|
onRefreshBookPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
BookIndexOverview.defaultProps = {
|
BookIndexOverview.defaultProps = {
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ class BookIndexOverviews extends Component {
|
|||||||
sortKey,
|
sortKey,
|
||||||
overviewOptions,
|
overviewOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
scrollTop
|
scrollTop,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -91,6 +93,8 @@ class BookIndexOverviews extends Component {
|
|||||||
(prevState.width !== width ||
|
(prevState.width !== width ||
|
||||||
prevState.rowHeight !== rowHeight ||
|
prevState.rowHeight !== rowHeight ||
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items) ||
|
hasDifferentItemsOrOrder(prevProps.items, items) ||
|
||||||
|
prevProps.isEditorActive !== isEditorActive ||
|
||||||
|
prevProps.selectedState !== selectedState ||
|
||||||
prevProps.overviewOptions !== overviewOptions)) {
|
prevProps.overviewOptions !== overviewOptions)) {
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
@@ -148,7 +152,10 @@ class BookIndexOverviews extends Component {
|
|||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
isSmallScreen
|
isSmallScreen,
|
||||||
|
selectedState,
|
||||||
|
isEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -183,6 +190,9 @@ class BookIndexOverviews extends Component {
|
|||||||
isSmallScreen={isSmallScreen}
|
isSmallScreen={isSmallScreen}
|
||||||
bookId={book.id}
|
bookId={book.id}
|
||||||
authorId={book.authorId}
|
authorId={book.authorId}
|
||||||
|
isSelected={selectedState[book.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -263,7 +273,10 @@ BookIndexOverviews.propTypes = {
|
|||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookIndexOverviews;
|
export default BookIndexOverviews;
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ $hoverScale: 1.05;
|
|||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorSelect {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
|||||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||||
import EditBookModalConnector from 'Book/Edit/EditBookModalConnector';
|
import EditBookModalConnector from 'Book/Edit/EditBookModalConnector';
|
||||||
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
||||||
|
import CheckInput from 'Components/Form/CheckInput';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
@@ -73,6 +74,15 @@ class BookIndexPoster extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = ({ value, shiftKey }) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
onSelectedChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSelectedChange({ id, value, shiftKey });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -103,6 +113,9 @@ class BookIndexPoster extends Component {
|
|||||||
isSearchingBook,
|
isSearchingBook,
|
||||||
onRefreshBookPress,
|
onRefreshBookPress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
|
onSelectedChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -132,6 +145,18 @@ class BookIndexPoster extends Component {
|
|||||||
<div>
|
<div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
|
{
|
||||||
|
isEditorActive &&
|
||||||
|
<div className={styles.editorSelect}>
|
||||||
|
<CheckInput
|
||||||
|
className={styles.checkInput}
|
||||||
|
name={id.toString()}
|
||||||
|
value={isSelected}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<Label className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
className={styles.action}
|
className={styles.action}
|
||||||
@@ -309,7 +334,10 @@ BookIndexPoster.propTypes = {
|
|||||||
isRefreshingBook: PropTypes.bool.isRequired,
|
isRefreshingBook: PropTypes.bool.isRequired,
|
||||||
isSearchingBook: PropTypes.bool.isRequired,
|
isSearchingBook: PropTypes.bool.isRequired,
|
||||||
onRefreshBookPress: PropTypes.func.isRequired,
|
onRefreshBookPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
BookIndexPoster.defaultProps = {
|
BookIndexPoster.defaultProps = {
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ class BookIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
scrollTop
|
isEditorActive,
|
||||||
|
scrollTop,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -142,7 +144,9 @@ class BookIndexPosters extends Component {
|
|||||||
prevState.columnWidth !== columnWidth ||
|
prevState.columnWidth !== columnWidth ||
|
||||||
prevState.columnCount !== columnCount ||
|
prevState.columnCount !== columnCount ||
|
||||||
prevState.rowHeight !== rowHeight ||
|
prevState.rowHeight !== rowHeight ||
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
hasDifferentItemsOrOrder(prevProps.items, items)) ||
|
||||||
|
prevProps.isEditorActive !== isEditorActive ||
|
||||||
|
prevProps.selectedState !== selectedState) {
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
}
|
}
|
||||||
@@ -202,7 +206,10 @@ class BookIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat
|
timeFormat,
|
||||||
|
selectedState,
|
||||||
|
isEditorActive,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -251,6 +258,9 @@ class BookIndexPosters extends Component {
|
|||||||
style={style}
|
style={style}
|
||||||
bookId={book.id}
|
bookId={book.id}
|
||||||
authorId={book.authorId}
|
authorId={book.authorId}
|
||||||
|
isSelected={selectedState[book.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -333,7 +343,10 @@ BookIndexPosters.propTypes = {
|
|||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookIndexPosters;
|
export default BookIndexPosters;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import BookIndexTableOptionsConnector from './BookIndexTableOptionsConnector';
|
import BookIndexTableOptionsConnector from './BookIndexTableOptionsConnector';
|
||||||
import styles from './BookIndexHeader.css';
|
import styles from './BookIndexHeader.css';
|
||||||
@@ -13,6 +14,10 @@ function BookIndexHeader(props) {
|
|||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
onTableOptionChange,
|
onTableOptionChange,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isEditorActive,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -31,6 +36,21 @@ function BookIndexHeader(props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'select') {
|
||||||
|
if (isEditorActive) {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectAllHeaderCell
|
||||||
|
key={name}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'actions') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeaderCell
|
<VirtualTableHeaderCell
|
||||||
@@ -75,7 +95,11 @@ function BookIndexHeader(props) {
|
|||||||
|
|
||||||
BookIndexHeader.propTypes = {
|
BookIndexHeader.propTypes = {
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onTableOptionChange: PropTypes.func.isRequired
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookIndexHeader;
|
export default BookIndexHeader;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
@@ -100,8 +101,11 @@ class BookIndexRow extends Component {
|
|||||||
columns,
|
columns,
|
||||||
isRefreshingBook,
|
isRefreshingBook,
|
||||||
isSearchingBook,
|
isSearchingBook,
|
||||||
|
isEditorActive,
|
||||||
|
isSelected,
|
||||||
onRefreshBookPress,
|
onRefreshBookPress,
|
||||||
onSearchPress
|
onSearchPress,
|
||||||
|
onSelectedChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -128,6 +132,19 @@ class BookIndexRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEditorActive && name === 'select') {
|
||||||
|
return (
|
||||||
|
<VirtualTableSelectCell
|
||||||
|
inputClassName={styles.checkInput}
|
||||||
|
id={id}
|
||||||
|
key={name}
|
||||||
|
isSelected={isSelected}
|
||||||
|
isDisabled={false}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'status') {
|
if (name === 'status') {
|
||||||
return (
|
return (
|
||||||
<BookStatusCell
|
<BookStatusCell
|
||||||
@@ -368,7 +385,10 @@ BookIndexRow.propTypes = {
|
|||||||
isRefreshingBook: PropTypes.bool.isRequired,
|
isRefreshingBook: PropTypes.bool.isRequired,
|
||||||
isSearchingBook: PropTypes.bool.isRequired,
|
isSearchingBook: PropTypes.bool.isRequired,
|
||||||
onRefreshBookPress: PropTypes.func.isRequired,
|
onRefreshBookPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
BookIndexRow.defaultProps = {
|
BookIndexRow.defaultProps = {
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ class BookIndexTable extends Component {
|
|||||||
rowRenderer = ({ key, rowIndex, style }) => {
|
rowRenderer = ({ key, rowIndex, style }) => {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
columns
|
columns,
|
||||||
|
selectedState,
|
||||||
|
onSelectedChange,
|
||||||
|
isEditorActive
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const book = items[rowIndex];
|
const book = items[rowIndex];
|
||||||
@@ -64,6 +67,9 @@ class BookIndexTable extends Component {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
authorId={book.authorId}
|
authorId={book.authorId}
|
||||||
bookId={book.id}
|
bookId={book.id}
|
||||||
|
isSelected={selectedState[book.id]}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRow>
|
</VirtualTableRow>
|
||||||
);
|
);
|
||||||
@@ -81,7 +87,12 @@ class BookIndexTable extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
scroller,
|
scroller,
|
||||||
scrollTop
|
scrollTop,
|
||||||
|
allSelected,
|
||||||
|
allUnselected,
|
||||||
|
onSelectAllChange,
|
||||||
|
isEditorActive,
|
||||||
|
selectedState
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -101,8 +112,13 @@ class BookIndexTable extends Component {
|
|||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onSortPress={onSortPress}
|
onSortPress={onSortPress}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
isEditorActive={isEditorActive}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
selectedState={selectedState}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
@@ -120,7 +136,13 @@ BookIndexTable.propTypes = {
|
|||||||
scrollTop: PropTypes.number,
|
scrollTop: PropTypes.number,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
onSelectedChange: PropTypes.func.isRequired,
|
||||||
|
onSelectAllChange: PropTypes.func.isRequired,
|
||||||
|
isEditorActive: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookIndexTable;
|
export default BookIndexTable;
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const INTERACTIVE_IMPORT = 'ManualImport';
|
|||||||
export const MISSING_BOOK_SEARCH = 'MissingBookSearch';
|
export const MISSING_BOOK_SEARCH = 'MissingBookSearch';
|
||||||
export const MOVE_AUTHOR = 'MoveAuthor';
|
export const MOVE_AUTHOR = 'MoveAuthor';
|
||||||
export const REFRESH_AUTHOR = 'RefreshAuthor';
|
export const REFRESH_AUTHOR = 'RefreshAuthor';
|
||||||
|
export const BULK_REFRESH_AUTHOR = 'BulkRefreshAuthor';
|
||||||
export const REFRESH_BOOK = 'RefreshBook';
|
export const REFRESH_BOOK = 'RefreshBook';
|
||||||
|
export const BULK_REFRESH_BOOK = 'BulkRefreshBook';
|
||||||
export const RENAME_FILES = 'RenameFiles';
|
export const RENAME_FILES = 'RenameFiles';
|
||||||
export const RENAME_AUTHOR = 'RenameAuthor';
|
export const RENAME_AUTHOR = 'RenameAuthor';
|
||||||
export const RESCAN_FOLDERS = 'RescanFolders';
|
export const RESCAN_FOLDERS = 'RescanFolders';
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ const links = [
|
|||||||
title: 'Add New',
|
title: 'Add New',
|
||||||
to: '/add/search'
|
to: '/add/search'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Mass Editor',
|
|
||||||
to: '/authoreditor'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Bookshelf',
|
title: 'Bookshelf',
|
||||||
to: '/shelf'
|
to: '/shelf'
|
||||||
|
|||||||
@@ -1,243 +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 createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
import { filterPredicates, filters, sortPredicates } from './authorActions';
|
|
||||||
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';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
export const section = 'authorEditor';
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
export const defaultState = {
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null,
|
|
||||||
sortKey: 'sortName',
|
|
||||||
sortDirection: sortDirections.ASCENDING,
|
|
||||||
secondarySortKey: 'sortName',
|
|
||||||
secondarySortDirection: sortDirections.ASCENDING,
|
|
||||||
selectedFilterKey: 'all',
|
|
||||||
filters,
|
|
||||||
filterPredicates,
|
|
||||||
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
columnLabel: 'Status',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true,
|
|
||||||
isModifiable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sortName',
|
|
||||||
label: 'Name',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualityProfileId',
|
|
||||||
label: 'Quality Profile',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'metadataProfileId',
|
|
||||||
label: 'Metadata Profile',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'path',
|
|
||||||
label: 'Path',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sizeOnDisk',
|
|
||||||
label: 'Size on Disk',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: 'Tags',
|
|
||||||
isSortable: false,
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
filterBuilderProps: [
|
|
||||||
{
|
|
||||||
name: 'monitored',
|
|
||||||
label: 'Monitored',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.BOOL
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: 'Status',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.AUTHOR_STATUS
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualityProfileId',
|
|
||||||
label: 'Quality Profile',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'metadataProfileId',
|
|
||||||
label: 'Metadata Profile',
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'path',
|
|
||||||
label: 'Path',
|
|
||||||
type: filterBuilderTypes.STRING
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rootFolderPath',
|
|
||||||
label: 'Root Folder Path',
|
|
||||||
type: filterBuilderTypes.EXACT
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sizeOnDisk',
|
|
||||||
label: 'Size on Disk',
|
|
||||||
type: filterBuilderTypes.NUMBER,
|
|
||||||
valueType: filterBuilderValueTypes.BYTES
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: 'Tags',
|
|
||||||
type: filterBuilderTypes.ARRAY,
|
|
||||||
valueType: filterBuilderValueTypes.TAG
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
sortPredicates
|
|
||||||
};
|
|
||||||
|
|
||||||
export const persistState = [
|
|
||||||
'authorEditor.sortKey',
|
|
||||||
'authorEditor.sortDirection',
|
|
||||||
'authorEditor.selectedFilterKey',
|
|
||||||
'authorEditor.customFilters'
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const SET_AUTHOR_EDITOR_SORT = 'authorEditor/setAuthorEditorSort';
|
|
||||||
export const SET_AUTHOR_EDITOR_FILTER = 'authorEditor/setAuthorEditorFilter';
|
|
||||||
export const SAVE_AUTHOR_EDITOR = 'authorEditor/saveAuthorEditor';
|
|
||||||
export const BULK_DELETE_AUTHOR = 'authorEditor/bulkDeleteAuthor';
|
|
||||||
export const SET_AUTHOR_EDITOR_TABLE_OPTION = 'authorEditor/setAuthorEditorTableOption';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const setAuthorEditorSort = createAction(SET_AUTHOR_EDITOR_SORT);
|
|
||||||
export const setAuthorEditorFilter = createAction(SET_AUTHOR_EDITOR_FILTER);
|
|
||||||
export const saveAuthorEditor = createThunk(SAVE_AUTHOR_EDITOR);
|
|
||||||
export const bulkDeleteAuthor = createThunk(BULK_DELETE_AUTHOR);
|
|
||||||
export const setAuthorEditorTableOption = createAction(SET_AUTHOR_EDITOR_TABLE_OPTION);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
|
||||||
[SAVE_AUTHOR_EDITOR]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/author/editor',
|
|
||||||
method: 'PUT',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done((data) => {
|
|
||||||
dispatch(batchActions([
|
|
||||||
...data.map((author) => {
|
|
||||||
return updateItem({
|
|
||||||
id: author.id,
|
|
||||||
section: 'authors',
|
|
||||||
...author
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[BULK_DELETE_AUTHOR]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/author/editor',
|
|
||||||
method: 'DELETE',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done(() => {
|
|
||||||
// SignalR will take care of removing the author 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_AUTHOR_EDITOR_TABLE_OPTION]: createSetTableOptionReducer(section),
|
|
||||||
[SET_AUTHOR_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
|
|
||||||
[SET_AUTHOR_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
|
||||||
|
|
||||||
}, defaultState, section);
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import { filterPredicates, filters, sortPredicates } from './authorActions';
|
import { filterPredicates, filters, sortPredicates } from './authorActions';
|
||||||
|
import { set, updateItem } from './baseActions';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||||
@@ -16,6 +20,10 @@ export const section = 'authorIndex';
|
|||||||
// State
|
// State
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null,
|
||||||
sortKey: 'sortNameLastFirst',
|
sortKey: 'sortNameLastFirst',
|
||||||
sortDirection: sortDirections.ASCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
secondarySortKey: 'sortNameLastFirst',
|
secondarySortKey: 'sortNameLastFirst',
|
||||||
@@ -52,6 +60,14 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
columnLabel: 'Select',
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true,
|
||||||
|
isModifiable: false,
|
||||||
|
isHidden: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
columnLabel: 'Status',
|
columnLabel: 'Status',
|
||||||
@@ -328,6 +344,8 @@ export const SET_AUTHOR_TABLE_OPTION = 'authorIndex/setAuthorTableOption';
|
|||||||
export const SET_AUTHOR_POSTER_OPTION = 'authorIndex/setAuthorPosterOption';
|
export const SET_AUTHOR_POSTER_OPTION = 'authorIndex/setAuthorPosterOption';
|
||||||
export const SET_AUTHOR_BANNER_OPTION = 'authorIndex/setAuthorBannerOption';
|
export const SET_AUTHOR_BANNER_OPTION = 'authorIndex/setAuthorBannerOption';
|
||||||
export const SET_AUTHOR_OVERVIEW_OPTION = 'authorIndex/setAuthorOverviewOption';
|
export const SET_AUTHOR_OVERVIEW_OPTION = 'authorIndex/setAuthorOverviewOption';
|
||||||
|
export const SAVE_AUTHOR_EDITOR = 'authorIndex/saveAuthorEditor';
|
||||||
|
export const BULK_DELETE_AUTHOR = 'authorIndex/bulkDeleteAuthor';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
@@ -339,6 +357,85 @@ export const setAuthorTableOption = createAction(SET_AUTHOR_TABLE_OPTION);
|
|||||||
export const setAuthorPosterOption = createAction(SET_AUTHOR_POSTER_OPTION);
|
export const setAuthorPosterOption = createAction(SET_AUTHOR_POSTER_OPTION);
|
||||||
export const setAuthorBannerOption = createAction(SET_AUTHOR_BANNER_OPTION);
|
export const setAuthorBannerOption = createAction(SET_AUTHOR_BANNER_OPTION);
|
||||||
export const setAuthorOverviewOption = createAction(SET_AUTHOR_OVERVIEW_OPTION);
|
export const setAuthorOverviewOption = createAction(SET_AUTHOR_OVERVIEW_OPTION);
|
||||||
|
export const saveAuthorEditor = createThunk(SAVE_AUTHOR_EDITOR);
|
||||||
|
export const bulkDeleteAuthor = createThunk(BULK_DELETE_AUTHOR);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
[SAVE_AUTHOR_EDITOR]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/author/editor',
|
||||||
|
method: 'PUT',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done((data) => {
|
||||||
|
dispatch(batchActions([
|
||||||
|
...data.map((author) => {
|
||||||
|
return updateItem({
|
||||||
|
id: author.id,
|
||||||
|
section: 'authors',
|
||||||
|
...author
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[BULK_DELETE_AUTHOR]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/author/editor',
|
||||||
|
method: 'DELETE',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done(() => {
|
||||||
|
// SignaR will take care of removing the author from the collection
|
||||||
|
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Reducers
|
// Reducers
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { batchActions } from 'redux-batched-actions';
|
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
import { set, updateItem } from './baseActions';
|
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
export const section = 'bookEditor';
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
export const defaultState = {
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const SAVE_BOOK_EDITOR = 'bookEditor/saveBookEditor';
|
|
||||||
export const BULK_DELETE_BOOK = 'bookEditor/bulkDeleteBook';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const saveBookEditor = createThunk(SAVE_BOOK_EDITOR);
|
|
||||||
export const bulkDeleteBook = createThunk(BULK_DELETE_BOOK);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
|
||||||
[SAVE_BOOK_EDITOR]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/book/editor',
|
|
||||||
method: 'PUT',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done((data) => {
|
|
||||||
dispatch(batchActions([
|
|
||||||
...data.map((book) => {
|
|
||||||
return updateItem({
|
|
||||||
id: book.id,
|
|
||||||
section: 'books',
|
|
||||||
...book
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[BULK_DELETE_BOOK]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/book/editor',
|
|
||||||
method: 'DELETE',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done(() => {
|
|
||||||
// SignalR will take care of removing the book 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({}, defaultState, section);
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
|
import { set, updateItem } from './baseActions';
|
||||||
import { filterPredicates, filters, sortPredicates } from './bookActions';
|
import { filterPredicates, filters, sortPredicates } from './bookActions';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||||
@@ -16,6 +20,10 @@ export const section = 'bookIndex';
|
|||||||
// State
|
// State
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null,
|
||||||
sortKey: 'title',
|
sortKey: 'title',
|
||||||
sortDirection: sortDirections.ASCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
secondarySortKey: 'title',
|
secondarySortKey: 'title',
|
||||||
@@ -49,6 +57,14 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'select',
|
||||||
|
columnLabel: 'Select',
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true,
|
||||||
|
isModifiable: false,
|
||||||
|
isHidden: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
columnLabel: 'Status',
|
columnLabel: 'Status',
|
||||||
@@ -253,6 +269,8 @@ export const SET_BOOK_TABLE_OPTION = 'bookIndex/setBookTableOption';
|
|||||||
export const SET_BOOK_POSTER_OPTION = 'bookIndex/setBookPosterOption';
|
export const SET_BOOK_POSTER_OPTION = 'bookIndex/setBookPosterOption';
|
||||||
export const SET_BOOK_BANNER_OPTION = 'bookIndex/setBookBannerOption';
|
export const SET_BOOK_BANNER_OPTION = 'bookIndex/setBookBannerOption';
|
||||||
export const SET_BOOK_OVERVIEW_OPTION = 'bookIndex/setBookOverviewOption';
|
export const SET_BOOK_OVERVIEW_OPTION = 'bookIndex/setBookOverviewOption';
|
||||||
|
export const SAVE_BOOK_EDITOR = 'bookEditor/saveBookEditor';
|
||||||
|
export const BULK_DELETE_BOOK = 'bookEditor/bulkDeleteBook';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
@@ -264,6 +282,85 @@ export const setBookTableOption = createAction(SET_BOOK_TABLE_OPTION);
|
|||||||
export const setBookPosterOption = createAction(SET_BOOK_POSTER_OPTION);
|
export const setBookPosterOption = createAction(SET_BOOK_POSTER_OPTION);
|
||||||
export const setBookBannerOption = createAction(SET_BOOK_BANNER_OPTION);
|
export const setBookBannerOption = createAction(SET_BOOK_BANNER_OPTION);
|
||||||
export const setBookOverviewOption = createAction(SET_BOOK_OVERVIEW_OPTION);
|
export const setBookOverviewOption = createAction(SET_BOOK_OVERVIEW_OPTION);
|
||||||
|
export const saveBookEditor = createThunk(SAVE_BOOK_EDITOR);
|
||||||
|
export const bulkDeleteBook = createThunk(BULK_DELETE_BOOK);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
[SAVE_BOOK_EDITOR]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/book/editor',
|
||||||
|
method: 'PUT',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done((data) => {
|
||||||
|
dispatch(batchActions([
|
||||||
|
...data.map((book) => {
|
||||||
|
return updateItem({
|
||||||
|
id: book.id,
|
||||||
|
section: 'books',
|
||||||
|
...book
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[BULK_DELETE_BOOK]: function(getState, payload, dispatch) {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const promise = createAjaxRequest({
|
||||||
|
url: '/book/editor',
|
||||||
|
method: 'DELETE',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
dataType: 'json'
|
||||||
|
}).request;
|
||||||
|
|
||||||
|
promise.done(() => {
|
||||||
|
// SignalR will take care of removing the book from the collection
|
||||||
|
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Reducers
|
// Reducers
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import * as app from './appActions';
|
import * as app from './appActions';
|
||||||
import * as author from './authorActions';
|
import * as author from './authorActions';
|
||||||
import * as authorDetails from './authorDetailsActions';
|
import * as authorDetails from './authorDetailsActions';
|
||||||
import * as authorEditor from './authorEditorActions';
|
|
||||||
import * as authorHistory from './authorHistoryActions';
|
import * as authorHistory from './authorHistoryActions';
|
||||||
import * as authorIndex from './authorIndexActions';
|
import * as authorIndex from './authorIndexActions';
|
||||||
import * as blocklist from './blocklistActions';
|
import * as blocklist from './blocklistActions';
|
||||||
import * as books from './bookActions';
|
import * as books from './bookActions';
|
||||||
import * as bookEditor from './bookEditorActions';
|
|
||||||
import * as bookFiles from './bookFileActions';
|
import * as bookFiles from './bookFileActions';
|
||||||
import * as bookHistory from './bookHistoryActions';
|
import * as bookHistory from './bookHistoryActions';
|
||||||
import * as bookIndex from './bookIndexActions';
|
import * as bookIndex from './bookIndexActions';
|
||||||
@@ -36,7 +34,6 @@ export default [
|
|||||||
app,
|
app,
|
||||||
author,
|
author,
|
||||||
authorDetails,
|
authorDetails,
|
||||||
authorEditor,
|
|
||||||
authorHistory,
|
authorHistory,
|
||||||
authorIndex,
|
authorIndex,
|
||||||
blocklist,
|
blocklist,
|
||||||
@@ -44,7 +41,6 @@ export default [
|
|||||||
bookHistory,
|
bookHistory,
|
||||||
bookIndex,
|
bookIndex,
|
||||||
books,
|
books,
|
||||||
bookEditor,
|
|
||||||
bookStudio,
|
bookStudio,
|
||||||
calendar,
|
calendar,
|
||||||
captcha,
|
captcha,
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ function createBookQualityProfileSelector() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.qualityProfiles.items,
|
(state) => state.settings.qualityProfiles.items,
|
||||||
createBookSelector(),
|
createBookSelector(),
|
||||||
(qualityProfiles, book = {}) => {
|
(qualityProfiles, book) => {
|
||||||
|
if (!book) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
return qualityProfiles.find((profile) => {
|
return qualityProfiles.find((profile) => {
|
||||||
return profile.id === book.author.qualityProfileId;
|
return profile.id === book.author.qualityProfileId;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import bookEntities from 'Book/bookEntities';
|
|
||||||
|
|
||||||
function createBookSelector() {
|
function createBookSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { bookId }) => bookId,
|
(state, { bookId }) => bookId,
|
||||||
(state, { bookEntity = bookEntities.BOOKS }) => _.get(state, bookEntity, { items: [] }),
|
(state) => state.books.itemMap,
|
||||||
(bookId, books) => {
|
(state) => state.books.items,
|
||||||
return _.find(books.items, { id: bookId });
|
(bookId, itemMap, allBooks) => {
|
||||||
|
return allBooks[itemMap[bookId]];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"AuthorClickToChangeBook": "Click to change book",
|
"AuthorClickToChangeBook": "Click to change book",
|
||||||
"AuthorEditor": "Author Editor",
|
"AuthorEditor": "Author Editor",
|
||||||
"AuthorFolderFormat": "Author Folder Format",
|
"AuthorFolderFormat": "Author Folder Format",
|
||||||
|
"AuthorIndex": "Author Index",
|
||||||
"AuthorNameHelpText": "The name of the author/book to exclude (can be anything meaningful)",
|
"AuthorNameHelpText": "The name of the author/book to exclude (can be anything meaningful)",
|
||||||
"Authors": "Authors",
|
"Authors": "Authors",
|
||||||
"Automatic": "Automatic",
|
"Automatic": "Automatic",
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
"BookAvailableButMissing": "Book Available, but Missing",
|
"BookAvailableButMissing": "Book Available, but Missing",
|
||||||
"BookDownloaded": "Book Downloaded",
|
"BookDownloaded": "Book Downloaded",
|
||||||
"BookEditor": "Book Editor",
|
"BookEditor": "Book Editor",
|
||||||
|
"BookIndex": "Book Index",
|
||||||
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total: {2})",
|
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total: {2})",
|
||||||
"BookFileCounttotalBookCountBooksDownloadedInterp": "{0}/{1} books downloaded",
|
"BookFileCounttotalBookCountBooksDownloadedInterp": "{0}/{1} books downloaded",
|
||||||
"BookFilesCountMessage": "No book files",
|
"BookFilesCountMessage": "No book files",
|
||||||
@@ -343,6 +345,8 @@
|
|||||||
"ManualImport": "Manual Import",
|
"ManualImport": "Manual Import",
|
||||||
"MarkAsFailed": "Mark as Failed",
|
"MarkAsFailed": "Mark as Failed",
|
||||||
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
||||||
|
"MassBookSearch": "Mass Book Search",
|
||||||
|
"MassBookSearchWarning": "Are you sure you want to perform mass book search for {0} books?",
|
||||||
"MaximumLimits": "Maximum Limits",
|
"MaximumLimits": "Maximum Limits",
|
||||||
"MaximumSize": "Maximum Size",
|
"MaximumSize": "Maximum Size",
|
||||||
"MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.",
|
"MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.",
|
||||||
@@ -649,6 +653,7 @@
|
|||||||
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "The author folder {0} and all of its content will be deleted.",
|
"TheAuthorFolderAndAllOfItsContentWillBeDeleted": "The author folder {0} and all of its content will be deleted.",
|
||||||
"TheBooksFilesWillBeDeleted": "The book's files will be deleted.",
|
"TheBooksFilesWillBeDeleted": "The book's files will be deleted.",
|
||||||
"TheFollowingFilesWillBeDeleted": "The following files will be deleted:",
|
"TheFollowingFilesWillBeDeleted": "The following files will be deleted:",
|
||||||
|
"ThisCannotBeCancelled": "This cannot be cancelled once started without restarting Readarr.",
|
||||||
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "This will apply to all indexers, please follow the rules set forth by them",
|
"ThisWillApplyToAllIndexersPleaseFollowTheRulesSetForthByThem": "This will apply to all indexers, please follow the rules set forth by them",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"TimeFormat": "Time Format",
|
"TimeFormat": "Time Format",
|
||||||
@@ -713,6 +718,7 @@
|
|||||||
"UnmonitoredHelpText": "Include unmonitored books in the iCal feed",
|
"UnmonitoredHelpText": "Include unmonitored books in the iCal feed",
|
||||||
"UnselectAll": "Unselect All",
|
"UnselectAll": "Unselect All",
|
||||||
"UpdateAll": "Update all",
|
"UpdateAll": "Update all",
|
||||||
|
"UpdateSelected": "Updated selected",
|
||||||
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
|
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
|
||||||
"UpdateCovers": "Update Covers",
|
"UpdateCovers": "Update Covers",
|
||||||
"UpdateCoversHelpText": "Set book covers in Calibre to match those in Readarr",
|
"UpdateCoversHelpText": "Set book covers in Calibre to match those in Readarr",
|
||||||
|
|||||||
@@ -35,14 +35,12 @@ namespace Readarr.Api.V1.Books
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public object DeleteBook([FromBody] BookEditorResource resource)
|
public void DeleteBook([FromBody] BookEditorResource resource)
|
||||||
{
|
{
|
||||||
foreach (var bookId in resource.BookIds)
|
foreach (var bookId in resource.BookIds)
|
||||||
{
|
{
|
||||||
_bookService.DeleteBook(bookId, resource.DeleteFiles ?? false, resource.AddImportListExclusion ?? false);
|
_bookService.DeleteBook(bookId, resource.DeleteFiles ?? false, resource.AddImportListExclusion ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new object();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user