New: Add more information to Select Series Modal
Co-authored-by: Qstick <qstick@gmail.com>
This commit is contained in:
parent
dc6204d377
commit
ec9b29e364
|
@ -0,0 +1,4 @@
|
||||||
|
.row {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
composes: row from '~./VirtualTableRow.css';
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'row': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import VirtualTableRow from './VirtualTableRow';
|
||||||
|
import styles from './VirtualTableRowButton.css';
|
||||||
|
|
||||||
|
function VirtualTableRowButton(props) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className={styles.row}
|
||||||
|
component={VirtualTableRow}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VirtualTableRowButton;
|
|
@ -1,5 +1,13 @@
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import { throttle } from 'lodash';
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
@ -7,24 +15,136 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
|
import Column from 'Components/Table/Column';
|
||||||
|
import VirtualTableRowButton from 'Components/Table/VirtualTableRowButton';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import Series from 'Series/Series';
|
import Series from 'Series/Series';
|
||||||
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
|
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
|
||||||
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import SelectSeriesModalTableHeader from './SelectSeriesModalTableHeader';
|
||||||
import SelectSeriesRow from './SelectSeriesRow';
|
import SelectSeriesRow from './SelectSeriesRow';
|
||||||
import styles from './SelectSeriesModalContent.css';
|
import styles from './SelectSeriesModalContent.css';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
label: () => translate('Title'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'year',
|
||||||
|
label: () => translate('Year'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tvdbId',
|
||||||
|
label: () => translate('TvdbId'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'imdbId',
|
||||||
|
label: () => translate('ImdbId'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const bodyPadding = parseInt(dimensions.pageContentBodyPadding);
|
||||||
|
|
||||||
interface SelectSeriesModalContentProps {
|
interface SelectSeriesModalContentProps {
|
||||||
modalTitle: string;
|
modalTitle: string;
|
||||||
onSeriesSelect(series: Series): void;
|
onSeriesSelect(series: Series): void;
|
||||||
onModalClose(): void;
|
onModalClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RowItemData {
|
||||||
|
items: Series[];
|
||||||
|
columns: Column[];
|
||||||
|
onSeriesSelect(seriesId: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
|
||||||
|
index,
|
||||||
|
style,
|
||||||
|
data,
|
||||||
|
}) => {
|
||||||
|
const { items, columns, onSeriesSelect } = data;
|
||||||
|
|
||||||
|
if (index >= items.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = items[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableRowButton
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
onPress={() => onSeriesSelect(series.id)}
|
||||||
|
>
|
||||||
|
<SelectSeriesRow
|
||||||
|
key={series.id}
|
||||||
|
id={series.id}
|
||||||
|
title={series.title}
|
||||||
|
tvdbId={series.tvdbId}
|
||||||
|
imdbId={series.imdbId}
|
||||||
|
year={series.year}
|
||||||
|
columns={columns}
|
||||||
|
onSeriesSelect={onSeriesSelect}
|
||||||
|
/>
|
||||||
|
</VirtualTableRowButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||||
const { modalTitle, onSeriesSelect, onModalClose } = props;
|
const { modalTitle, onSeriesSelect, onModalClose } = props;
|
||||||
|
|
||||||
|
const listRef = useRef<List<RowItemData>>(null);
|
||||||
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
||||||
const allSeries: Series[] = useSelector(createAllSeriesSelector());
|
const allSeries: Series[] = useSelector(createAllSeriesSelector());
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const current = scrollerRef?.current as HTMLElement;
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
const width = current.clientWidth;
|
||||||
|
const height = current.clientHeight;
|
||||||
|
const padding = bodyPadding - 5;
|
||||||
|
|
||||||
|
setSize({
|
||||||
|
width: width - padding * 2,
|
||||||
|
height: height + padding,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [windowHeight, scrollerRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||||
|
const currentScrollListener = currentScrollerRef;
|
||||||
|
|
||||||
|
const handleScroll = throttle(() => {
|
||||||
|
const { offsetTop = 0 } = currentScrollerRef;
|
||||||
|
const scrollTop = currentScrollerRef.scrollTop - offsetTop;
|
||||||
|
|
||||||
|
listRef.current?.scrollTo(scrollTop);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
currentScrollListener.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
handleScroll.cancel();
|
||||||
|
|
||||||
|
if (currentScrollListener) {
|
||||||
|
currentScrollListener.removeEventListener('scroll', handleScroll);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [listRef, scrollerRef]);
|
||||||
|
|
||||||
const onFilterChange = useCallback(
|
const onFilterChange = useCallback(
|
||||||
({ value }: { value: string }) => {
|
({ value }: { value: string }) => {
|
||||||
|
@ -47,8 +167,11 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||||
a.sortTitle.localeCompare(b.sortTitle)
|
a.sortTitle.localeCompare(b.sortTitle)
|
||||||
);
|
);
|
||||||
|
|
||||||
return sorted.filter((item) =>
|
return sorted.filter(
|
||||||
item.title.toLowerCase().includes(filter.toLowerCase())
|
(item) =>
|
||||||
|
item.title.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
item.tvdbId.toString().includes(filter) ||
|
||||||
|
item.imdbId?.includes(filter)
|
||||||
);
|
);
|
||||||
}, [allSeries, filter]);
|
}, [allSeries, filter]);
|
||||||
|
|
||||||
|
@ -69,17 +192,31 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
|
||||||
onChange={onFilterChange}
|
onChange={onFilterChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Scroller className={styles.scroller} autoFocus={false}>
|
<Scroller
|
||||||
{items.map((item) => {
|
className={styles.scroller}
|
||||||
return (
|
autoFocus={false}
|
||||||
<SelectSeriesRow
|
ref={scrollerRef}
|
||||||
key={item.id}
|
>
|
||||||
id={item.id}
|
<SelectSeriesModalTableHeader columns={columns} />
|
||||||
title={item.title}
|
<List<RowItemData>
|
||||||
onSeriesSelect={onSeriesSelectWrapper}
|
ref={listRef}
|
||||||
/>
|
style={{
|
||||||
);
|
width: '100%',
|
||||||
})}
|
height: '100%',
|
||||||
|
overflow: 'none',
|
||||||
|
}}
|
||||||
|
width={size.width}
|
||||||
|
height={size.height}
|
||||||
|
itemCount={items.length}
|
||||||
|
itemSize={38}
|
||||||
|
itemData={{
|
||||||
|
items,
|
||||||
|
columns,
|
||||||
|
onSeriesSelect: onSeriesSelectWrapper,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Row}
|
||||||
|
</List>
|
||||||
</Scroller>
|
</Scroller>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
.title {
|
||||||
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 4 0 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 0 0 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imdbId,
|
||||||
|
.tvdbId {
|
||||||
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 0 0 110px;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'imdbId': string;
|
||||||
|
'title': string;
|
||||||
|
'tvdbId': string;
|
||||||
|
'year': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Column from 'Components/Table/Column';
|
||||||
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
|
import styles from './SelectSeriesModalTableHeader.css';
|
||||||
|
|
||||||
|
interface SelectSeriesModalTableHeaderProps {
|
||||||
|
columns: Column[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectSeriesModalTableHeader(
|
||||||
|
props: SelectSeriesModalTableHeaderProps
|
||||||
|
) {
|
||||||
|
const { columns } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableHeader>
|
||||||
|
{columns.map((column) => {
|
||||||
|
const { name, label, isVisible } = column;
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableHeaderCell
|
||||||
|
key={name}
|
||||||
|
className={
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
styles[name]
|
||||||
|
}
|
||||||
|
name={name}
|
||||||
|
>
|
||||||
|
{typeof label === 'function' ? label() : label}
|
||||||
|
</VirtualTableHeaderCell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VirtualTableHeader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectSeriesModalTableHeader;
|
|
@ -1,4 +1,25 @@
|
||||||
.series {
|
.cell {
|
||||||
padding: 8px;
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
border-bottom: 1px solid var(--borderColor);
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
composes: cell;
|
||||||
|
|
||||||
|
flex: 4 0 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
composes: cell;
|
||||||
|
|
||||||
|
flex: 0 0 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tvdbId,
|
||||||
|
.imdbId {
|
||||||
|
composes: cell;
|
||||||
|
|
||||||
|
flex: 0 0 110px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'series': string;
|
'cell': string;
|
||||||
|
'imdbId': string;
|
||||||
|
'title': string;
|
||||||
|
'tvdbId': string;
|
||||||
|
'year': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Label from 'Components/Label';
|
||||||
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
import styles from './SelectSeriesRow.css';
|
import styles from './SelectSeriesRow.css';
|
||||||
|
|
||||||
class SelectSeriesRow extends Component {
|
class SelectSeriesRow extends Component {
|
||||||
|
@ -17,13 +18,27 @@ class SelectSeriesRow extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Link
|
<>
|
||||||
className={styles.series}
|
<VirtualTableRowCell className={styles.title}>
|
||||||
component="div"
|
{this.props.title}
|
||||||
onPress={this.onPress}
|
</VirtualTableRowCell>
|
||||||
>
|
|
||||||
{this.props.title}
|
<VirtualTableRowCell className={styles.year}>
|
||||||
</Link>
|
{this.props.year}
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
|
||||||
|
<VirtualTableRowCell className={styles.tvdbId}>
|
||||||
|
<Label>{this.props.tvdbId}</Label>
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
|
||||||
|
<VirtualTableRowCell className={styles.imdbId}>
|
||||||
|
{
|
||||||
|
this.props.imdbId ?
|
||||||
|
<Label>{this.props.imdbId}</Label> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +46,9 @@ class SelectSeriesRow extends Component {
|
||||||
SelectSeriesRow.propTypes = {
|
SelectSeriesRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
|
tvdbId: PropTypes.number.isRequired,
|
||||||
|
imdbId: PropTypes.string,
|
||||||
|
year: PropTypes.number.isRequired,
|
||||||
onSeriesSelect: PropTypes.func.isRequired
|
onSeriesSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -538,6 +538,7 @@
|
||||||
"Ignored": "Ignored",
|
"Ignored": "Ignored",
|
||||||
"IgnoredAddresses": "Ignored Addresses",
|
"IgnoredAddresses": "Ignored Addresses",
|
||||||
"Images": "Images",
|
"Images": "Images",
|
||||||
|
"ImdbId": "IMDb ID",
|
||||||
"Implementation": "Implementation",
|
"Implementation": "Implementation",
|
||||||
"Import": "Import",
|
"Import": "Import",
|
||||||
"ImportCountSeries": "Import {selectedCount} Series",
|
"ImportCountSeries": "Import {selectedCount} Series",
|
||||||
|
|
Loading…
Reference in New Issue