diff --git a/frontend/src/App/SelectContext.tsx b/frontend/src/App/SelectContext.tsx
index 66be388ce..eca22c6c7 100644
--- a/frontend/src/App/SelectContext.tsx
+++ b/frontend/src/App/SelectContext.tsx
@@ -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';
diff --git a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx
index 2f62b97ce..e9e4c4aa5 100644
--- a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx
+++ b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx
@@ -129,7 +129,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
}, [setIsDeleteArtistModalOpen]);
const onSelectedChange = useCallback(
- ({ id, value, shiftKey }: SelectStateInputProps) => {
+ ({ id, value, shiftKey = false }: SelectStateInputProps) => {
selectDispatch({
type: 'toggleSelected',
id,
diff --git a/frontend/src/Artist/Index/Table/ArtistIndexTable.tsx b/frontend/src/Artist/Index/Table/ArtistIndexTable.tsx
index c3c8044ce..9b3038a9b 100644
--- a/frontend/src/Artist/Index/Table/ArtistIndexTable.tsx
+++ b/frontend/src/Artist/Index/Table/ArtistIndexTable.tsx
@@ -10,7 +10,6 @@ import ArtistIndexTableHeader from 'Artist/Index/Table/ArtistIndexTableHeader';
import Scroller from 'Components/Scroller/Scroller';
import Column from 'Components/Table/Column';
import useMeasure from 'Helpers/Hooks/useMeasure';
-import ScrollDirection from 'Helpers/Props/ScrollDirection';
import SortDirection from 'Helpers/Props/SortDirection';
import dimensions from 'Styles/Variables/dimensions';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
@@ -176,10 +175,7 @@ function ArtistIndexTable(props: ArtistIndexTableProps) {
return (
-
+
=
+ ComponentPropsWithoutRef & {
+ component?: C;
+ to?: string;
+ target?: string;
+ isDisabled?: LinkProps['disabled'];
+ noRouter?: boolean;
+ onPress?(event: SyntheticEvent): void;
+ };
-export interface LinkProps extends React.HTMLProps {
- className?: string;
- component?:
- | string
- | FunctionComponent
- | ComponentClass;
- to?: string;
- target?: string;
- isDisabled?: boolean;
- noRouter?: boolean;
- onPress?(event: SyntheticEvent): void;
-}
-function Link(props: LinkProps) {
- const {
- className,
- component = 'button',
- to,
- target,
- type,
- isDisabled,
- noRouter = false,
- onPress,
- ...otherProps
- } = props;
+export default function Link({
+ className,
+ component,
+ to,
+ target,
+ type,
+ isDisabled,
+ noRouter,
+ onPress,
+ ...otherProps
+}: LinkProps) {
+ const Component = component || 'button';
const onClick = useCallback(
(event: SyntheticEvent) => {
- if (!isDisabled && onPress) {
- onPress(event);
+ if (isDisabled) {
+ return;
}
+
+ onPress?.(event);
},
[isDisabled, onPress]
);
- const linkProps: React.HTMLProps & ReactRouterLinkProps = {
- target,
- };
- let el = component;
-
- if (to) {
- if (/\w+?:\/\//.test(to)) {
- el = 'a';
- linkProps.href = to;
- linkProps.target = target || '_blank';
- linkProps.rel = 'noreferrer';
- } else if (noRouter) {
- el = 'a';
- linkProps.href = to;
- linkProps.target = target || '_self';
- } else {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- el = RouterLink;
- linkProps.to = `${window.Lidarr.urlBase}/${to.replace(/^\//, '')}`;
- linkProps.target = target;
- }
- }
-
- if (el === 'button' || el === 'input') {
- linkProps.type = type || 'button';
- linkProps.disabled = isDisabled;
- }
-
- linkProps.className = classNames(
+ const linkClass = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
- const elementProps = {
- ...otherProps,
- type,
- ...linkProps,
- };
+ if (to) {
+ const toLink = /\w+?:\/\//.test(to);
- elementProps.onClick = onClick;
+ if (toLink || noRouter) {
+ return (
+
+ );
+ }
- return React.createElement(el, elementProps);
+ return (
+
+ );
+ }
+
+ return (
+
+ );
}
-
-export default Link;
diff --git a/frontend/src/Components/Page/PageContentBody.tsx b/frontend/src/Components/Page/PageContentBody.tsx
index ce9b0e7e4..2f2850c62 100644
--- a/frontend/src/Components/Page/PageContentBody.tsx
+++ b/frontend/src/Components/Page/PageContentBody.tsx
@@ -1,6 +1,5 @@
import React, { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react';
import Scroller, { OnScroll } from 'Components/Scroller/Scroller';
-import ScrollDirection from 'Helpers/Props/ScrollDirection';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
@@ -36,7 +35,7 @@ const PageContentBody = forwardRef(
ref={ref}
{...otherProps}
className={className}
- scrollDirection={ScrollDirection.Vertical}
+ scrollDirection={'vertical'}
onScroll={onScrollWrapper}
>
{children}
diff --git a/frontend/src/Components/Scroller/Scroller.tsx b/frontend/src/Components/Scroller/Scroller.tsx
index 37b16eebd..b2780551a 100644
--- a/frontend/src/Components/Scroller/Scroller.tsx
+++ b/frontend/src/Components/Scroller/Scroller.tsx
@@ -1,14 +1,15 @@
import classNames from 'classnames';
import { throttle } from 'lodash';
import React, {
+ ComponentProps,
ForwardedRef,
forwardRef,
- MutableRefObject,
ReactNode,
useEffect,
+ useImperativeHandle,
useRef,
} from 'react';
-import ScrollDirection from 'Helpers/Props/ScrollDirection';
+import { ScrollDirection } from 'Helpers/Props/scrollDirections';
import styles from './Scroller.css';
export interface OnScroll {
@@ -24,6 +25,7 @@ interface ScrollerProps {
scrollTop?: number;
initialScrollTop?: number;
children?: ReactNode;
+ style?: ComponentProps<'div'>['style'];
onScroll?: (payload: OnScroll) => void;
}
@@ -33,7 +35,7 @@ const Scroller = forwardRef(
className,
autoFocus = false,
autoScroll = true,
- scrollDirection = ScrollDirection.Vertical,
+ scrollDirection = 'vertical',
children,
scrollTop,
initialScrollTop,
@@ -41,13 +43,14 @@ const Scroller = forwardRef(
...otherProps
} = props;
- const internalRef = useRef();
- const currentRef = (ref as MutableRefObject) ?? internalRef;
+ const internalRef = useRef(null);
+
+ useImperativeHandle(ref, () => internalRef.current!, []);
useEffect(
() => {
if (initialScrollTop != null) {
- currentRef.current.scrollTop = initialScrollTop;
+ internalRef.current!.scrollTop = initialScrollTop;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -56,16 +59,16 @@ const Scroller = forwardRef(
useEffect(() => {
if (scrollTop != null) {
- currentRef.current.scrollTop = scrollTop;
+ internalRef.current!.scrollTop = scrollTop;
}
- if (autoFocus && scrollDirection !== ScrollDirection.None) {
- currentRef.current.focus({ preventScroll: true });
+ if (autoFocus && scrollDirection !== 'none') {
+ internalRef.current!.focus({ preventScroll: true });
}
- }, [autoFocus, currentRef, scrollDirection, scrollTop]);
+ }, [autoFocus, scrollDirection, scrollTop]);
useEffect(() => {
- const div = currentRef.current;
+ const div = internalRef.current!;
const handleScroll = throttle(() => {
const scrollLeft = div.scrollLeft;
@@ -74,17 +77,17 @@ const Scroller = forwardRef(
onScroll?.({ scrollLeft, scrollTop });
}, 10);
- div.addEventListener('scroll', handleScroll);
+ div?.addEventListener('scroll', handleScroll);
return () => {
- div.removeEventListener('scroll', handleScroll);
+ div?.removeEventListener('scroll', handleScroll);
};
- }, [currentRef, onScroll]);
+ }, [onScroll]);
return (
- );
- }
-
- return (
-
- {getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
-
- );
- }
-}
-
-RelativeDateCell.propTypes = {
- className: PropTypes.string.isRequired,
- date: PropTypes.string,
- includeSeconds: PropTypes.bool.isRequired,
- showRelativeDates: PropTypes.bool.isRequired,
- shortDateFormat: PropTypes.string.isRequired,
- longDateFormat: PropTypes.string.isRequired,
- timeFormat: PropTypes.string.isRequired,
- component: PropTypes.elementType,
- dispatch: PropTypes.func
-};
-
-RelativeDateCell.defaultProps = {
- className: styles.cell,
- includeSeconds: false,
- component: TableRowCell
-};
-
-export default RelativeDateCell;
diff --git a/frontend/src/Components/Table/Cells/RelativeDateCell.tsx b/frontend/src/Components/Table/Cells/RelativeDateCell.tsx
new file mode 100644
index 000000000..c694c17e2
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/RelativeDateCell.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
+import formatDateTime from 'Utilities/Date/formatDateTime';
+import getRelativeDate from 'Utilities/Date/getRelativeDate';
+import TableRowCell from './TableRowCell';
+import styles from './RelativeDateCell.css';
+
+interface RelativeDateCellProps {
+ className?: string;
+ date?: string;
+ includeSeconds?: boolean;
+ component?: React.ElementType;
+}
+
+function RelativeDateCell(props: RelativeDateCellProps) {
+ const {
+ className = styles.cell,
+ date,
+ includeSeconds = false,
+ component: Component = TableRowCell,
+ ...otherProps
+ } = props;
+
+ const { showRelativeDates, shortDateFormat, longDateFormat, timeFormat } =
+ useSelector(createUISettingsSelector());
+
+ if (!date) {
+ return ;
+ }
+
+ return (
+
+ {getRelativeDate(date, shortDateFormat, showRelativeDates, {
+ timeFormat,
+ includeSeconds,
+ timeForToday: true,
+ })}
+
+ );
+}
+
+export default RelativeDateCell;
diff --git a/frontend/src/Components/Table/Cells/TableRowCell.js b/frontend/src/Components/Table/Cells/TableRowCell.js
deleted file mode 100644
index f66bbf3aa..000000000
--- a/frontend/src/Components/Table/Cells/TableRowCell.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import styles from './TableRowCell.css';
-
-class TableRowCell extends Component {
-
- //
- // Render
-
- render() {
- const {
- className,
- children,
- ...otherProps
- } = this.props;
-
- return (
-
- {children}
- |
- );
- }
-}
-
-TableRowCell.propTypes = {
- className: PropTypes.string.isRequired,
- children: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
-};
-
-TableRowCell.defaultProps = {
- className: styles.cell
-};
-
-export default TableRowCell;
diff --git a/frontend/src/Components/Table/Cells/TableRowCell.tsx b/frontend/src/Components/Table/Cells/TableRowCell.tsx
new file mode 100644
index 000000000..aed693222
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/TableRowCell.tsx
@@ -0,0 +1,11 @@
+import React, { ComponentPropsWithoutRef } from 'react';
+import styles from './TableRowCell.css';
+
+export type TableRowCellProps = ComponentPropsWithoutRef<'td'>;
+
+export default function TableRowCell({
+ className = styles.cell,
+ ...tdProps
+}: TableRowCellProps) {
+ return | ;
+}
diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.js b/frontend/src/Components/Table/Cells/TableRowCellButton.js
deleted file mode 100644
index ff50d3bc9..000000000
--- a/frontend/src/Components/Table/Cells/TableRowCellButton.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import Link from 'Components/Link/Link';
-import TableRowCell from './TableRowCell';
-import styles from './TableRowCellButton.css';
-
-function TableRowCellButton({ className, ...otherProps }) {
- return (
-
- );
-}
-
-TableRowCellButton.propTypes = {
- className: PropTypes.string.isRequired
-};
-
-TableRowCellButton.defaultProps = {
- className: styles.cell
-};
-
-export default TableRowCellButton;
diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.tsx b/frontend/src/Components/Table/Cells/TableRowCellButton.tsx
new file mode 100644
index 000000000..c80a3d626
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/TableRowCellButton.tsx
@@ -0,0 +1,19 @@
+import React, { ReactNode } from 'react';
+import Link, { LinkProps } from 'Components/Link/Link';
+import TableRowCell from './TableRowCell';
+import styles from './TableRowCellButton.css';
+
+interface TableRowCellButtonProps extends LinkProps {
+ className?: string;
+ children: ReactNode;
+}
+
+function TableRowCellButton(props: TableRowCellButtonProps) {
+ const { className = styles.cell, ...otherProps } = props;
+
+ return (
+
+ );
+}
+
+export default TableRowCellButton;
diff --git a/frontend/src/Components/Table/Cells/TableSelectCell.js b/frontend/src/Components/Table/Cells/TableSelectCell.js
deleted file mode 100644
index a2a297f2e..000000000
--- a/frontend/src/Components/Table/Cells/TableSelectCell.js
+++ /dev/null
@@ -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 (
-
-
-
- );
- }
-}
-
-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;
diff --git a/frontend/src/Components/Table/Cells/TableSelectCell.tsx b/frontend/src/Components/Table/Cells/TableSelectCell.tsx
new file mode 100644
index 000000000..1f9e4b200
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/TableSelectCell.tsx
@@ -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 {
+ 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 (
+
+
+
+ );
+}
+
+export default TableSelectCell;
diff --git a/frontend/src/Components/Table/Cells/VirtualTableRowCell.js b/frontend/src/Components/Table/Cells/VirtualTableRowCell.js
deleted file mode 100644
index 42999216f..000000000
--- a/frontend/src/Components/Table/Cells/VirtualTableRowCell.js
+++ /dev/null
@@ -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 (
-
- {children}
-
- );
-}
-
-VirtualTableRowCell.propTypes = {
- className: PropTypes.string.isRequired,
- children: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
-};
-
-VirtualTableRowCell.defaultProps = {
- className: styles.cell
-};
-
-export default VirtualTableRowCell;
diff --git a/frontend/src/Components/Table/Cells/VirtualTableRowCell.tsx b/frontend/src/Components/Table/Cells/VirtualTableRowCell.tsx
new file mode 100644
index 000000000..6a3307c2a
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/VirtualTableRowCell.tsx
@@ -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 {children}
;
+}
+
+export default VirtualTableRowCell;
diff --git a/frontend/src/Components/Table/Cells/VirtualTableSelectCell.js b/frontend/src/Components/Table/Cells/VirtualTableSelectCell.js
deleted file mode 100644
index 02ed71fc6..000000000
--- a/frontend/src/Components/Table/Cells/VirtualTableSelectCell.js
+++ /dev/null
@@ -1,86 +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
-
- );
-}
-
-class VirtualTableSelectCell extends Component {
-
- //
- // Listeners
-
- onChange = ({ value, shiftKey }) => {
- const {
- id,
- onSelectedChange
- } = this.props;
-
- onSelectedChange({ id, value, shiftKey });
- };
-
- //
- // Render
-
- render() {
- const {
- className,
- inputClassName,
- id,
- isSelected,
- isDisabled,
- ...otherProps
- } = this.props;
-
- return (
-
-
-
- );
- }
-}
-
-VirtualTableSelectCell.propTypes = {
- className: PropTypes.string.isRequired,
- 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 = {
- className: styles.cell,
- inputClassName: styles.input,
- isSelected: false
-};
-
-export default VirtualTableSelectCell;
diff --git a/frontend/src/Components/Table/Cells/VirtualTableSelectCell.tsx b/frontend/src/Components/Table/Cells/VirtualTableSelectCell.tsx
new file mode 100644
index 000000000..924ed08ad
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/VirtualTableSelectCell.tsx
@@ -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 | string;
+ 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 (
+
+
+
+ );
+}
+
+export default VirtualTableSelectCell;
diff --git a/frontend/src/Components/Table/Column.ts b/frontend/src/Components/Table/Column.ts
index 31a696df7..22d22e963 100644
--- a/frontend/src/Components/Table/Column.ts
+++ b/frontend/src/Components/Table/Column.ts
@@ -1,12 +1,16 @@
import React from 'react';
+import { SortDirection } from 'Helpers/Props/sortDirections';
type PropertyFunction = () => T;
+// TODO: Convert to generic so `name` can be a type
interface Column {
name: string;
label: string | PropertyFunction | React.ReactNode;
+ className?: string;
columnLabel?: string;
isSortable?: boolean;
+ fixedSortDirection?: SortDirection;
isVisible: boolean;
isModifiable?: boolean;
}
diff --git a/frontend/src/Components/Table/Table.js b/frontend/src/Components/Table/Table.js
deleted file mode 100644
index 8afbf9ea0..000000000
--- a/frontend/src/Components/Table/Table.js
+++ /dev/null
@@ -1,143 +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 (
-
-
-
- {
- selectAll ?
- :
- null
- }
-
- {
- columns.map((column) => {
- const {
- name,
- isVisible
- } = column;
-
- if (!isVisible) {
- return null;
- }
-
- if (
- (name === 'actions' || name === 'details') &&
- onTableOptionChange
- ) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
- {typeof column.label === 'function' ? column.label() : column.label}
-
- );
- })
- }
-
-
- {children}
-
-
- );
-}
-
-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;
diff --git a/frontend/src/Components/Table/Table.tsx b/frontend/src/Components/Table/Table.tsx
new file mode 100644
index 000000000..4f770f8cb
--- /dev/null
+++ b/frontend/src/Components/Table/Table.tsx
@@ -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 (
+
+
+
+ {selectAll && onSelectAllChange ? (
+
+ ) : null}
+
+ {columns.map((column) => {
+ const { name, isVisible, isSortable, ...otherColumnProps } = column;
+
+ if (!isVisible) {
+ return null;
+ }
+
+ if (
+ (name === 'actions' || name === 'details') &&
+ onTableOptionChange
+ ) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {typeof column.label === 'function'
+ ? column.label()
+ : column.label}
+
+ );
+ })}
+
+ {children}
+
+
+ );
+}
+
+export default Table;
diff --git a/frontend/src/Components/Table/TableBody.js b/frontend/src/Components/Table/TableBody.js
deleted file mode 100644
index 5cc60d6f4..000000000
--- a/frontend/src/Components/Table/TableBody.js
+++ /dev/null
@@ -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 (
- {children}
- );
- }
-
-}
-
-TableBody.propTypes = {
- children: PropTypes.node
-};
-
-export default TableBody;
diff --git a/frontend/src/Components/Table/TableBody.tsx b/frontend/src/Components/Table/TableBody.tsx
new file mode 100644
index 000000000..3bd267d5d
--- /dev/null
+++ b/frontend/src/Components/Table/TableBody.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+interface TableBodyProps {
+ children?: React.ReactNode;
+}
+
+function TableBody({ children }: TableBodyProps) {
+ return {children};
+}
+
+export default TableBody;
diff --git a/frontend/src/Components/Table/TableHeader.js b/frontend/src/Components/Table/TableHeader.js
deleted file mode 100644
index 81943e919..000000000
--- a/frontend/src/Components/Table/TableHeader.js
+++ /dev/null
@@ -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 (
-
-
- {children}
-
-
- );
- }
-}
-
-TableHeader.propTypes = {
- children: PropTypes.node
-};
-
-export default TableHeader;
diff --git a/frontend/src/Components/Table/TableHeader.tsx b/frontend/src/Components/Table/TableHeader.tsx
new file mode 100644
index 000000000..ad00b1d60
--- /dev/null
+++ b/frontend/src/Components/Table/TableHeader.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+interface TableHeaderProps {
+ children?: React.ReactNode;
+}
+
+function TableHeader({ children }: TableHeaderProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default TableHeader;
diff --git a/frontend/src/Components/Table/TableHeaderCell.js b/frontend/src/Components/Table/TableHeaderCell.js
deleted file mode 100644
index b0ed5c571..000000000
--- a/frontend/src/Components/Table/TableHeaderCell.js
+++ /dev/null
@@ -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 ?
-
- {children}
-
- {
- isSorting &&
-
- }
- :
-
-
- {children}
- |
- );
- }
-}
-
-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;
diff --git a/frontend/src/Components/Table/TableHeaderCell.tsx b/frontend/src/Components/Table/TableHeaderCell.tsx
new file mode 100644
index 000000000..f89ee6bcc
--- /dev/null
+++ b/frontend/src/Components/Table/TableHeaderCell.tsx
@@ -0,0 +1,66 @@
+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,
+ columnLabel,
+ isSortable = false,
+ 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 ? (
+
+ {children}
+
+ {isSorting && }
+
+ ) : (
+ {children} |
+ );
+}
+
+export default TableHeaderCell;
diff --git a/frontend/src/Components/Table/TablePager.js b/frontend/src/Components/Table/TablePager.js
deleted file mode 100644
index 6f71ee5d2..000000000
--- a/frontend/src/Components/Table/TablePager.js
+++ /dev/null
@@ -1,180 +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 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 (
-
-
- {
- isFetching &&
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- !isShowingPageSelect &&
-
- {page} / {totalPages}
-
- }
-
- {
- isShowingPageSelect &&
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Total records: {totalRecords}
-
-
-
- );
- }
-
-}
-
-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;
diff --git a/frontend/src/Components/Table/TablePager.tsx b/frontend/src/Components/Table/TablePager.tsx
new file mode 100644
index 000000000..d21833de1
--- /dev/null
+++ b/frontend/src/Components/Table/TablePager.tsx
@@ -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) => {
+ setIsShowingPageSelect(false);
+ onPageSelect(value);
+ },
+ [onPageSelect]
+ );
+
+ const handlePageSelectBlur = useCallback(() => {
+ setIsShowingPageSelect(false);
+ }, []);
+
+ if (!page) {
+ return null;
+ }
+
+ return (
+
+
+ {isFetching ? (
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isShowingPageSelect ? null : (
+
+ {page} / {totalPages}
+
+ )}
+
+ {isShowingPageSelect ? (
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {translate('TotalRecords', { totalRecords })}
+
+
+
+ );
+}
+
+export default TablePager;
diff --git a/frontend/src/Components/Table/TableRow.js b/frontend/src/Components/Table/TableRow.js
deleted file mode 100644
index c76083183..000000000
--- a/frontend/src/Components/Table/TableRow.js
+++ /dev/null
@@ -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 (
-
- {children}
-
- );
-}
-
-TableRow.propTypes = {
- className: PropTypes.string.isRequired,
- children: PropTypes.node,
- overlayContent: PropTypes.bool
-};
-
-TableRow.defaultProps = {
- className: styles.row
-};
-
-export default TableRow;
diff --git a/frontend/src/Components/Table/TableRow.tsx b/frontend/src/Components/Table/TableRow.tsx
new file mode 100644
index 000000000..53054d5c1
--- /dev/null
+++ b/frontend/src/Components/Table/TableRow.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import styles from './TableRow.css';
+
+interface TableRowProps extends React.HTMLAttributes {
+ className?: string;
+ children?: React.ReactNode;
+ overlayContent?: boolean;
+}
+
+function TableRow({
+ className = styles.row,
+ children,
+ ...otherProps
+}: TableRowProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default TableRow;
diff --git a/frontend/src/Components/Table/TableRowButton.js b/frontend/src/Components/Table/TableRowButton.js
deleted file mode 100644
index 7ff679673..000000000
--- a/frontend/src/Components/Table/TableRowButton.js
+++ /dev/null
@@ -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 (
-
- );
-}
-
-export default TableRowButton;
diff --git a/frontend/src/Components/Table/TableRowButton.tsx b/frontend/src/Components/Table/TableRowButton.tsx
new file mode 100644
index 000000000..15aeac2a8
--- /dev/null
+++ b/frontend/src/Components/Table/TableRowButton.tsx
@@ -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 ;
+}
+
+export default TableRowButton;
diff --git a/frontend/src/Components/Table/TableSelectAllHeaderCell.js b/frontend/src/Components/Table/TableSelectAllHeaderCell.js
deleted file mode 100644
index c889c32ae..000000000
--- a/frontend/src/Components/Table/TableSelectAllHeaderCell.js
+++ /dev/null
@@ -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 (
-
-
-
- );
-}
-
-TableSelectAllHeaderCell.propTypes = {
- allSelected: PropTypes.bool.isRequired,
- allUnselected: PropTypes.bool.isRequired,
- onSelectAllChange: PropTypes.func.isRequired
-};
-
-export default TableSelectAllHeaderCell;
diff --git a/frontend/src/Components/Table/TableSelectAllHeaderCell.tsx b/frontend/src/Components/Table/TableSelectAllHeaderCell.tsx
new file mode 100644
index 000000000..418d3adce
--- /dev/null
+++ b/frontend/src/Components/Table/TableSelectAllHeaderCell.tsx
@@ -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 (
+
+
+
+ );
+}
+
+export default TableSelectAllHeaderCell;
diff --git a/frontend/src/Components/Table/VirtualTable.js b/frontend/src/Components/Table/VirtualTable.js
deleted file mode 100644
index 5473413cb..000000000
--- a/frontend/src/Components/Table/VirtualTable.js
+++ /dev/null
@@ -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 (
-
- {({ height, registerChild, onChildScroll, scrollTop }) => {
- if (!height) {
- return null;
- }
- return (
-
-
- {header}
-
-
-
-
-
- );
- }
- }
-
- );
- }
-}
-
-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;
diff --git a/frontend/src/Components/Table/VirtualTable.tsx b/frontend/src/Components/Table/VirtualTable.tsx
new file mode 100644
index 000000000..362be113b
--- /dev/null
+++ b/frontend/src/Components/Table/VirtualTable.tsx
@@ -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 {
+ 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({
+ isSmallScreen,
+ className = styles.tableContainer,
+ items,
+ scroller,
+ scrollIndex,
+ scrollTop,
+ header,
+ headerHeight = 38,
+ rowHeight = ROW_HEIGHT,
+ rowRenderer,
+ ...otherProps
+}: VirtualTableProps) {
+ const [measureRef, bounds] = useMeasure();
+ const gridRef = useRef(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 (
+
+ {({ height, registerChild, onChildScroll, scrollTop }) => {
+ if (!height) {
+ return null;
+ }
+ return (
+
+
+ {header}
+
+ {/* @ts-expect-error - ref type is incompatible */}
+
+
+
+
+
+ );
+ }}
+
+ );
+}
+
+export default VirtualTable;
diff --git a/frontend/src/Components/Table/VirtualTableHeader.js b/frontend/src/Components/Table/VirtualTableHeader.js
deleted file mode 100644
index cf6a0f47b..000000000
--- a/frontend/src/Components/Table/VirtualTableHeader.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import styles from './VirtualTableHeader.css';
-
-function VirtualTableHeader({ children }) {
- return (
-
- {children}
-
- );
-}
-
-VirtualTableHeader.propTypes = {
- children: PropTypes.node
-};
-
-export default VirtualTableHeader;
diff --git a/frontend/src/Components/Table/VirtualTableHeader.tsx b/frontend/src/Components/Table/VirtualTableHeader.tsx
new file mode 100644
index 000000000..5e9db83dc
--- /dev/null
+++ b/frontend/src/Components/Table/VirtualTableHeader.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import styles from './VirtualTableHeader.css';
+
+interface VirtualTableHeaderProps {
+ children?: React.ReactNode;
+}
+
+function VirtualTableHeader({ children }: VirtualTableHeaderProps) {
+ return {children}
;
+}
+
+export default VirtualTableHeader;
diff --git a/frontend/src/Components/Table/VirtualTableHeaderCell.js b/frontend/src/Components/Table/VirtualTableHeaderCell.js
deleted file mode 100644
index 55c688d01..000000000
--- a/frontend/src/Components/Table/VirtualTableHeaderCell.js
+++ /dev/null
@@ -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
-
- {label}
-
- );
-}
-
-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 ?
-
- {children}
-
- {
- isSorting &&
-
- }
- :
-
-
- {children}
-
- );
- }
-}
-
-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;
diff --git a/frontend/src/Components/Table/VirtualTableHeaderCell.tsx b/frontend/src/Components/Table/VirtualTableHeaderCell.tsx
new file mode 100644
index 000000000..fdaa53612
--- /dev/null
+++ b/frontend/src/Components/Table/VirtualTableHeaderCell.tsx
@@ -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 ? (
+
+ {children}
+
+ {isSorting ? : null}
+
+ ) : (
+ {children}
+ );
+}
+
+export default VirtualTableHeaderCell;
diff --git a/frontend/src/Components/Table/VirtualTableRow.js b/frontend/src/Components/Table/VirtualTableRow.js
deleted file mode 100644
index 0a423902e..000000000
--- a/frontend/src/Components/Table/VirtualTableRow.js
+++ /dev/null
@@ -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 (
-
- {children}
-
- );
-}
-
-VirtualTableRow.propTypes = {
- className: PropTypes.string.isRequired,
- style: PropTypes.object.isRequired,
- children: PropTypes.node
-};
-
-VirtualTableRow.defaultProps = {
- className: styles.row
-};
-
-export default VirtualTableRow;
diff --git a/frontend/src/Components/Table/VirtualTableRow.tsx b/frontend/src/Components/Table/VirtualTableRow.tsx
new file mode 100644
index 000000000..dcdb3da4f
--- /dev/null
+++ b/frontend/src/Components/Table/VirtualTableRow.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import styles from './VirtualTableRow.css';
+
+interface VirtualTableRowProps extends React.HTMLAttributes {
+ className: string;
+ style: object;
+ children?: React.ReactNode;
+}
+
+function VirtualTableRow({
+ className = styles.row,
+ children,
+ style,
+ ...otherProps
+}: VirtualTableRowProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default VirtualTableRow;
diff --git a/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.js b/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.js
deleted file mode 100644
index 58b246763..000000000
--- a/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.js
+++ /dev/null
@@ -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 (
-
-
-
- );
-}
-
-VirtualTableSelectAllHeaderCell.propTypes = {
- allSelected: PropTypes.bool.isRequired,
- allUnselected: PropTypes.bool.isRequired,
- onSelectAllChange: PropTypes.func.isRequired
-};
-
-export default VirtualTableSelectAllHeaderCell;
diff --git a/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.tsx b/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.tsx
new file mode 100644
index 000000000..be91ef58f
--- /dev/null
+++ b/frontend/src/Components/Table/VirtualTableSelectAllHeaderCell.tsx
@@ -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 (
+
+
+
+ );
+}
+
+export default VirtualTableSelectAllHeaderCell;
diff --git a/frontend/src/Helpers/Hooks/useSelectState.tsx b/frontend/src/Helpers/Hooks/useSelectState.tsx
index 8fb96e42a..4e1038bb6 100644
--- a/frontend/src/Helpers/Hooks/useSelectState.tsx
+++ b/frontend/src/Helpers/Hooks/useSelectState.tsx
@@ -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;
+export type SelectedState = Record;
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';
diff --git a/frontend/src/Helpers/Props/ScrollDirection.ts b/frontend/src/Helpers/Props/ScrollDirection.ts
deleted file mode 100644
index 0da932d22..000000000
--- a/frontend/src/Helpers/Props/ScrollDirection.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-enum ScrollDirection {
- Horizontal = 'horizontal',
- Vertical = 'vertical',
- None = 'none',
- Both = 'both',
-}
-
-export default ScrollDirection;
diff --git a/frontend/src/Helpers/Props/scrollDirections.js b/frontend/src/Helpers/Props/scrollDirections.ts
similarity index 71%
rename from frontend/src/Helpers/Props/scrollDirections.js
rename to frontend/src/Helpers/Props/scrollDirections.ts
index 1ae61143b..e82fdfae6 100644
--- a/frontend/src/Helpers/Props/scrollDirections.js
+++ b/frontend/src/Helpers/Props/scrollDirections.ts
@@ -4,3 +4,5 @@ export const HORIZONTAL = 'horizontal';
export const VERTICAL = 'vertical';
export const all = [NONE, HORIZONTAL, VERTICAL, BOTH];
+
+export type ScrollDirection = 'none' | 'both' | 'horizontal' | 'vertical';
diff --git a/frontend/src/Helpers/Props/sortDirections.js b/frontend/src/Helpers/Props/sortDirections.ts
similarity index 68%
rename from frontend/src/Helpers/Props/sortDirections.js
rename to frontend/src/Helpers/Props/sortDirections.ts
index ff3b17bb6..f082cfa59 100644
--- a/frontend/src/Helpers/Props/sortDirections.js
+++ b/frontend/src/Helpers/Props/sortDirections.ts
@@ -2,3 +2,5 @@ export const ASCENDING = 'ascending';
export const DESCENDING = 'descending';
export const all = [ASCENDING, DESCENDING];
+
+export type SortDirection = 'ascending' | 'descending';
diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx
index aabaf67c1..db7ca8fc9 100644
--- a/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx
+++ b/frontend/src/Settings/CustomFormats/CustomFormats/Manage/ManageCustomFormatsModalContent.tsx
@@ -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';
@@ -133,7 +133,7 @@ function ManageCustomFormatsModalContent(
);
const onSelectAllChange = useCallback(
- ({ value }: SelectStateInputProps) => {
+ ({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
index b2c1208cb..43c40a912 100644
--- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
+++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx
@@ -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';
@@ -185,7 +185,7 @@ function ManageDownloadClientsModalContent(
);
const onSelectAllChange = useCallback(
- ({ value }: SelectStateInputProps) => {
+ ({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]
diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx
index 4fee485c9..36a06cbee 100644
--- a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx
+++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx
@@ -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]
diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx
index 997d1b566..f14723939 100644
--- a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx
+++ b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx
@@ -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';
@@ -183,7 +183,7 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
);
const onSelectAllChange = useCallback(
- ({ value }: SelectStateInputProps) => {
+ ({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]
diff --git a/frontend/src/System/Tasks/Queued/QueuedTasks.tsx b/frontend/src/System/Tasks/Queued/QueuedTasks.tsx
index e79deed7c..ec4438b00 100644
--- a/frontend/src/System/Tasks/Queued/QueuedTasks.tsx
+++ b/frontend/src/System/Tasks/Queued/QueuedTasks.tsx
@@ -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,
},
];
diff --git a/frontend/src/typings/Table.ts b/frontend/src/typings/Table.ts
new file mode 100644
index 000000000..63c079612
--- /dev/null
+++ b/frontend/src/typings/Table.ts
@@ -0,0 +1,6 @@
+import Column from 'Components/Table/Column';
+
+export interface TableOptionsChangePayload {
+ pageSize?: number;
+ columns?: Column[];
+}
diff --git a/frontend/src/typings/inputs.ts b/frontend/src/typings/inputs.ts
index 7d202cd44..e218abbbd 100644
--- a/frontend/src/typings/inputs.ts
+++ b/frontend/src/typings/inputs.ts
@@ -5,7 +5,6 @@ export type InputChanged = {
export type InputOnChange = (change: InputChanged) => void;
-export type CheckInputChanged = {
- name: string;
- value: boolean;
-};
+export interface CheckInputChanged extends InputChanged {
+ shiftKey: boolean;
+}
diff --git a/frontend/src/typings/props.ts b/frontend/src/typings/props.ts
index 5b87e36b3..c1c025fac 100644
--- a/frontend/src/typings/props.ts
+++ b/frontend/src/typings/props.ts
@@ -1,5 +1,5 @@
export interface SelectStateInputProps {
- id: number;
- value: boolean;
+ id: number | string;
+ value: boolean | null;
shiftKey: boolean;
}
diff --git a/package.json b/package.json
index 8d8be81c1..e71db1711 100644
--- a/package.json
+++ b/package.json
@@ -100,6 +100,7 @@
"@types/react-lazyload": "3.2.3",
"@types/react-router-dom": "5.3.3",
"@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",
@@ -148,6 +149,9 @@
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"
},
+ "resolutions": {
+ "string-width": "4.2.3"
+ },
"volta": {
"node": "16.17.0",
"yarn": "1.22.19"
diff --git a/yarn.lock b/yarn.lock
index 1ff6e115e..0b1e764ae 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1528,6 +1528,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"
@@ -2974,11 +2982,6 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1:
es-errors "^1.3.0"
gopd "^1.2.0"
-eastasianwidth@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
- integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
-
electron-to-chromium@^1.5.227:
version "1.5.228"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz#38b849bc8714bd21fb64f5ad56bf8cfd8638e1e9"
@@ -2999,11 +3002,6 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-emoji-regex@^9.2.2:
- version "9.2.2"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
- integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
-
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@@ -6511,7 +6509,7 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.1.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -6520,24 +6518,6 @@ string-template@~0.2.1:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
-string-width@^4.1.0, string-width@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^5.0.1, string-width@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
- integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
- dependencies:
- eastasianwidth "^0.2.0"
- emoji-regex "^9.2.2"
- strip-ansi "^7.0.1"
-
string.prototype.matchall@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"
@@ -6611,14 +6591,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==