Convert Table to TypeScript

pull/7640/head
Mark McDowall 4 months ago
parent 0fdeb05663
commit 699120a8fd
No known key found for this signature in database

@ -9,13 +9,13 @@ export type SelectContextAction =
| { type: 'unselectAll' }
| {
type: 'toggleSelected';
id: number;
isSelected: boolean;
id: number | string;
isSelected: boolean | null;
shiftKey: boolean;
}
| {
type: 'removeItem';
id: number;
id: number | string;
}
| {
type: 'updateItems';

@ -4,7 +4,7 @@ import { InputChanged } from 'typings/inputs';
import styles from './SelectInput.css';
interface SelectInputOption {
key: string;
key: string | number;
value: string | number | (() => string | number);
}

@ -1,80 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import TableRowCell from './TableRowCell';
import styles from './TableSelectCell.css';
class TableSelectCell extends Component {
//
// Lifecycle
componentDidMount() {
const {
id,
isSelected,
onSelectedChange
} = this.props;
onSelectedChange({ id, value: isSelected });
}
componentWillUnmount() {
const {
id,
onSelectedChange
} = this.props;
onSelectedChange({ id, value: null });
}
//
// Listeners
onChange = ({ value, shiftKey }, a, b, c, d) => {
const {
id,
onSelectedChange
} = this.props;
onSelectedChange({ id, value, shiftKey });
};
//
// Render
render() {
const {
className,
id,
isSelected,
...otherProps
} = this.props;
return (
<TableRowCell className={className}>
<CheckInput
className={styles.input}
name={id.toString()}
value={isSelected}
{...otherProps}
onChange={this.onChange}
/>
</TableRowCell>
);
}
}
TableSelectCell.propTypes = {
className: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
isSelected: PropTypes.bool.isRequired,
onSelectedChange: PropTypes.func.isRequired
};
TableSelectCell.defaultProps = {
className: styles.selectCell,
isSelected: false
};
export default TableSelectCell;

@ -0,0 +1,59 @@
import React, { useCallback, useEffect, useRef } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import { CheckInputChanged } from 'typings/inputs';
import { SelectStateInputProps } from 'typings/props';
import TableRowCell, { TableRowCellProps } from './TableRowCell';
import styles from './TableSelectCell.css';
interface TableSelectCellProps extends Omit<TableRowCellProps, 'id'> {
className?: string;
id: number | string;
isSelected?: boolean;
onSelectedChange: (options: SelectStateInputProps) => void;
}
function TableSelectCell({
className = styles.selectCell,
id,
isSelected = false,
onSelectedChange,
...otherProps
}: TableSelectCellProps) {
const initialIsSelected = useRef(isSelected);
const handleSelectedChange = useRef(onSelectedChange);
handleSelectedChange.current = onSelectedChange;
const handleChange = useCallback(
({ value, shiftKey }: CheckInputChanged) => {
onSelectedChange({ id, value, shiftKey });
},
[id, onSelectedChange]
);
useEffect(() => {
handleSelectedChange.current({
id,
value: initialIsSelected.current,
shiftKey: false,
});
return () => {
handleSelectedChange.current({ id, value: null, shiftKey: false });
};
}, [id]);
return (
<TableRowCell className={className}>
<CheckInput
className={styles.input}
name={id.toString()}
value={isSelected}
{...otherProps}
onChange={handleChange}
/>
</TableRowCell>
);
}
export default TableSelectCell;

@ -1,29 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './VirtualTableRowCell.css';
function VirtualTableRowCell(props) {
const {
className,
children
} = props;
return (
<div
className={className}
>
{children}
</div>
);
}
VirtualTableRowCell.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
};
VirtualTableRowCell.defaultProps = {
className: styles.cell
};
export default VirtualTableRowCell;

@ -0,0 +1,16 @@
import React from 'react';
import styles from './VirtualTableRowCell.css';
export interface VirtualTableRowCellProps {
className?: string;
children?: string | React.ReactNode;
}
function VirtualTableRowCell({
className = styles.cell,
children,
}: VirtualTableRowCellProps) {
return <div className={className}>{children}</div>;
}
export default VirtualTableRowCell;

@ -1,83 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import VirtualTableRowCell from './VirtualTableRowCell';
import styles from './VirtualTableSelectCell.css';
export function virtualTableSelectCellRenderer(cellProps) {
const {
cellKey,
rowData,
columnData,
...otherProps
} = cellProps;
return (
// eslint-disable-next-line no-use-before-define
<VirtualTableSelectCell
key={cellKey}
id={rowData.name}
isSelected={rowData.isSelected}
{...columnData}
{...otherProps}
/>
);
}
class VirtualTableSelectCell extends Component {
//
// Listeners
onChange = ({ value, shiftKey }) => {
const {
id,
onSelectedChange
} = this.props;
onSelectedChange({ id, value, shiftKey });
};
//
// Render
render() {
const {
inputClassName,
id,
isSelected,
isDisabled,
...otherProps
} = this.props;
return (
<VirtualTableRowCell
className={styles.cell}
{...otherProps}
>
<CheckInput
className={inputClassName}
name={id.toString()}
value={isSelected}
isDisabled={isDisabled}
onChange={this.onChange}
/>
</VirtualTableRowCell>
);
}
}
VirtualTableSelectCell.propTypes = {
inputClassName: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
onSelectedChange: PropTypes.func.isRequired
};
VirtualTableSelectCell.defaultProps = {
inputClassName: styles.input,
isSelected: false
};
export default VirtualTableSelectCell;

@ -0,0 +1,46 @@
import React, { useCallback } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import { CheckInputChanged } from 'typings/inputs';
import { SelectStateInputProps } from 'typings/props';
import VirtualTableRowCell, {
VirtualTableRowCellProps,
} from './VirtualTableRowCell';
import styles from './VirtualTableSelectCell.css';
interface VirtualTableSelectCellProps extends VirtualTableRowCellProps {
inputClassName?: string;
id: number;
isSelected?: boolean;
isDisabled: boolean;
onSelectedChange: (options: SelectStateInputProps) => void;
}
function VirtualTableSelectCell({
inputClassName = styles.input,
id,
isSelected = false,
isDisabled,
onSelectedChange,
...otherProps
}: VirtualTableSelectCellProps) {
const handleChange = useCallback(
({ value, shiftKey }: CheckInputChanged) => {
onSelectedChange({ id, value, shiftKey });
},
[id, onSelectedChange]
);
return (
<VirtualTableRowCell className={styles.cell} {...otherProps}>
<CheckInput
className={inputClassName}
name={id.toString()}
value={isSelected}
isDisabled={isDisabled}
onChange={handleChange}
/>
</VirtualTableRowCell>
);
}
export default VirtualTableSelectCell;

@ -1,146 +0,0 @@
import classNames from 'classnames';
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import IconButton from 'Components/Link/IconButton';
import Scroller from 'Components/Scroller/Scroller';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { icons, scrollDirections } from 'Helpers/Props';
import TableHeader from './TableHeader';
import TableHeaderCell from './TableHeaderCell';
import TableSelectAllHeaderCell from './TableSelectAllHeaderCell';
import styles from './Table.css';
const tableHeaderCellProps = [
'sortKey',
'sortDirection'
];
function getTableHeaderCellProps(props) {
return _.reduce(tableHeaderCellProps, (result, key) => {
if (props.hasOwnProperty(key)) {
result[key] = props[key];
}
return result;
}, {});
}
function Table(props) {
const {
className,
horizontalScroll,
selectAll,
columns,
optionsComponent,
pageSize,
canModifyColumns,
children,
onSortPress,
onTableOptionChange,
...otherProps
} = props;
return (
<Scroller
className={classNames(
styles.tableContainer,
horizontalScroll && styles.horizontalScroll
)}
scrollDirection={
horizontalScroll ?
scrollDirections.HORIZONTAL :
scrollDirections.NONE
}
autoFocus={false}
>
<table className={className}>
<TableHeader>
{
selectAll ?
<TableSelectAllHeaderCell {...otherProps} /> :
null
}
{
columns.map((column) => {
const {
name,
isVisible,
isSortable,
...otherColumnProps
} = column;
if (!isVisible) {
return null;
}
if (
(name === 'actions' || name === 'details') &&
onTableOptionChange
) {
return (
<TableHeaderCell
key={name}
className={styles[name]}
name={name}
isSortable={false}
{...otherProps}
{...otherColumnProps}
>
<TableOptionsModalWrapper
columns={columns}
optionsComponent={optionsComponent}
pageSize={pageSize}
canModifyColumns={canModifyColumns}
onTableOptionChange={onTableOptionChange}
>
<IconButton
name={icons.ADVANCED_SETTINGS}
/>
</TableOptionsModalWrapper>
</TableHeaderCell>
);
}
return (
<TableHeaderCell
key={column.name}
onSortPress={onSortPress}
{...getTableHeaderCellProps(otherProps)}
{...column}
>
{typeof column.label === 'function' ? column.label() : column.label}
</TableHeaderCell>
);
})
}
</TableHeader>
{children}
</table>
</Scroller>
);
}
Table.propTypes = {
...TableHeaderCell.props,
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
optionsComponent: PropTypes.elementType,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool,
children: PropTypes.node,
onSortPress: PropTypes.func,
onTableOptionChange: PropTypes.func
};
Table.defaultProps = {
className: styles.table,
horizontalScroll: true,
selectAll: false
};
export default Table;

@ -0,0 +1,124 @@
import classNames from 'classnames';
import React from 'react';
import IconButton from 'Components/Link/IconButton';
import Scroller from 'Components/Scroller/Scroller';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import { icons, scrollDirections } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import { CheckInputChanged } from 'typings/inputs';
import { TableOptionsChangePayload } from 'typings/Table';
import Column from './Column';
import TableHeader from './TableHeader';
import TableHeaderCell from './TableHeaderCell';
import TableSelectAllHeaderCell from './TableSelectAllHeaderCell';
import styles from './Table.css';
interface TableProps {
className?: string;
horizontalScroll?: boolean;
selectAll?: boolean;
allSelected?: boolean;
allUnselected?: boolean;
columns: Column[];
optionsComponent?: React.ElementType;
pageSize?: number;
canModifyColumns?: boolean;
sortKey?: string;
sortDirection?: SortDirection;
children?: React.ReactNode;
onSortPress?: (name: string, sortDirection?: SortDirection) => void;
onTableOptionChange?: (payload: TableOptionsChangePayload) => void;
onSelectAllChange?: (change: CheckInputChanged) => void;
}
function Table({
className = styles.table,
horizontalScroll = true,
selectAll = false,
allSelected = false,
allUnselected = false,
columns,
optionsComponent,
pageSize,
canModifyColumns,
sortKey,
sortDirection,
children,
onSortPress,
onTableOptionChange,
onSelectAllChange,
}: TableProps) {
return (
<Scroller
className={classNames(
styles.tableContainer,
horizontalScroll && styles.horizontalScroll
)}
scrollDirection={
horizontalScroll ? scrollDirections.HORIZONTAL : scrollDirections.NONE
}
autoFocus={false}
>
<table className={className}>
<TableHeader>
{selectAll && onSelectAllChange ? (
<TableSelectAllHeaderCell
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
/>
) : null}
{columns.map((column) => {
const { name, isVisible, isSortable, ...otherColumnProps } = column;
if (!isVisible) {
return null;
}
if (
(name === 'actions' || name === 'details') &&
onTableOptionChange
) {
return (
<TableHeaderCell
key={name}
name={name}
isSortable={false}
{...otherColumnProps}
>
<TableOptionsModalWrapper
columns={columns}
optionsComponent={optionsComponent}
pageSize={pageSize}
canModifyColumns={canModifyColumns}
onTableOptionChange={onTableOptionChange}
>
<IconButton name={icons.ADVANCED_SETTINGS} />
</TableOptionsModalWrapper>
</TableHeaderCell>
);
}
return (
<TableHeaderCell
key={column.name}
{...column}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
>
{typeof column.label === 'function'
? column.label()
: column.label}
</TableHeaderCell>
);
})}
</TableHeader>
{children}
</table>
</Scroller>
);
}
export default Table;

@ -1,25 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
class TableBody extends Component {
//
// Render
render() {
const {
children
} = this.props;
return (
<tbody>{children}</tbody>
);
}
}
TableBody.propTypes = {
children: PropTypes.node
};
export default TableBody;

@ -0,0 +1,11 @@
import React from 'react';
interface TableBodyProps {
children?: React.ReactNode;
}
function TableBody({ children }: TableBodyProps) {
return <tbody>{children}</tbody>;
}
export default TableBody;

@ -1,28 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
class TableHeader extends Component {
//
// Render
render() {
const {
children
} = this.props;
return (
<thead>
<tr>
{children}
</tr>
</thead>
);
}
}
TableHeader.propTypes = {
children: PropTypes.node
};
export default TableHeader;

@ -0,0 +1,15 @@
import React from 'react';
interface TableHeaderProps {
children?: React.ReactNode;
}
function TableHeader({ children }: TableHeaderProps) {
return (
<thead>
<tr>{children}</tr>
</thead>
);
}
export default TableHeader;

@ -1,99 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, sortDirections } from 'Helpers/Props';
import styles from './TableHeaderCell.css';
class TableHeaderCell extends Component {
//
// Listeners
onPress = () => {
const {
name,
fixedSortDirection
} = this.props;
if (fixedSortDirection) {
this.props.onSortPress(name, fixedSortDirection);
} else {
this.props.onSortPress(name);
}
};
//
// Render
render() {
const {
className,
name,
label,
columnLabel,
isSortable,
isVisible,
isModifiable,
sortKey,
sortDirection,
fixedSortDirection,
children,
onSortPress,
...otherProps
} = this.props;
const isSorting = isSortable && sortKey === name;
const sortIcon = sortDirection === sortDirections.ASCENDING ?
icons.SORT_ASCENDING :
icons.SORT_DESCENDING;
return (
isSortable ?
<Link
{...otherProps}
component="th"
className={className}
label={typeof label === 'function' ? label() : label}
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
onPress={this.onPress}
>
{children}
{
isSorting &&
<Icon
name={sortIcon}
className={styles.sortIcon}
/>
}
</Link> :
<th className={className}>
{children}
</th>
);
}
}
TableHeaderCell.propTypes = {
className: PropTypes.string,
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
isSortable: PropTypes.bool,
isVisible: PropTypes.bool,
isModifiable: PropTypes.bool,
sortKey: PropTypes.string,
fixedSortDirection: PropTypes.string,
sortDirection: PropTypes.string,
children: PropTypes.node,
onSortPress: PropTypes.func
};
TableHeaderCell.defaultProps = {
className: styles.headerCell,
isSortable: false
};
export default TableHeaderCell;

@ -0,0 +1,70 @@
import React, { useCallback } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, sortDirections } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import styles from './TableHeaderCell.css';
interface TableHeaderCellProps {
className?: string;
name: string;
label?: string | (() => string) | React.ReactNode;
columnLabel?: string | (() => string);
isSortable?: boolean;
isVisible?: boolean;
isModifiable?: boolean;
sortKey?: string;
fixedSortDirection?: SortDirection;
sortDirection?: string;
children?: React.ReactNode;
onSortPress?: (name: string, sortDirection?: SortDirection) => void;
}
function TableHeaderCell({
className = styles.headerCell,
name,
label,
columnLabel,
isSortable = false,
isVisible,
isModifiable,
sortKey,
sortDirection,
fixedSortDirection,
children,
onSortPress,
...otherProps
}: TableHeaderCellProps) {
const isSorting = isSortable && sortKey === name;
const sortIcon =
sortDirection === sortDirections.ASCENDING
? icons.SORT_ASCENDING
: icons.SORT_DESCENDING;
const handlePress = useCallback(() => {
if (fixedSortDirection) {
onSortPress?.(name, fixedSortDirection);
} else {
onSortPress?.(name);
}
}, [name, fixedSortDirection, onSortPress]);
return isSortable ? (
<Link
{...otherProps}
component="th"
className={className}
// label={typeof label === 'function' ? label() : label}
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
onPress={handlePress}
>
{children}
{isSorting && <Icon name={sortIcon} className={styles.sortIcon} />}
</Link>
) : (
<th className={className}>{children}</th>
);
}
export default TableHeaderCell;

@ -1,181 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import SelectInput from 'Components/Form/SelectInput';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './TablePager.css';
class TablePager extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isShowingPageSelect: false
};
}
//
// Listeners
onOpenPageSelectClick = () => {
this.setState({ isShowingPageSelect: true });
};
onPageSelect = ({ value: page }) => {
this.setState({ isShowingPageSelect: false });
this.props.onPageSelect(parseInt(page));
};
onPageSelectBlur = () => {
this.setState({ isShowingPageSelect: false });
};
//
// Render
render() {
const {
page,
totalPages,
totalRecords,
isFetching,
onFirstPagePress,
onPreviousPagePress,
onNextPagePress,
onLastPagePress
} = this.props;
const isShowingPageSelect = this.state.isShowingPageSelect;
const pages = Array.from(new Array(totalPages), (x, i) => {
const pageNumber = i + 1;
return {
key: pageNumber,
value: pageNumber
};
});
if (!page) {
return null;
}
const isFirstPage = page === 1;
const isLastPage = page === totalPages;
return (
<div className={styles.pager}>
<div className={styles.loadingContainer}>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
<div className={styles.controlsContainer}>
<div className={styles.controls}>
<Link
className={classNames(
styles.pageLink,
isFirstPage && styles.disabledPageButton
)}
isDisabled={isFirstPage}
onPress={onFirstPagePress}
>
<Icon name={icons.PAGE_FIRST} />
</Link>
<Link
className={classNames(
styles.pageLink,
isFirstPage && styles.disabledPageButton
)}
isDisabled={isFirstPage}
onPress={onPreviousPagePress}
>
<Icon name={icons.PAGE_PREVIOUS} />
</Link>
<div className={styles.pageNumber}>
{
!isShowingPageSelect &&
<Link
isDisabled={totalPages === 1}
onPress={this.onOpenPageSelectClick}
>
{page} / {totalPages}
</Link>
}
{
isShowingPageSelect &&
<SelectInput
className={styles.pageSelect}
name="pageSelect"
value={page}
values={pages}
autoFocus={true}
onChange={this.onPageSelect}
onBlur={this.onPageSelectBlur}
/>
}
</div>
<Link
className={classNames(
styles.pageLink,
isLastPage && styles.disabledPageButton
)}
isDisabled={isLastPage}
onPress={onNextPagePress}
>
<Icon name={icons.PAGE_NEXT} />
</Link>
<Link
className={classNames(
styles.pageLink,
isLastPage && styles.disabledPageButton
)}
isDisabled={isLastPage}
onPress={onLastPagePress}
>
<Icon name={icons.PAGE_LAST} />
</Link>
</div>
</div>
<div className={styles.recordsContainer}>
<div className={styles.records}>
{translate('TotalRecords', { totalRecords })}
</div>
</div>
</div>
);
}
}
TablePager.propTypes = {
page: PropTypes.number,
totalPages: PropTypes.number,
totalRecords: PropTypes.number,
isFetching: PropTypes.bool,
onFirstPagePress: PropTypes.func.isRequired,
onPreviousPagePress: PropTypes.func.isRequired,
onNextPagePress: PropTypes.func.isRequired,
onLastPagePress: PropTypes.func.isRequired,
onPageSelect: PropTypes.func.isRequired
};
export default TablePager;

@ -0,0 +1,159 @@
import classNames from 'classnames';
import React, { useCallback, useMemo, useState } from 'react';
import SelectInput from 'Components/Form/SelectInput';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { icons } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './TablePager.css';
interface TablePagerProps {
page?: number;
totalPages?: number;
totalRecords?: number;
isFetching?: boolean;
onFirstPagePress: () => void;
onPreviousPagePress: () => void;
onNextPagePress: () => void;
onLastPagePress: () => void;
onPageSelect: (page: number) => void;
}
function TablePager({
page,
totalPages,
totalRecords = 0,
isFetching,
onFirstPagePress,
onPreviousPagePress,
onNextPagePress,
onLastPagePress,
onPageSelect,
}: TablePagerProps) {
const [isShowingPageSelect, setIsShowingPageSelect] = useState(false);
const isFirstPage = page === 1;
const isLastPage = page === totalPages;
const pages = useMemo(() => {
return Array.from(new Array(totalPages), (_x, i) => {
const pageNumber = i + 1;
return {
key: pageNumber,
value: String(pageNumber),
};
});
}, [totalPages]);
const handleOpenPageSelectClick = useCallback(() => {
setIsShowingPageSelect(true);
}, []);
const handlePageSelect = useCallback(
({ value }: InputChanged<number>) => {
setIsShowingPageSelect(false);
onPageSelect(value);
},
[onPageSelect]
);
const handlePageSelectBlur = useCallback(() => {
setIsShowingPageSelect(false);
}, []);
if (!page) {
return null;
}
return (
<div className={styles.pager}>
<div className={styles.loadingContainer}>
{isFetching ? (
<LoadingIndicator className={styles.loading} size={20} />
) : null}
</div>
<div className={styles.controlsContainer}>
<div className={styles.controls}>
<Link
className={classNames(
styles.pageLink,
isFirstPage && styles.disabledPageButton
)}
isDisabled={isFirstPage}
onPress={onFirstPagePress}
>
<Icon name={icons.PAGE_FIRST} />
</Link>
<Link
className={classNames(
styles.pageLink,
isFirstPage && styles.disabledPageButton
)}
isDisabled={isFirstPage}
onPress={onPreviousPagePress}
>
<Icon name={icons.PAGE_PREVIOUS} />
</Link>
<div className={styles.pageNumber}>
{isShowingPageSelect ? null : (
<Link
isDisabled={totalPages === 1}
onPress={handleOpenPageSelectClick}
>
{page} / {totalPages}
</Link>
)}
{isShowingPageSelect ? (
<SelectInput
className={styles.pageSelect}
name="pageSelect"
value={page}
values={pages}
autoFocus={true}
onChange={handlePageSelect}
onBlur={handlePageSelectBlur}
/>
) : null}
</div>
<Link
className={classNames(
styles.pageLink,
isLastPage && styles.disabledPageButton
)}
isDisabled={isLastPage}
onPress={onNextPagePress}
>
<Icon name={icons.PAGE_NEXT} />
</Link>
<Link
className={classNames(
styles.pageLink,
isLastPage && styles.disabledPageButton
)}
isDisabled={isLastPage}
onPress={onLastPagePress}
>
<Icon name={icons.PAGE_LAST} />
</Link>
</div>
</div>
<div className={styles.recordsContainer}>
<div className={styles.records}>
{translate('TotalRecords', { totalRecords })}
</div>
</div>
</div>
);
}
export default TablePager;

@ -1,33 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './TableRow.css';
function TableRow(props) {
const {
className,
children,
overlayContent,
...otherProps
} = props;
return (
<tr
className={className}
{...otherProps}
>
{children}
</tr>
);
}
TableRow.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.node,
overlayContent: PropTypes.bool
};
TableRow.defaultProps = {
className: styles.row
};
export default TableRow;

@ -0,0 +1,23 @@
import React from 'react';
import styles from './TableRow.css';
interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
className?: string;
children?: React.ReactNode;
overlayContent?: boolean;
}
function TableRow({
className = styles.row,
children,
overlayContent,
...otherProps
}: TableRowProps) {
return (
<tr className={className} {...otherProps}>
{children}
</tr>
);
}
export default TableRow;

@ -1,16 +0,0 @@
import React from 'react';
import Link from 'Components/Link/Link';
import TableRow from './TableRow';
import styles from './TableRowButton.css';
function TableRowButton(props) {
return (
<Link
className={styles.row}
component={TableRow}
{...props}
/>
);
}
export default TableRowButton;

@ -0,0 +1,10 @@
import React from 'react';
import Link, { LinkProps } from 'Components/Link/Link';
import TableRow from './TableRow';
import styles from './TableRowButton.css';
function TableRowButton(props: LinkProps) {
return <Link className={styles.row} component={TableRow} {...props} />;
}
export default TableRowButton;

@ -1,47 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import CheckInput from 'Components/Form/CheckInput';
import VirtualTableHeaderCell from './TableHeaderCell';
import styles from './TableSelectAllHeaderCell.css';
function getValue(allSelected, allUnselected) {
if (allSelected) {
return true;
} else if (allUnselected) {
return false;
}
return null;
}
function TableSelectAllHeaderCell(props) {
const {
allSelected,
allUnselected,
onSelectAllChange
} = props;
const value = getValue(allSelected, allUnselected);
return (
<VirtualTableHeaderCell
className={styles.selectAllHeaderCell}
name="selectAll"
>
<CheckInput
className={styles.input}
name="selectAll"
value={value}
onChange={onSelectAllChange}
/>
</VirtualTableHeaderCell>
);
}
TableSelectAllHeaderCell.propTypes = {
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
onSelectAllChange: PropTypes.func.isRequired
};
export default TableSelectAllHeaderCell;

@ -0,0 +1,43 @@
import React, { useMemo } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import { CheckInputChanged } from 'typings/inputs';
import VirtualTableHeaderCell from './TableHeaderCell';
import styles from './TableSelectAllHeaderCell.css';
interface TableSelectAllHeaderCellProps {
allSelected: boolean;
allUnselected: boolean;
onSelectAllChange: (change: CheckInputChanged) => void;
}
function TableSelectAllHeaderCell({
allSelected,
allUnselected,
onSelectAllChange,
}: TableSelectAllHeaderCellProps) {
const value = useMemo(() => {
if (allSelected) {
return true;
} else if (allUnselected) {
return false;
}
return null;
}, [allSelected, allUnselected]);
return (
<VirtualTableHeaderCell
className={styles.selectAllHeaderCell}
name="selectAll"
>
<CheckInput
className={styles.input}
name="selectAll"
value={value}
onChange={onSelectAllChange}
/>
</VirtualTableHeaderCell>
);
}
export default TableSelectAllHeaderCell;

@ -1,202 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Grid, WindowScroller } from 'react-virtualized';
import Measure from 'Components/Measure';
import Scroller from 'Components/Scroller/Scroller';
import { scrollDirections } from 'Helpers/Props';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import styles from './VirtualTable.css';
const ROW_HEIGHT = 38;
function overscanIndicesGetter(options) {
const {
cellCount,
overscanCellsCount,
startIndex,
stopIndex
} = options;
// The default getter takes the scroll direction into account,
// but that can cause issues. Ignore the scroll direction and
// always over return more items.
const overscanStartIndex = startIndex - overscanCellsCount;
const overscanStopIndex = stopIndex + overscanCellsCount;
return {
overscanStartIndex: Math.max(0, overscanStartIndex),
overscanStopIndex: Math.min(cellCount - 1, overscanStopIndex)
};
}
class VirtualTable extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
width: 0,
scrollRestored: false
};
this._grid = null;
}
componentDidUpdate(prevProps, prevState) {
const {
items,
scrollIndex,
scrollTop
} = this.props;
const {
width,
scrollRestored
} = this.state;
if (this._grid && (prevState.width !== width || hasDifferentItemsOrOrder(prevProps.items, items))) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
this._grid.scrollToCell({
rowIndex: scrollIndex,
columnIndex: 0
});
}
}
//
// Control
setGridRef = (ref) => {
this._grid = ref;
};
//
// Listeners
onMeasure = ({ width }) => {
this.setState({
width
});
};
//
// Render
render() {
const {
isSmallScreen,
className,
items,
scroller,
header,
headerHeight,
rowHeight,
rowRenderer,
...otherProps
} = this.props;
const {
width
} = this.state;
const gridStyle = {
boxSizing: undefined,
direction: undefined,
height: undefined,
position: undefined,
willChange: undefined,
overflow: undefined,
width: undefined
};
const containerStyle = {
position: undefined
};
return (
<WindowScroller
scrollElement={isSmallScreen ? undefined : scroller}
>
{({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return null;
}
return (
<Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<Scroller
className={className}
scrollDirection={scrollDirections.HORIZONTAL}
>
{header}
<div ref={registerChild}>
<Grid
{...otherProps}
ref={this.setGridRef}
autoContainerWidth={true}
autoHeight={true}
autoWidth={true}
width={width}
height={height}
headerHeight={height - headerHeight}
rowHeight={rowHeight}
rowCount={items.length}
columnCount={1}
columnWidth={width}
scrollTop={scrollTop}
onScroll={onChildScroll}
overscanRowCount={2}
cellRenderer={rowRenderer}
overscanIndicesGetter={overscanIndicesGetter}
scrollToAlignment={'start'}
isScrollingOptout={true}
className={styles.tableBodyContainer}
style={gridStyle}
containerStyle={containerStyle}
/>
</div>
</Scroller>
</Measure>
);
}
}
</WindowScroller>
);
}
}
VirtualTable.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
className: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
scrollIndex: PropTypes.number,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired,
rowHeight: PropTypes.number.isRequired
};
VirtualTable.defaultProps = {
className: styles.tableContainer,
headerHeight: 38,
rowHeight: ROW_HEIGHT
};
export default VirtualTable;

@ -0,0 +1,167 @@
import React, { ReactNode, useEffect, useRef } from 'react';
import { Grid, GridCellProps, WindowScroller } from 'react-virtualized';
import ModelBase from 'App/ModelBase';
import Scroller from 'Components/Scroller/Scroller';
import useMeasure from 'Helpers/Hooks/useMeasure';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { scrollDirections } from 'Helpers/Props';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import styles from './VirtualTable.css';
const ROW_HEIGHT = 38;
function overscanIndicesGetter(options: {
cellCount: number;
overscanCellsCount: number;
startIndex: number;
stopIndex: number;
}) {
const { cellCount, overscanCellsCount, startIndex, stopIndex } = options;
// The default getter takes the scroll direction into account,
// but that can cause issues. Ignore the scroll direction and
// always over return more items.
const overscanStartIndex = startIndex - overscanCellsCount;
const overscanStopIndex = stopIndex + overscanCellsCount;
return {
overscanStartIndex: Math.max(0, overscanStartIndex),
overscanStopIndex: Math.min(cellCount - 1, overscanStopIndex),
};
}
interface VirtualTableProps<T extends ModelBase> {
isSmallScreen: boolean;
className?: string;
items: T[];
scrollIndex?: number;
scrollTop?: number;
scroller: Element;
header: React.ReactNode;
headerHeight?: number;
rowRenderer: (rowProps: GridCellProps) => ReactNode;
rowHeight?: number;
}
function VirtualTable<T extends ModelBase>({
isSmallScreen,
className = styles.tableContainer,
items,
scroller,
scrollIndex,
scrollTop,
header,
headerHeight = 38,
rowHeight = ROW_HEIGHT,
rowRenderer,
...otherProps
}: VirtualTableProps<T>) {
const [measureRef, bounds] = useMeasure();
const gridRef = useRef<Grid>(null);
const scrollRestored = useRef(false);
const previousScrollIndex = usePrevious(scrollIndex);
const previousItems = usePrevious(items);
const width = bounds.width;
const gridStyle = {
boxSizing: undefined,
direction: undefined,
height: undefined,
position: undefined,
willChange: undefined,
overflow: undefined,
width: undefined,
};
const containerStyle = {
position: undefined,
};
useEffect(() => {
if (gridRef.current && width > 0) {
gridRef.current.recomputeGridSize();
}
}, [width]);
useEffect(() => {
if (
gridRef.current &&
previousItems &&
hasDifferentItemsOrOrder(previousItems, items)
) {
gridRef.current.recomputeGridSize();
}
}, [items, previousItems]);
useEffect(() => {
if (gridRef.current && scrollTop && !scrollRestored.current) {
gridRef.current.scrollToPosition({ scrollLeft: 0, scrollTop });
scrollRestored.current = true;
}
}, [scrollTop]);
useEffect(() => {
if (
gridRef.current &&
scrollIndex != null &&
scrollIndex !== previousScrollIndex
) {
gridRef.current.scrollToCell({
rowIndex: scrollIndex,
columnIndex: 0,
});
}
}, [scrollIndex, previousScrollIndex]);
return (
<WindowScroller scrollElement={isSmallScreen ? undefined : scroller}>
{({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return null;
}
return (
<div ref={measureRef}>
<Scroller
className={className}
scrollDirection={scrollDirections.HORIZONTAL}
>
{header}
{/* @ts-expect-error - ref type is incompatible */}
<div ref={registerChild}>
<Grid
{...otherProps}
ref={gridRef}
autoContainerWidth={true}
autoHeight={true}
autoWidth={true}
width={width}
height={height}
headerHeight={height - headerHeight}
rowHeight={rowHeight}
rowCount={items.length}
columnCount={1}
columnWidth={width}
scrollTop={scrollTop}
overscanRowCount={2}
cellRenderer={rowRenderer}
overscanIndicesGetter={overscanIndicesGetter}
scrollToAlignment="start"
isScrollingOptout={true}
className={styles.tableBodyContainer}
style={gridStyle}
containerStyle={containerStyle}
onScroll={onChildScroll}
/>
</div>
</Scroller>
</div>
);
}}
</WindowScroller>
);
}
export default VirtualTable;

@ -1,17 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './VirtualTableHeader.css';
function VirtualTableHeader({ children }) {
return (
<div className={styles.header}>
{children}
</div>
);
}
VirtualTableHeader.propTypes = {
children: PropTypes.node
};
export default VirtualTableHeader;

@ -0,0 +1,12 @@
import React from 'react';
import styles from './VirtualTableHeader.css';
interface VirtualTableHeaderProps {
children?: React.ReactNode;
}
function VirtualTableHeader({ children }: VirtualTableHeaderProps) {
return <div className={styles.header}>{children}</div>;
}
export default VirtualTableHeader;

@ -1,109 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, sortDirections } from 'Helpers/Props';
import styles from './VirtualTableHeaderCell.css';
export function headerRenderer(headerProps) {
const {
columnData = {},
dataKey,
label
} = headerProps;
return (
// eslint-disable-next-line no-use-before-define
<VirtualTableHeaderCell
name={dataKey}
{...columnData}
>
{label}
</VirtualTableHeaderCell>
);
}
class VirtualTableHeaderCell extends Component {
//
// Listeners
onPress = () => {
const {
name,
fixedSortDirection
} = this.props;
if (fixedSortDirection) {
this.props.onSortPress(name, fixedSortDirection);
} else {
this.props.onSortPress(name);
}
};
//
// Render
render() {
const {
className,
name,
isSortable,
sortKey,
sortDirection,
fixedSortDirection,
children,
onSortPress,
...otherProps
} = this.props;
const isSorting = isSortable && sortKey === name;
const sortIcon = sortDirection === sortDirections.ASCENDING ?
icons.SORT_ASCENDING :
icons.SORT_DESCENDING;
return (
isSortable ?
<Link
component="div"
className={className}
onPress={this.onPress}
{...otherProps}
>
{children}
{
isSorting &&
<Icon
name={sortIcon}
className={styles.sortIcon}
/>
}
</Link> :
<div className={className}>
{children}
</div>
);
}
}
VirtualTableHeaderCell.propTypes = {
className: PropTypes.string,
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
isSortable: PropTypes.bool,
sortKey: PropTypes.string,
fixedSortDirection: PropTypes.string,
sortDirection: PropTypes.string,
children: PropTypes.node,
onSortPress: PropTypes.func
};
VirtualTableHeaderCell.defaultProps = {
className: styles.headerCell,
isSortable: false
};
export default VirtualTableHeaderCell;

@ -0,0 +1,60 @@
import React, { useCallback } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, sortDirections } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import styles from './VirtualTableHeaderCell.css';
interface VirtualTableHeaderCellProps {
className?: string;
name: string;
isSortable?: boolean;
sortKey?: string;
fixedSortDirection?: SortDirection;
sortDirection?: string;
children?: React.ReactNode;
onSortPress?: (name: string, sortDirection?: SortDirection) => void;
}
function VirtualTableHeaderCell({
className = styles.headerCell,
name,
isSortable = false,
sortKey,
sortDirection,
fixedSortDirection,
children,
onSortPress,
...otherProps
}: VirtualTableHeaderCellProps) {
const isSorting = isSortable && sortKey === name;
const sortIcon =
sortDirection === sortDirections.ASCENDING
? icons.SORT_ASCENDING
: icons.SORT_DESCENDING;
const handlePress = useCallback(() => {
if (fixedSortDirection) {
onSortPress?.(name, fixedSortDirection);
} else {
onSortPress?.(name);
}
}, [name, fixedSortDirection, onSortPress]);
return isSortable ? (
<Link
component="div"
className={className}
onPress={handlePress}
{...otherProps}
>
{children}
{isSorting ? <Icon name={sortIcon} className={styles.sortIcon} /> : null}
</Link>
) : (
<div className={className}>{children}</div>
);
}
export default VirtualTableHeaderCell;

@ -1,34 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './VirtualTableRow.css';
function VirtualTableRow(props) {
const {
className,
children,
style,
...otherProps
} = props;
return (
<div
className={className}
style={style}
{...otherProps}
>
{children}
</div>
);
}
VirtualTableRow.propTypes = {
className: PropTypes.string.isRequired,
style: PropTypes.object.isRequired,
children: PropTypes.node
};
VirtualTableRow.defaultProps = {
className: styles.row
};
export default VirtualTableRow;

@ -0,0 +1,23 @@
import React from 'react';
import styles from './VirtualTableRow.css';
interface VirtualTableRowProps extends React.HTMLAttributes<HTMLDivElement> {
className: string;
style: object;
children?: React.ReactNode;
}
function VirtualTableRow({
className = styles.row,
children,
style,
...otherProps
}: VirtualTableRowProps) {
return (
<div className={className} style={style} {...otherProps}>
{children}
</div>
);
}
export default VirtualTableRow;

@ -1,16 +0,0 @@
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;

@ -0,0 +1,10 @@
import React from 'react';
import Link, { LinkProps } from 'Components/Link/Link';
import VirtualTableRow from './VirtualTableRow';
import styles from './VirtualTableRowButton.css';
function VirtualTableRowButton(props: LinkProps) {
return <Link className={styles.row} component={VirtualTableRow} {...props} />;
}
export default VirtualTableRowButton;

@ -1,47 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import CheckInput from 'Components/Form/CheckInput';
import VirtualTableHeaderCell from './VirtualTableHeaderCell';
import styles from './VirtualTableSelectAllHeaderCell.css';
function getValue(allSelected, allUnselected) {
if (allSelected) {
return true;
} else if (allUnselected) {
return false;
}
return null;
}
function VirtualTableSelectAllHeaderCell(props) {
const {
allSelected,
allUnselected,
onSelectAllChange
} = props;
const value = getValue(allSelected, allUnselected);
return (
<VirtualTableHeaderCell
className={styles.selectAllHeaderCell}
name="selectAll"
>
<CheckInput
className={styles.input}
name="selectAll"
value={value}
onChange={onSelectAllChange}
/>
</VirtualTableHeaderCell>
);
}
VirtualTableSelectAllHeaderCell.propTypes = {
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
onSelectAllChange: PropTypes.func.isRequired
};
export default VirtualTableSelectAllHeaderCell;

@ -0,0 +1,43 @@
import React, { useMemo } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import { CheckInputChanged } from 'typings/inputs';
import VirtualTableHeaderCell from './VirtualTableHeaderCell';
import styles from './VirtualTableSelectAllHeaderCell.css';
interface VirtualTableSelectAllHeaderCellProps {
allSelected: boolean;
allUnselected: boolean;
onSelectAllChange: (change: CheckInputChanged) => void;
}
function VirtualTableSelectAllHeaderCell({
allSelected,
allUnselected,
onSelectAllChange,
}: VirtualTableSelectAllHeaderCellProps) {
const value = useMemo(() => {
if (allSelected) {
return true;
} else if (allUnselected) {
return false;
}
return null;
}, [allSelected, allUnselected]);
return (
<VirtualTableHeaderCell
className={styles.selectAllHeaderCell}
name="selectAll"
>
<CheckInput
className={styles.input}
name="selectAll"
value={value}
onChange={onSelectAllChange}
/>
</VirtualTableHeaderCell>
);
}
export default VirtualTableSelectAllHeaderCell;

@ -5,11 +5,11 @@ import areAllSelected from 'Utilities/Table/areAllSelected';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
export type SelectedState = Record<number, boolean>;
export type SelectedState = Record<number | string, boolean>;
export interface SelectState {
selectedState: SelectedState;
lastToggled: number | null;
lastToggled: number | string | null;
allSelected: boolean;
allUnselected: boolean;
}
@ -20,14 +20,14 @@ export type SelectAction =
| { type: 'unselectAll'; items: ModelBase[] }
| {
type: 'toggleSelected';
id: number;
isSelected: boolean;
id: number | string;
isSelected: boolean | null;
shiftKey: boolean;
items: ModelBase[];
}
| {
type: 'removeItem';
id: number;
id: number | string;
}
| {
type: 'updateItems';

@ -129,7 +129,7 @@ function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
);
const onSortPress = useCallback(
(newSortKey: string, newSortDirection: SortDirection) => {
(newSortKey: string, newSortDirection?: SortDirection) => {
dispatch(
setEpisodesSort({
sortKey: newSortKey,

@ -10,6 +10,7 @@ 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 Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons, kinds, sizes } from 'Helpers/Props';
@ -20,29 +21,34 @@ import FavoriteFolderRow from './FavoriteFolderRow';
import RecentFolderRow from './RecentFolderRow';
import styles from './InteractiveImportSelectFolderModalContent.css';
const favoriteFoldersColumns = [
const favoriteFoldersColumns: Column[] = [
{
name: 'folder',
label: () => translate('Folder'),
isVisible: true,
},
{
name: 'actions',
label: '',
isVisible: true,
},
];
const recentFoldersColumns = [
const recentFoldersColumns: Column[] = [
{
name: 'folder',
label: () => translate('Folder'),
isVisible: true,
},
{
name: 'lastUsed',
label: () => translate('LastUsed'),
isVisible: true,
},
{
name: 'actions',
label: '',
isVisible: true,
},
];

@ -58,7 +58,7 @@ import {
} from 'Store/Actions/interactiveImportActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SortCallback } from 'typings/callbacks';
import { SelectStateInputProps } from 'typings/props';
import { CheckInputChanged } from 'typings/inputs';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
@ -431,7 +431,7 @@ function InteractiveImportModalContent(
}, [previousIsDeleting, isDeleting, deleteError, onModalClose]);
const onSelectAllChange = useCallback(
({ value }: SelectStateInputProps) => {
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]
@ -449,8 +449,8 @@ function InteractiveImportModalContent(
setWithoutEpisodeFileIdRowsSelected(
hasEpisodeFileId || !value
? without(withoutEpisodeFileIdRowsSelected, id)
: [...withoutEpisodeFileIdRowsSelected, id]
? without(withoutEpisodeFileIdRowsSelected, id as number)
: [...withoutEpisodeFileIdRowsSelected, id as number]
);
},
[

@ -169,8 +169,8 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
onValidRowChange,
]);
const onSelectedChangeWrapper = useCallback(
(result: SelectedChangeProps) => {
const handleSelectedChange = useCallback(
(result: SelectStateInputProps) => {
onSelectedChange({
...result,
hasEpisodeFileId: !!episodeFileId,
@ -398,7 +398,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChangeWrapper}
onSelectedChange={handleSelectedChange}
/>
<TableRowCell className={styles.relativePath} title={relativePath}>

@ -147,7 +147,7 @@ function InteractiveSearch({ type, searchPayload }: InteractiveSearchProps) {
);
const handleSortPress = useCallback(
(sortKey: string, sortDirection: SortDirection) => {
(sortKey: string, sortDirection?: SortDirection) => {
dispatch(setReleasesSort({ sortKey, sortDirection }));
},
[dispatch]

@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props';
@ -10,7 +11,7 @@ import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector
import translate from 'Utilities/String/translate';
import RootFolderRow from './RootFolderRow';
const rootFolderColumns = [
const rootFolderColumns: Column[] = [
{
name: 'path',
label: () => translate('Path'),
@ -28,6 +29,7 @@ const rootFolderColumns = [
},
{
name: 'actions',
label: '',
isVisible: true,
},
];

@ -8,11 +8,11 @@ import HeartRating from 'Components/HeartRating';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import SeriesTagList from 'Components/SeriesTagList';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import Column from 'Components/Table/Column';
import SeriesTagList from 'Components/SeriesTagList';
import { icons } from 'Helpers/Props';
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModal from 'Series/Edit/EditSeriesModal';

@ -21,7 +21,7 @@ import {
setManageCustomFormatsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import { CheckInputChanged } from 'typings/inputs';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@ -135,7 +135,7 @@ function ManageCustomFormatsModalContent(
);
const onSelectAllChange = useCallback(
({ value }: SelectStateInputProps) => {
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]

@ -21,7 +21,7 @@ import {
setManageDownloadClientsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import { CheckInputChanged } from 'typings/inputs';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@ -187,7 +187,7 @@ function ManageDownloadClientsModalContent(
);
const onSelectAllChange = useCallback(
({ value }: SelectStateInputProps) => {
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]

@ -19,6 +19,7 @@ import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import usePrevious from 'Helpers/Hooks/usePrevious';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { icons, kinds } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import {
bulkDeleteImportListExclusions,
clearImportListExclusions,
@ -150,8 +151,8 @@ function ImportListExclusions() {
});
const handleSortPress = useCallback(
(sortKey: { sortKey: string }) => {
dispatch(setImportListExclusionSort({ sortKey }));
(sortKey: string, sortDirection?: SortDirection) => {
dispatch(setImportListExclusionSort({ sortKey, sortDirection }));
},
[dispatch]
);

@ -19,7 +19,7 @@ import {
bulkEditImportLists,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import { CheckInputChanged } from 'typings/inputs';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@ -168,7 +168,7 @@ function ManageImportListsModalContent(
);
const onSelectAllChange = useCallback(
({ value }: SelectStateInputProps) => {
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]

@ -21,7 +21,7 @@ import {
setManageIndexersSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import { CheckInputChanged } from 'typings/inputs';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
@ -185,7 +185,7 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
);
const onSelectAllChange = useCallback(
({ value }: SelectStateInputProps) => {
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]

@ -54,7 +54,7 @@ function LogsTableRow({
}, []);
return (
<TableRowButton overlayContent={true} onPress={handlePress}>
<TableRowButton onPress={handlePress}>
{columns.map((column) => {
const { name, isVisible } = column;

@ -3,13 +3,14 @@ import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { fetchCommands } from 'Store/Actions/commandActions';
import translate from 'Utilities/String/translate';
import QueuedTaskRow from './QueuedTaskRow';
const columns = [
const columns: Column[] = [
{
name: 'trigger',
label: '',
@ -42,6 +43,7 @@ const columns = [
},
{
name: 'actions',
label: '',
isVisible: true,
},
];

@ -2,8 +2,8 @@ import ModelBase from 'App/ModelBase';
function getToggledRange<T extends ModelBase>(
items: T[],
id: number,
lastToggled: number
id: number | string,
lastToggled: number | string
) {
const lastToggledIndex = items.findIndex((item) => item.id === lastToggled);
const changedIndex = items.findIndex((item) => item.id === id);

@ -6,25 +6,26 @@ import getToggledRange from './getToggledRange';
function toggleSelected<T extends ModelBase>(
selectState: SelectState,
items: T[],
id: number,
selected: boolean,
id: number | string,
selected: boolean | null,
shiftKey: boolean
) {
const lastToggled = selectState.lastToggled;
const nextSelectedState = {
...selectState.selectedState,
[id]: selected,
};
if (selected == null) {
delete nextSelectedState[id];
}
} else {
nextSelectedState[id] = selected;
if (shiftKey && lastToggled) {
const { lower, upper } = getToggledRange(items, id, lastToggled);
if (shiftKey && lastToggled) {
const { lower, upper } = getToggledRange(items, id, lastToggled);
for (let i = lower; i < upper; i++) {
nextSelectedState[items[i].id] = selected;
for (let i = lower; i < upper; i++) {
nextSelectedState[items[i].id] = selected;
}
}
}

@ -2,5 +2,5 @@ import { SortDirection } from 'Helpers/Props/sortDirections';
export type SortCallback = (
sortKey: string,
sortDirection: SortDirection
sortDirection?: SortDirection
) => void;

@ -1,5 +1,5 @@
export interface SelectStateInputProps {
id: number;
value: boolean;
id: number | string;
value: boolean | null;
shiftKey: boolean;
}

@ -102,6 +102,7 @@
"@types/react-router-dom": "5.3.3",
"@types/react-slider": "1.3.6",
"@types/react-text-truncate": "0.19.0",
"@types/react-virtualized": "9.22.0",
"@types/react-window": "1.8.8",
"@types/redux-actions": "2.6.5",
"@types/webpack-livereload-plugin": "2.3.6",

@ -1479,6 +1479,14 @@
dependencies:
"@types/react" "*"
"@types/react-virtualized@9.22.0":
version "9.22.0"
resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.22.0.tgz#2ff9b3692fa04a429df24ffc7d181d9f33b3831d"
integrity sha512-JL/YCCFZ123za//cj10Apk54F0UGFMrjOE0QHTuXt1KBMFrzLOGv9/x6Uc/pZ0Gaf4o6w61Fostvlw0DwuPXig==
dependencies:
"@types/prop-types" "*"
"@types/react" "*"
"@types/react-window@1.8.8":
version "1.8.8"
resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3"

Loading…
Cancel
Save