New: Add index views for all books

This commit is contained in:
ta264
2021-09-01 21:04:39 +01:00
parent a774cf0682
commit 2558660b7b
65 changed files with 5433 additions and 12 deletions
@@ -0,0 +1,99 @@
$hoverScale: 1.05;
.container {
&:hover {
.content {
background-color: $tableRowHoverBackgroundColor;
}
}
}
.content {
display: flex;
flex-grow: 1;
}
.poster {
position: absolute;
top: 0;
left: 0;
}
.posterContainer {
position: relative;
overflow: hidden;
}
.link {
composes: link from '~Components/Link/Link.css';
display: block;
color: $defaultColor;
&:hover {
color: $defaultColor;
text-decoration: none;
}
}
.ended {
position: absolute;
top: 0;
right: 0;
z-index: 1;
width: 0;
height: 0;
border-width: 0 25px 25px 0;
border-style: solid;
border-color: transparent $dangerColor transparent transparent;
color: $white;
}
.info {
display: flex;
flex: 1 0 1px;
flex-direction: column;
overflow: hidden;
padding-left: 10px;
}
.titleRow {
display: flex;
justify-content: space-between;
flex: 0 0 auto;
margin-bottom: 10px;
line-height: 32px;
}
.title {
@add-mixin truncate;
composes: link;
flex: 1 0 1px;
font-weight: 300;
font-size: 30px;
}
.actions {
white-space: nowrap;
}
.details {
display: flex;
justify-content: space-between;
flex: 1 0 auto;
}
.overview {
composes: link;
flex: 0 1 1000px;
overflow: hidden;
min-height: 0;
}
@media only screen and (max-width: $breakpointSmall) {
.overview {
display: none;
}
}
@@ -0,0 +1,278 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
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 IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import { icons } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
import stripHtml from 'Utilities/String/stripHtml';
import translate from 'Utilities/String/translate';
import BookIndexOverviewInfo from './BookIndexOverviewInfo';
import styles from './BookIndexOverview.css';
const columnPadding = parseInt(dimensions.authorIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(dimensions.authorIndexColumnPaddingSmallScreen);
const defaultFontSize = parseInt(fonts.defaultFontSize);
const lineHeight = parseFloat(fonts.lineHeight);
// Hardcoded height beased on line-height of 32 + bottom margin of 10.
// Less side-effecty than using react-measure.
const titleRowHeight = 42;
function getContentHeight(rowHeight, isSmallScreen) {
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
return rowHeight - padding;
}
class BookIndexOverview extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditAuthorModalOpen: false,
isDeleteAuthorModalOpen: false
};
}
//
// Listeners
onEditAuthorPress = () => {
this.setState({ isEditAuthorModalOpen: true });
}
onEditAuthorModalClose = () => {
this.setState({ isEditAuthorModalOpen: false });
}
onDeleteAuthorPress = () => {
this.setState({
isEditAuthorModalOpen: false,
isDeleteAuthorModalOpen: true
});
}
onDeleteAuthorModalClose = () => {
this.setState({ isDeleteAuthorModalOpen: false });
}
//
// Render
render() {
const {
id,
title,
overview,
monitored,
titleSlug,
nextAiring,
statistics,
images,
posterWidth,
posterHeight,
qualityProfile,
overviewOptions,
showSearchAction,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat,
rowHeight,
isSmallScreen,
isRefreshingBook,
isSearchingBook,
onRefreshBookPress,
onSearchPress,
...otherProps
} = this.props;
const {
bookCount,
sizeOnDisk,
bookFileCount,
totalBookCount
} = statistics;
const {
isEditAuthorModalOpen,
isDeleteAuthorModalOpen
} = this.state;
const link = `/book/${titleSlug}`;
const elementStyle = {
width: `${posterWidth}px`,
height: `${posterHeight}px`,
objectFit: 'contain'
};
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
const overviewHeight = contentHeight - titleRowHeight;
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.posterContainer}>
{
status === 'ended' &&
<div
className={styles.ended}
title={translate('Ended')}
/>
}
<Link
className={styles.link}
style={elementStyle}
to={link}
>
<AuthorPoster
className={styles.poster}
style={elementStyle}
images={images}
coverType={'cover'}
size={250}
lazy={false}
overflow={true}
blurBackground={true}
/>
</Link>
<BookIndexProgressBar
monitored={monitored}
bookCount={bookCount}
bookFileCount={bookFileCount}
totalBookCount={totalBookCount}
posterWidth={posterWidth}
detailedProgressBar={overviewOptions.detailedProgressBar}
/>
</div>
<div className={styles.info} style={{ maxHeight: contentHeight }}>
<div className={styles.titleRow}>
<Link
className={styles.title}
to={link}
>
{title}
</Link>
<div className={styles.actions}>
<SpinnerIconButton
name={icons.REFRESH}
title={translate('RefreshBook')}
isSpinning={isRefreshingBook}
onPress={onRefreshBookPress}
/>
{
showSearchAction &&
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title={translate('SearchForMonitoredBooks')}
isSpinning={isSearchingBook}
onPress={onSearchPress}
/>
}
<IconButton
name={icons.EDIT}
title={translate('EditAuthor')}
onPress={this.onEditAuthorPress}
/>
</div>
</div>
<div className={styles.details}>
<Link
className={styles.overview}
to={link}
>
<TextTruncate
line={Math.floor(overviewHeight / (defaultFontSize * lineHeight))}
text={stripHtml(overview)}
/>
</Link>
<BookIndexOverviewInfo
height={overviewHeight}
monitored={monitored}
sizeOnDisk={sizeOnDisk}
qualityProfile={qualityProfile}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
longDateFormat={longDateFormat}
timeFormat={timeFormat}
{...overviewOptions}
{...otherProps}
/>
</div>
</div>
</div>
<EditAuthorModalConnector
isOpen={isEditAuthorModalOpen}
authorId={id}
onModalClose={this.onEditAuthorModalClose}
onDeleteAuthorPress={this.onDeleteAuthorPress}
/>
<DeleteAuthorModal
isOpen={isDeleteAuthorModalOpen}
authorId={id}
onModalClose={this.onDeleteAuthorModalClose}
/>
</div>
);
}
}
BookIndexOverview.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
titleSlug: PropTypes.string.isRequired,
nextAiring: PropTypes.string,
statistics: PropTypes.object.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
posterWidth: PropTypes.number.isRequired,
posterHeight: PropTypes.number.isRequired,
rowHeight: PropTypes.number.isRequired,
qualityProfile: PropTypes.object.isRequired,
overviewOptions: PropTypes.object.isRequired,
showSearchAction: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
isRefreshingBook: PropTypes.bool.isRequired,
isSearchingBook: PropTypes.bool.isRequired,
onRefreshBookPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired
};
BookIndexOverview.defaultProps = {
statistics: {
bookCount: 0,
bookFileCount: 0,
totalBookCount: 0
}
};
export default BookIndexOverview;
@@ -0,0 +1,12 @@
.infos {
display: flex;
flex: 0 0 250px;
flex-direction: column;
margin-left: 10px;
}
@media only screen and (max-width: $breakpointSmall) {
.infos {
margin-left: 0;
}
}
@@ -0,0 +1,205 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import BookIndexOverviewInfoRow from './BookIndexOverviewInfoRow';
import styles from './BookIndexOverviewInfo.css';
const infoRowHeight = parseInt(dimensions.authorIndexOverviewInfoRowHeight);
const rows = [
{
name: 'monitored',
showProp: 'showMonitored',
valueProp: 'monitored'
},
{
name: 'qualityProfileId',
showProp: 'showQualityProfile',
valueProp: 'qualityProfile'
},
{
name: 'releaseDate',
showProp: 'showReleaseDate',
valueProp: 'releaseDate'
},
{
name: 'added',
showProp: 'showAdded',
valueProp: 'added'
},
{
name: 'path',
showProp: 'showPath',
valueProp: 'author'
},
{
name: 'sizeOnDisk',
showProp: 'showSizeOnDisk',
valueProp: 'sizeOnDisk'
}
];
function isVisible(row, props) {
const {
name,
showProp,
valueProp
} = row;
if (props[valueProp] == null) {
return false;
}
return props[showProp] || props.sortKey === name;
}
function getInfoRowProps(row, props) {
const { name } = row;
if (name === 'monitored') {
const monitoredText = props.monitored ? 'Monitored' : 'Unmonitored';
return {
title: monitoredText,
iconName: props.monitored ? icons.MONITORED : icons.UNMONITORED,
label: monitoredText
};
}
if (name === 'qualityProfileId') {
return {
title: 'Quality Profile',
iconName: icons.PROFILE,
label: props.qualityProfile.name
};
}
if (name === 'releaseDate') {
const {
releaseDate,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat
} = props;
return {
title: `ReleaseDate: ${formatDateTime(releaseDate, longDateFormat, timeFormat)}`,
iconName: icons.CALENDAR,
label: getRelativeDate(
releaseDate,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: true
}
)
};
}
if (name === 'added') {
const {
added,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat
} = props;
return {
title: `Added: ${formatDateTime(added, longDateFormat, timeFormat)}`,
iconName: icons.ADD,
label: getRelativeDate(
added,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: true
}
)
};
}
if (name === 'path') {
return {
title: 'Path',
iconName: icons.FOLDER,
label: props.author.path
};
}
if (name === 'sizeOnDisk') {
return {
title: 'Size on Disk',
iconName: icons.DRIVE,
label: formatBytes(props.sizeOnDisk)
};
}
}
function BookIndexOverviewInfo(props) {
const {
height
} = props;
let shownRows = 1;
const maxRows = Math.floor(height / (infoRowHeight + 4));
return (
<div className={styles.infos}>
{
rows.map((row) => {
if (!isVisible(row, props)) {
return null;
}
if (shownRows >= maxRows) {
return null;
}
shownRows++;
const infoRowProps = getInfoRowProps(row, props);
return (
<BookIndexOverviewInfoRow
key={row.name}
{...infoRowProps}
/>
);
})
}
</div>
);
}
BookIndexOverviewInfo.propTypes = {
height: PropTypes.number.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
showAdded: PropTypes.bool.isRequired,
showReleaseDate: PropTypes.bool.isRequired,
showPath: PropTypes.bool.isRequired,
showSizeOnDisk: PropTypes.bool.isRequired,
monitored: PropTypes.bool.isRequired,
qualityProfile: PropTypes.object.isRequired,
author: PropTypes.object.isRequired,
releaseDate: PropTypes.string,
added: PropTypes.string,
sizeOnDisk: PropTypes.number,
sortKey: PropTypes.string.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired
};
export default BookIndexOverviewInfo;
@@ -0,0 +1,10 @@
.infoRow {
flex: 0 0 $authorIndexOverviewInfoRowHeight;
margin: 2px 0;
}
.icon {
margin-right: 5px;
width: 25px !important;
text-align: center;
}
@@ -0,0 +1,35 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import styles from './BookIndexOverviewInfoRow.css';
function BookIndexOverviewInfoRow(props) {
const {
title,
iconName,
label
} = props;
return (
<div
className={styles.infoRow}
title={title}
>
<Icon
className={styles.icon}
name={iconName}
size={14}
/>
{label}
</div>
);
}
BookIndexOverviewInfoRow.propTypes = {
title: PropTypes.string,
iconName: PropTypes.object.isRequired,
label: PropTypes.string.isRequired
};
export default BookIndexOverviewInfoRow;
@@ -0,0 +1,3 @@
.grid {
flex: 1 0 auto;
}
@@ -0,0 +1,269 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Grid, WindowScroller } from 'react-virtualized';
import BookIndexItemConnector from 'Book/Index/BookIndexItemConnector';
import Measure from 'Components/Measure';
import dimensions from 'Styles/Variables/dimensions';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import BookIndexOverview from './BookIndexOverview';
import styles from './BookIndexOverviews.css';
// Poster container dimensions
const columnPadding = parseInt(dimensions.authorIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(dimensions.authorIndexColumnPaddingSmallScreen);
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
function calculatePosterWidth(posterSize, isSmallScreen) {
const maxiumPosterWidth = isSmallScreen ? 192 : 202;
if (posterSize === 'large') {
return maxiumPosterWidth;
}
if (posterSize === 'medium') {
return Math.floor(maxiumPosterWidth * 0.75);
}
return Math.floor(maxiumPosterWidth * 0.5);
}
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
const {
detailedProgressBar
} = overviewOptions;
const heights = [
posterHeight,
detailedProgressBar ? detailedProgressBarHeight : progressBarHeight,
isSmallScreen ? columnPaddingSmallScreen : columnPadding
];
return heights.reduce((acc, height) => acc + height, 0);
}
function calculatePosterHeight(posterWidth) {
return Math.ceil((400 / 256) * posterWidth);
}
class BookIndexOverviews extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
width: 0,
columnCount: 1,
posterWidth: 162,
posterHeight: 253,
rowHeight: calculateRowHeight(253, null, props.isSmallScreen, {}),
scrollRestored: false
};
this._grid = null;
}
componentDidUpdate(prevProps, prevState) {
const {
items,
sortKey,
overviewOptions,
jumpToCharacter,
scrollTop
} = this.props;
const {
width,
rowHeight,
scrollRestored
} = this.state;
if (prevProps.sortKey !== sortKey ||
prevProps.overviewOptions !== overviewOptions) {
this.calculateGrid();
}
if (this._grid &&
(prevState.width !== width ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items) ||
prevProps.overviewOptions !== overviewOptions)) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (this._grid && index != null) {
this._grid.scrollToCell({
rowIndex: index,
columnIndex: 0
});
}
}
}
//
// Control
setGridRef = (ref) => {
this._grid = ref;
}
calculateGrid = (width = this.state.width, isSmallScreen) => {
const {
sortKey,
overviewOptions
} = this.props;
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
const posterHeight = calculatePosterHeight(posterWidth);
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
this.setState({
width,
posterWidth,
posterHeight,
rowHeight
});
}
cellRenderer = ({ key, rowIndex, style }) => {
const {
items,
sortKey,
overviewOptions,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat,
isSmallScreen
} = this.props;
const {
posterWidth,
posterHeight,
rowHeight
} = this.state;
const book = items[rowIndex];
if (!book) {
return null;
}
return (
<div
key={key}
style={style}
>
<BookIndexItemConnector
key={book.id}
component={BookIndexOverview}
sortKey={sortKey}
posterWidth={posterWidth}
posterHeight={posterHeight}
rowHeight={rowHeight}
overviewOptions={overviewOptions}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
longDateFormat={longDateFormat}
timeFormat={timeFormat}
isSmallScreen={isSmallScreen}
bookId={book.id}
authorId={book.authorId}
/>
</div>
);
}
//
// Listeners
onMeasure = ({ width }) => {
this.calculateGrid(width, this.props.isSmallScreen);
}
//
// Render
render() {
const {
items,
isSmallScreen,
scroller
} = this.props;
const {
width,
rowHeight
} = this.state;
return (
<Measure
onMeasure={this.onMeasure}
>
<WindowScroller
scrollElement={isSmallScreen ? undefined : scroller}
>
{({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return <div />;
}
return (
<div ref={registerChild}>
<Grid
ref={this.setGridRef}
className={styles.grid}
autoHeight={true}
height={height}
columnCount={1}
columnWidth={width}
rowCount={items.length}
rowHeight={rowHeight}
width={width}
onScroll={onChildScroll}
scrollTop={scrollTop}
overscanRowCount={2}
cellRenderer={this.cellRenderer}
onSectionRendered={this.onSectionRendered}
scrollToAlignment={'start'}
isScrollingOptOut={true}
/>
</div>
);
}
}
</WindowScroller>
</Measure>
);
}
}
BookIndexOverviews.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
overviewOptions: PropTypes.object.isRequired,
scrollTop: PropTypes.number.isRequired,
jumpToCharacter: PropTypes.string,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired
};
export default BookIndexOverviews;
@@ -0,0 +1,25 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import BookIndexOverviews from './BookIndexOverviews';
function createMapStateToProps() {
return createSelector(
(state) => state.authorIndex.overviewOptions,
createUISettingsSelector(),
createDimensionsSelector(),
(overviewOptions, uiSettings, dimensions) => {
return {
overviewOptions,
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat,
isSmallScreen: dimensions.isSmallScreen
};
}
);
}
export default connect(createMapStateToProps)(BookIndexOverviews);
@@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import BookIndexOverviewOptionsModalContentConnector from './BookIndexOverviewOptionsModalContentConnector';
function BookIndexOverviewOptionsModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<BookIndexOverviewOptionsModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
BookIndexOverviewOptionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default BookIndexOverviewOptionsModal;
@@ -0,0 +1,287 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
{ key: 'large', value: 'Large' }
];
class BookIndexOverviewOptionsModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
detailedProgressBar: props.detailedProgressBar,
size: props.size,
showReleaseDate: props.showReleaseDate,
showMonitored: props.showMonitored,
showQualityProfile: props.showQualityProfile,
showAdded: props.showAdded,
showPath: props.showPath,
showSizeOnDisk: props.showSizeOnDisk,
showSearchAction: props.showSearchAction
};
}
componentDidUpdate(prevProps) {
const {
detailedProgressBar,
size,
showReleaseDate,
showMonitored,
showQualityProfile,
showAdded,
showPath,
showSizeOnDisk,
showSearchAction
} = this.props;
const state = {};
if (detailedProgressBar !== prevProps.detailedProgressBar) {
state.detailedProgressBar = detailedProgressBar;
}
if (size !== prevProps.size) {
state.size = size;
}
if (showReleaseDate !== prevProps.showReleaseDate) {
state.showReleaseDate = showReleaseDate;
}
if (showMonitored !== prevProps.showMonitored) {
state.showMonitored = showMonitored;
}
if (showQualityProfile !== prevProps.showQualityProfile) {
state.showQualityProfile = showQualityProfile;
}
if (showAdded !== prevProps.showAdded) {
state.showAdded = showAdded;
}
if (showPath !== prevProps.showPath) {
state.showPath = showPath;
}
if (showSizeOnDisk !== prevProps.showSizeOnDisk) {
state.showSizeOnDisk = showSizeOnDisk;
}
if (showSearchAction !== prevProps.showSearchAction) {
state.showSearchAction = showSearchAction;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
}
//
// Listeners
onChangeOverviewOption = ({ name, value }) => {
this.setState({
[name]: value
}, () => {
this.props.onChangeOverviewOption({ [name]: value });
});
}
//
// Render
render() {
const {
onModalClose
} = this.props;
const {
detailedProgressBar,
size,
showReleaseDate,
showMonitored,
showQualityProfile,
showAdded,
showPath,
showSizeOnDisk,
showSearchAction
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Overview Options
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>
{translate('PosterSize')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="size"
value={size}
values={posterSizeOptions}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('DetailedProgressBar')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="detailedProgressBar"
value={detailedProgressBar}
helpText={translate('DetailedProgressBarHelpText')}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowReleaseDate')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showReleaseDate"
value={showReleaseDate}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowMonitored')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showMonitored"
value={showMonitored}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowQualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showQualityProfile"
value={showQualityProfile}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowDateAdded')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showAdded"
value={showAdded}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowPath')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showPath"
value={showPath}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowSizeOnDisk')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSizeOnDisk"
value={showSizeOnDisk}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText={translate('ShowSearchActionHelpText')}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
Close
</Button>
</ModalFooter>
</ModalContent>
);
}
}
BookIndexOverviewOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
showReleaseDate: PropTypes.bool.isRequired,
showAdded: PropTypes.bool.isRequired,
showPath: PropTypes.bool.isRequired,
showSizeOnDisk: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onChangeOverviewOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default BookIndexOverviewOptionsModalContent;
@@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setAuthorOverviewOption } from 'Store/Actions/authorIndexActions';
import BookIndexOverviewOptionsModalContent from './BookIndexOverviewOptionsModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.authorIndex,
(authorIndex) => {
return authorIndex.overviewOptions;
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onChangeOverviewOption(payload) {
dispatch(setAuthorOverviewOption(payload));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(BookIndexOverviewOptionsModalContent);