1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-11-26 04:33:01 +01:00

New: Add more information to Select Series Modal

Co-authored-by: Qstick <qstick@gmail.com>
This commit is contained in:
Bogdan 2023-08-29 05:26:38 +03:00 committed by GitHub
parent dc6204d377
commit ec9b29e364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 305 additions and 26 deletions

View File

@ -0,0 +1,4 @@
.row {
composes: link from '~Components/Link/Link.css';
composes: row from '~./VirtualTableRow.css';
}

View File

@ -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;

View File

@ -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;

View File

@ -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 { FixedSizeList as List, ListChildComponentProps } from 'react-window';
import TextInput from 'Components/Form/TextInput';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
@ -7,24 +15,136 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Scroller from 'Components/Scroller/Scroller';
import Column from 'Components/Table/Column';
import VirtualTableRowButton from 'Components/Table/VirtualTableRowButton';
import { scrollDirections } from 'Helpers/Props';
import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import dimensions from 'Styles/Variables/dimensions';
import translate from 'Utilities/String/translate';
import SelectSeriesModalTableHeader from './SelectSeriesModalTableHeader';
import SelectSeriesRow from './SelectSeriesRow';
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 {
modalTitle: string;
onSeriesSelect(series: Series): 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) {
const { modalTitle, onSeriesSelect, onModalClose } = props;
const listRef = useRef<List<RowItemData>>(null);
const scrollerRef = useRef<HTMLDivElement>(null);
const allSeries: Series[] = useSelector(createAllSeriesSelector());
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(
({ value }: { value: string }) => {
@ -47,8 +167,11 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
a.sortTitle.localeCompare(b.sortTitle)
);
return sorted.filter((item) =>
item.title.toLowerCase().includes(filter.toLowerCase())
return sorted.filter(
(item) =>
item.title.toLowerCase().includes(filter.toLowerCase()) ||
item.tvdbId.toString().includes(filter) ||
item.imdbId?.includes(filter)
);
}, [allSeries, filter]);
@ -69,17 +192,31 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
onChange={onFilterChange}
/>
<Scroller className={styles.scroller} autoFocus={false}>
{items.map((item) => {
return (
<SelectSeriesRow
key={item.id}
id={item.id}
title={item.title}
onSeriesSelect={onSeriesSelectWrapper}
/>
);
})}
<Scroller
className={styles.scroller}
autoFocus={false}
ref={scrollerRef}
>
<SelectSeriesModalTableHeader columns={columns} />
<List<RowItemData>
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>
</ModalBody>

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +1,25 @@
.series {
padding: 8px;
border-bottom: 1px solid var(--borderColor);
.cell {
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
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;
}

View File

@ -1,7 +1,11 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'series': string;
'cell': string;
'imdbId': string;
'title': string;
'tvdbId': string;
'year': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
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';
class SelectSeriesRow extends Component {
@ -17,13 +18,27 @@ class SelectSeriesRow extends Component {
render() {
return (
<Link
className={styles.series}
component="div"
onPress={this.onPress}
>
{this.props.title}
</Link>
<>
<VirtualTableRowCell className={styles.title}>
{this.props.title}
</VirtualTableRowCell>
<VirtualTableRowCell className={styles.year}>
{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 = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
tvdbId: PropTypes.number.isRequired,
imdbId: PropTypes.string,
year: PropTypes.number.isRequired,
onSeriesSelect: PropTypes.func.isRequired
};

View File

@ -538,6 +538,7 @@
"Ignored": "Ignored",
"IgnoredAddresses": "Ignored Addresses",
"Images": "Images",
"ImdbId": "IMDb ID",
"Implementation": "Implementation",
"Import": "Import",
"ImportCountSeries": "Import {selectedCount} Series",