mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-24 22:35:39 -04:00
New: Combine mass editor and author editor, enable book editor
This commit is contained in:
@@ -2,7 +2,9 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import NoAuthor from 'Author/NoAuthor';
|
||||
import BookEditorFooter from 'Book/Editor/BookEditorFooter';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
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 PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
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 hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
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 BookIndexFilterMenu from './Menus/BookIndexFilterMenu';
|
||||
import BookIndexSortMenu from './Menus/BookIndexSortMenu';
|
||||
@@ -52,12 +57,19 @@ class BookIndex extends Component {
|
||||
jumpBarItems: { order: [] },
|
||||
jumpToCharacter: null,
|
||||
isPosterOptionsModalOpen: false,
|
||||
isOverviewOptionsModalOpen: false
|
||||
isOverviewOptionsModalOpen: false,
|
||||
isConfirmSearchModalOpen: false,
|
||||
isEditorActive: false,
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setJumpBarItems();
|
||||
this.setSelectedState();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -72,6 +84,7 @@ class BookIndex extends Component {
|
||||
hasDifferentItemsOrOrder(prevProps.items, items)
|
||||
) {
|
||||
this.setJumpBarItems();
|
||||
this.setSelectedState();
|
||||
}
|
||||
|
||||
if (this.state.jumpToCharacter != null) {
|
||||
@@ -86,6 +99,48 @@ class BookIndex extends Component {
|
||||
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() {
|
||||
const {
|
||||
items,
|
||||
@@ -150,10 +205,64 @@ class BookIndex extends Component {
|
||||
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) => {
|
||||
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
|
||||
|
||||
@@ -173,11 +282,15 @@ class BookIndex extends Component {
|
||||
view,
|
||||
isRefreshingBook,
|
||||
isRssSyncExecuting,
|
||||
isSearching,
|
||||
isSaving,
|
||||
saveError,
|
||||
isDeleting,
|
||||
deleteError,
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
onFilterSelect,
|
||||
onViewSelect,
|
||||
onRefreshAuthorPress,
|
||||
onRssSyncPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -187,23 +300,35 @@ class BookIndex extends Component {
|
||||
jumpBarItems,
|
||||
jumpToCharacter,
|
||||
isPosterOptionsModalOpen,
|
||||
isOverviewOptionsModalOpen
|
||||
isOverviewOptionsModalOpen,
|
||||
isConfirmSearchModalOpen,
|
||||
isEditorActive,
|
||||
selectedState,
|
||||
allSelected,
|
||||
allUnselected
|
||||
} = this.state;
|
||||
|
||||
const selectedBookIds = this.getSelectedIds();
|
||||
|
||||
const ViewComponent = getViewComponent(view);
|
||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||
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 (
|
||||
<PageContent>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('UpdateAll')}
|
||||
label={refreshLabel}
|
||||
iconName={icons.REFRESH}
|
||||
spinningName={icons.REFRESH}
|
||||
isSpinning={isRefreshingBook}
|
||||
onPress={onRefreshAuthorPress}
|
||||
onPress={this.onRefreshBookPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
@@ -214,6 +339,44 @@ class BookIndex extends Component {
|
||||
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
|
||||
@@ -311,6 +474,12 @@ class BookIndex extends Component {
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
jumpToCharacter={jumpToCharacter}
|
||||
isEditorActive={isEditorActive}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
selectedState={selectedState}
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
@@ -336,6 +505,19 @@ class BookIndex extends Component {
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoaded && isEditorActive &&
|
||||
<BookEditorFooter
|
||||
bookIds={selectedBookIds}
|
||||
selectedCount={selectedBookIds.length}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
onSaveSelected={this.onSaveSelected}
|
||||
/>
|
||||
}
|
||||
|
||||
<BookIndexPosterOptionsModal
|
||||
isOpen={isPosterOptionsModalOpen}
|
||||
onModalClose={this.onPosterOptionsModalClose}
|
||||
@@ -346,6 +528,25 @@ class BookIndex extends Component {
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -365,14 +566,21 @@ BookIndex.propTypes = {
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
view: PropTypes.string.isRequired,
|
||||
isRefreshingBook: PropTypes.bool.isRequired,
|
||||
isSearching: PropTypes.bool.isRequired,
|
||||
isRssSyncExecuting: 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,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onViewSelect: PropTypes.func.isRequired,
|
||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
||||
onRefreshBookPress: 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;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
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 scrollPositions from 'Store/scrollPositions';
|
||||
import createBookClientSideCollectionItemsSelector from 'Store/Selectors/createBookClientSideCollectionItemsSelector';
|
||||
@@ -19,12 +19,16 @@ function createMapStateToProps() {
|
||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
book,
|
||||
isRefreshingAuthorCommand,
|
||||
isRefreshingBookCommand,
|
||||
isRssSyncExecuting,
|
||||
isCutoffBooksSearch,
|
||||
isMissingBooksSearch,
|
||||
dimensionsState
|
||||
) => {
|
||||
const isRefreshingBook = isRefreshingBookCommand || isRefreshingAuthorCommand;
|
||||
@@ -32,6 +36,7 @@ function createMapStateToProps() {
|
||||
...book,
|
||||
isRefreshingBook,
|
||||
isRssSyncExecuting,
|
||||
isSearching: isCutoffBooksSearch || isMissingBooksSearch,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
@@ -56,9 +61,14 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(setBookView({ view }));
|
||||
},
|
||||
|
||||
onRefreshAuthorPress() {
|
||||
dispatchSaveBookEditor(payload) {
|
||||
dispatch(saveBookEditor(payload));
|
||||
},
|
||||
|
||||
onRefreshBookPress(items) {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.REFRESH_AUTHOR
|
||||
name: commandNames.BULK_REFRESH_BOOK,
|
||||
bookIds: items
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -66,6 +76,13 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(executeCommand({
|
||||
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);
|
||||
}
|
||||
|
||||
onSaveSelected = (payload) => {
|
||||
this.props.dispatchSaveBookEditor(payload);
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
scrollPositions.bookIndex = scrollTop;
|
||||
}
|
||||
@@ -92,6 +113,7 @@ class BookIndexConnector extends Component {
|
||||
{...this.props}
|
||||
onViewSelect={this.onViewSelect}
|
||||
onScroll={this.onScroll}
|
||||
onSaveSelected={this.onSaveSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +122,8 @@ class BookIndexConnector extends Component {
|
||||
BookIndexConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
dispatchSetBookView: PropTypes.func.isRequired
|
||||
dispatchSetBookView: PropTypes.func.isRequired,
|
||||
dispatchSaveBookEditor: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
|
||||
@@ -42,7 +42,7 @@ function createMapStateToProps() {
|
||||
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
|
||||
// we want to return early here and again in the render function to avoid
|
||||
// trying to show an book that has no information available.
|
||||
|
||||
@@ -19,6 +19,13 @@ $hoverScale: 1.05;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.editorSelect {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -5,6 +5,7 @@ import AuthorPoster from 'Author/AuthorPoster';
|
||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
@@ -67,6 +68,15 @@ class BookIndexOverview extends Component {
|
||||
this.setState({ isDeleteAuthorModalOpen: false });
|
||||
}
|
||||
|
||||
onChange = ({ value, shiftKey }) => {
|
||||
const {
|
||||
id,
|
||||
onSelectedChange
|
||||
} = this.props;
|
||||
|
||||
onSelectedChange({ id, value, shiftKey });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -95,6 +105,8 @@ class BookIndexOverview extends Component {
|
||||
isSearchingBook,
|
||||
onRefreshBookPress,
|
||||
onSearchPress,
|
||||
isEditorActive,
|
||||
isSelected,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -125,6 +137,17 @@ class BookIndexOverview extends Component {
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer}>
|
||||
{
|
||||
isEditorActive &&
|
||||
<div className={styles.editorSelect}>
|
||||
<CheckInput
|
||||
className={styles.checkInput}
|
||||
name={id.toString()}
|
||||
value={isSelected}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
status === 'ended' &&
|
||||
<div
|
||||
@@ -264,7 +287,10 @@ BookIndexOverview.propTypes = {
|
||||
isRefreshingBook: PropTypes.bool.isRequired,
|
||||
isSearchingBook: PropTypes.bool.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 = {
|
||||
|
||||
@@ -73,7 +73,9 @@ class BookIndexOverviews extends Component {
|
||||
sortKey,
|
||||
overviewOptions,
|
||||
jumpToCharacter,
|
||||
scrollTop
|
||||
scrollTop,
|
||||
isEditorActive,
|
||||
selectedState
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -91,6 +93,8 @@ class BookIndexOverviews extends Component {
|
||||
(prevState.width !== width ||
|
||||
prevState.rowHeight !== rowHeight ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items) ||
|
||||
prevProps.isEditorActive !== isEditorActive ||
|
||||
prevProps.selectedState !== selectedState ||
|
||||
prevProps.overviewOptions !== overviewOptions)) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
@@ -148,7 +152,10 @@ class BookIndexOverviews extends Component {
|
||||
shortDateFormat,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
isSmallScreen
|
||||
isSmallScreen,
|
||||
selectedState,
|
||||
isEditorActive,
|
||||
onSelectedChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -183,6 +190,9 @@ class BookIndexOverviews extends Component {
|
||||
isSmallScreen={isSmallScreen}
|
||||
bookId={book.id}
|
||||
authorId={book.authorId}
|
||||
isSelected={selectedState[book.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
isEditorActive={isEditorActive}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -263,7 +273,10 @@ BookIndexOverviews.propTypes = {
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.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;
|
||||
|
||||
@@ -78,6 +78,13 @@ $hoverScale: 1.05;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.editorSelect {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
|
||||
@@ -5,6 +5,7 @@ import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||
import EditBookModalConnector from 'Book/Edit/EditBookModalConnector';
|
||||
import BookIndexProgressBar from 'Book/Index/ProgressBar/BookIndexProgressBar';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
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
|
||||
|
||||
@@ -103,6 +113,9 @@ class BookIndexPoster extends Component {
|
||||
isSearchingBook,
|
||||
onRefreshBookPress,
|
||||
onSearchPress,
|
||||
isEditorActive,
|
||||
isSelected,
|
||||
onSelectedChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -132,6 +145,18 @@ class BookIndexPoster extends Component {
|
||||
<div>
|
||||
<div className={styles.content}>
|
||||
<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}>
|
||||
<SpinnerIconButton
|
||||
className={styles.action}
|
||||
@@ -309,7 +334,10 @@ BookIndexPoster.propTypes = {
|
||||
isRefreshingBook: PropTypes.bool.isRequired,
|
||||
isSearchingBook: PropTypes.bool.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 = {
|
||||
|
||||
@@ -121,7 +121,9 @@ class BookIndexPosters extends Component {
|
||||
posterOptions,
|
||||
jumpToCharacter,
|
||||
isSmallScreen,
|
||||
scrollTop
|
||||
isEditorActive,
|
||||
scrollTop,
|
||||
selectedState
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -142,7 +144,9 @@ class BookIndexPosters extends Component {
|
||||
prevState.columnWidth !== columnWidth ||
|
||||
prevState.columnCount !== columnCount ||
|
||||
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
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
@@ -202,7 +206,10 @@ class BookIndexPosters extends Component {
|
||||
posterOptions,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
timeFormat,
|
||||
selectedState,
|
||||
isEditorActive,
|
||||
onSelectedChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -251,6 +258,9 @@ class BookIndexPosters extends Component {
|
||||
style={style}
|
||||
bookId={book.id}
|
||||
authorId={book.authorId}
|
||||
isSelected={selectedState[book.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
isEditorActive={isEditorActive}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -333,7 +343,10 @@ BookIndexPosters.propTypes = {
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.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;
|
||||
|
||||
@@ -5,6 +5,7 @@ import IconButton from 'Components/Link/IconButton';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import BookIndexTableOptionsConnector from './BookIndexTableOptionsConnector';
|
||||
import styles from './BookIndexHeader.css';
|
||||
@@ -13,6 +14,10 @@ function BookIndexHeader(props) {
|
||||
const {
|
||||
columns,
|
||||
onTableOptionChange,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
isEditorActive,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
@@ -31,6 +36,21 @@ function BookIndexHeader(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'select') {
|
||||
if (isEditorActive) {
|
||||
return (
|
||||
<VirtualTableSelectAllHeaderCell
|
||||
key={name}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={onSelectAllChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
@@ -75,7 +95,11 @@ function BookIndexHeader(props) {
|
||||
|
||||
BookIndexHeader.propTypes = {
|
||||
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;
|
||||
|
||||
@@ -11,6 +11,7 @@ import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import TagListConnector from 'Components/TagListConnector';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
@@ -100,8 +101,11 @@ class BookIndexRow extends Component {
|
||||
columns,
|
||||
isRefreshingBook,
|
||||
isSearchingBook,
|
||||
isEditorActive,
|
||||
isSelected,
|
||||
onRefreshBookPress,
|
||||
onSearchPress
|
||||
onSearchPress,
|
||||
onSelectedChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -128,6 +132,19 @@ class BookIndexRow extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEditorActive && name === 'select') {
|
||||
return (
|
||||
<VirtualTableSelectCell
|
||||
inputClassName={styles.checkInput}
|
||||
id={id}
|
||||
key={name}
|
||||
isSelected={isSelected}
|
||||
isDisabled={false}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'status') {
|
||||
return (
|
||||
<BookStatusCell
|
||||
@@ -368,7 +385,10 @@ BookIndexRow.propTypes = {
|
||||
isRefreshingBook: PropTypes.bool.isRequired,
|
||||
isSearchingBook: PropTypes.bool.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 = {
|
||||
|
||||
@@ -47,7 +47,10 @@ class BookIndexTable extends Component {
|
||||
rowRenderer = ({ key, rowIndex, style }) => {
|
||||
const {
|
||||
items,
|
||||
columns
|
||||
columns,
|
||||
selectedState,
|
||||
onSelectedChange,
|
||||
isEditorActive
|
||||
} = this.props;
|
||||
|
||||
const book = items[rowIndex];
|
||||
@@ -64,6 +67,9 @@ class BookIndexTable extends Component {
|
||||
columns={columns}
|
||||
authorId={book.authorId}
|
||||
bookId={book.id}
|
||||
isSelected={selectedState[book.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
isEditorActive={isEditorActive}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
);
|
||||
@@ -81,7 +87,12 @@ class BookIndexTable extends Component {
|
||||
isSmallScreen,
|
||||
onSortPress,
|
||||
scroller,
|
||||
scrollTop
|
||||
scrollTop,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
isEditorActive,
|
||||
selectedState
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -101,8 +112,13 @@ class BookIndexTable extends Component {
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={onSelectAllChange}
|
||||
isEditorActive={isEditorActive}
|
||||
/>
|
||||
}
|
||||
selectedState={selectedState}
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
@@ -120,7 +136,13 @@ BookIndexTable.propTypes = {
|
||||
scrollTop: PropTypes.number,
|
||||
scroller: PropTypes.instanceOf(Element).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;
|
||||
|
||||
Reference in New Issue
Block a user