mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-22 22:16:13 -04:00
Upgrade react-dnd and DnD Components to TypeScript
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
.columnContainer {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
@@ -43,6 +47,17 @@
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.notDragable {
|
||||
padding: 4px 0;
|
||||
.placeholder {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px dotted #aaa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.placeholderBefore {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholderAfter {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
interface CssExports {
|
||||
'checkContainer': string;
|
||||
'column': string;
|
||||
'columnContainer': string;
|
||||
'dragHandle': string;
|
||||
'dragIcon': string;
|
||||
'isDragging': string;
|
||||
'label': string;
|
||||
'notDragable': string;
|
||||
'placeholder': string;
|
||||
'placeholderAfter': string;
|
||||
'placeholderBefore': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import styles from './TableOptionsColumn.css';
|
||||
|
||||
function TableOptionsColumn(props) {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
isVisible,
|
||||
isModifiable,
|
||||
isDragging,
|
||||
connectDragSource,
|
||||
onVisibleChange
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={isModifiable ? undefined : styles.notDragable}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.column,
|
||||
isDragging && styles.isDragging
|
||||
)}
|
||||
>
|
||||
<label
|
||||
className={styles.label}
|
||||
>
|
||||
<CheckInput
|
||||
containerClassName={styles.checkContainer}
|
||||
name={name}
|
||||
value={isVisible}
|
||||
isDisabled={isModifiable === false}
|
||||
onChange={onVisibleChange}
|
||||
/>
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</label>
|
||||
|
||||
{
|
||||
!!connectDragSource &&
|
||||
connectDragSource(
|
||||
<div className={styles.dragHandle}>
|
||||
<Icon
|
||||
className={styles.dragIcon}
|
||||
name={icons.REORDER}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TableOptionsColumn.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isModifiable: PropTypes.bool.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool,
|
||||
connectDragSource: PropTypes.func,
|
||||
onVisibleChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TableOptionsColumn;
|
||||
@@ -0,0 +1,163 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useRef } from 'react';
|
||||
import { DragSourceMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import DragType from 'Helpers/DragType';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import Column from '../Column';
|
||||
import styles from './TableOptionsColumn.css';
|
||||
|
||||
interface DragItem {
|
||||
name: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface TableOptionsColumnProps {
|
||||
name: string;
|
||||
label: Column['label'];
|
||||
isDraggingDown: boolean;
|
||||
isDraggingUp: boolean;
|
||||
isVisible: boolean;
|
||||
isModifiable: boolean;
|
||||
index: number;
|
||||
onVisibleChange: (change: CheckInputChanged) => void;
|
||||
onColumnDragEnd: (didDrop: boolean) => void;
|
||||
onColumnDragMove: (dragIndex: number, hoverIndex: number) => void;
|
||||
}
|
||||
|
||||
function TableOptionsColumn({
|
||||
name,
|
||||
label,
|
||||
index,
|
||||
isDraggingDown,
|
||||
isDraggingUp,
|
||||
isVisible,
|
||||
isModifiable,
|
||||
onVisibleChange,
|
||||
onColumnDragEnd,
|
||||
onColumnDragMove,
|
||||
}: TableOptionsColumnProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [{ isOver }, dropRef] = useDrop<DragItem, void, { isOver: boolean }>({
|
||||
accept: DragType.TableColumn,
|
||||
collect(monitor) {
|
||||
return {
|
||||
isOver: monitor.isOver(),
|
||||
};
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isModifiable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dragIndex = item.index;
|
||||
const hoverIndex = index;
|
||||
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY =
|
||||
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
|
||||
|
||||
// When moving up, only trigger if drag position is above 50% and
|
||||
// when moving down, only trigger if drag position is below 50%.
|
||||
// If we're moving down the hoverIndex needs to be increased
|
||||
// by one so it's ordered properly. Otherwise the hoverIndex will work.
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
onColumnDragMove(dragIndex, hoverIndex);
|
||||
},
|
||||
});
|
||||
|
||||
const [{ isDragging }, dragRef, previewRef] = useDrag<
|
||||
DragItem,
|
||||
unknown,
|
||||
{ isDragging: boolean }
|
||||
>({
|
||||
type: DragType.TableColumn,
|
||||
item: () => {
|
||||
return {
|
||||
name,
|
||||
index,
|
||||
};
|
||||
},
|
||||
collect: (monitor: DragSourceMonitor<unknown, unknown>) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
end: (_item: DragItem, monitor) => {
|
||||
onColumnDragEnd(monitor.didDrop());
|
||||
},
|
||||
});
|
||||
|
||||
dropRef(previewRef(ref));
|
||||
|
||||
const isBefore = !isDragging && isDraggingUp && isOver;
|
||||
const isAfter = !isDragging && isDraggingDown && isOver;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.columnContainer}>
|
||||
{isBefore ? (
|
||||
<div
|
||||
className={classNames(styles.placeholder, styles.placeholderBefore)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={classNames(styles.column, isDragging && styles.isDragging)}
|
||||
>
|
||||
<label className={styles.label}>
|
||||
<CheckInput
|
||||
containerClassName={styles.checkContainer}
|
||||
name={name}
|
||||
value={isVisible}
|
||||
isDisabled={isModifiable === false}
|
||||
onChange={onVisibleChange}
|
||||
/>
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</label>
|
||||
|
||||
{isModifiable ? (
|
||||
<div ref={dragRef} className={styles.dragHandle}>
|
||||
<Icon className={styles.dragIcon} name={icons.REORDER} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isAfter ? (
|
||||
<div
|
||||
className={classNames(styles.placeholder, styles.placeholderAfter)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableOptionsColumn;
|
||||
@@ -1,4 +0,0 @@
|
||||
.dragPreview {
|
||||
width: 380px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'dragPreview': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -1,78 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DragLayer } from 'react-dnd';
|
||||
import DragPreviewLayer from 'Components/DragPreviewLayer';
|
||||
import { TABLE_COLUMN } from 'Helpers/dragTypes';
|
||||
import dimensions from 'Styles/Variables/dimensions.js';
|
||||
import TableOptionsColumn from './TableOptionsColumn';
|
||||
import styles from './TableOptionsColumnDragPreview.css';
|
||||
|
||||
const formGroupSmallWidth = parseInt(dimensions.formGroupSmallWidth);
|
||||
const formLabelLargeWidth = parseInt(dimensions.formLabelLargeWidth);
|
||||
const formLabelRightMarginWidth = parseInt(dimensions.formLabelRightMarginWidth);
|
||||
const dragHandleWidth = parseInt(dimensions.dragHandleWidth);
|
||||
|
||||
function collectDragLayer(monitor) {
|
||||
return {
|
||||
item: monitor.getItem(),
|
||||
itemType: monitor.getItemType(),
|
||||
currentOffset: monitor.getSourceClientOffset()
|
||||
};
|
||||
}
|
||||
|
||||
class TableOptionsColumnDragPreview extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
item,
|
||||
itemType,
|
||||
currentOffset
|
||||
} = this.props;
|
||||
|
||||
if (!currentOffset || itemType !== TABLE_COLUMN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The offset is shifted because the drag handle is on the right edge of the
|
||||
// list item and the preview is wider than the drag handle.
|
||||
|
||||
const { x, y } = currentOffset;
|
||||
const handleOffset = formGroupSmallWidth - formLabelLargeWidth - formLabelRightMarginWidth - dragHandleWidth;
|
||||
const transform = `translate3d(${x - handleOffset}px, ${y}px, 0)`;
|
||||
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
WebkitTransform: transform,
|
||||
msTransform: transform,
|
||||
transform
|
||||
};
|
||||
|
||||
return (
|
||||
<DragPreviewLayer>
|
||||
<div
|
||||
className={styles.dragPreview}
|
||||
style={style}
|
||||
>
|
||||
<TableOptionsColumn
|
||||
isDragging={false}
|
||||
{...item}
|
||||
/>
|
||||
</div>
|
||||
</DragPreviewLayer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableOptionsColumnDragPreview.propTypes = {
|
||||
item: PropTypes.object,
|
||||
itemType: PropTypes.string,
|
||||
currentOffset: PropTypes.shape({
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
export default DragLayer(collectDragLayer)(TableOptionsColumnDragPreview);
|
||||
@@ -1,18 +0,0 @@
|
||||
.columnDragSource {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.columnPlaceholder {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px dotted #aaa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.columnPlaceholderBefore {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.columnPlaceholderAfter {
|
||||
margin-top: 8px;
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'columnDragSource': string;
|
||||
'columnPlaceholder': string;
|
||||
'columnPlaceholderAfter': string;
|
||||
'columnPlaceholderBefore': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -1,164 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { TABLE_COLUMN } from 'Helpers/dragTypes';
|
||||
import TableOptionsColumn from './TableOptionsColumn';
|
||||
import styles from './TableOptionsColumnDragSource.css';
|
||||
|
||||
const columnDragSource = {
|
||||
beginDrag(column) {
|
||||
return column;
|
||||
},
|
||||
|
||||
endDrag(props, monitor, component) {
|
||||
props.onColumnDragEnd(monitor.getItem(), monitor.didDrop());
|
||||
}
|
||||
};
|
||||
|
||||
const columnDropTarget = {
|
||||
hover(props, monitor, component) {
|
||||
const dragIndex = monitor.getItem().index;
|
||||
const hoverIndex = props.index;
|
||||
|
||||
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When moving up, only trigger if drag position is above 50% and
|
||||
// when moving down, only trigger if drag position is below 50%.
|
||||
// If we're moving down the hoverIndex needs to be increased
|
||||
// by one so it's ordered properly. Otherwise the hoverIndex will work.
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onColumnDragMove(dragIndex, hoverIndex);
|
||||
}
|
||||
};
|
||||
|
||||
function collectDragSource(connect, monitor) {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging()
|
||||
};
|
||||
}
|
||||
|
||||
function collectDropTarget(connect, monitor) {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver()
|
||||
};
|
||||
}
|
||||
|
||||
class TableOptionsColumnDragSource extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
isVisible,
|
||||
isModifiable,
|
||||
index,
|
||||
isDragging,
|
||||
isDraggingUp,
|
||||
isDraggingDown,
|
||||
isOver,
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
onVisibleChange
|
||||
} = this.props;
|
||||
|
||||
const isBefore = !isDragging && isDraggingUp && isOver;
|
||||
const isAfter = !isDragging && isDraggingDown && isOver;
|
||||
|
||||
// if (isDragging && !isOver) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
return connectDropTarget(
|
||||
<div
|
||||
className={classNames(
|
||||
styles.columnDragSource,
|
||||
isBefore && styles.isDraggingUp,
|
||||
isAfter && styles.isDraggingDown
|
||||
)}
|
||||
>
|
||||
{
|
||||
isBefore &&
|
||||
<div
|
||||
className={classNames(
|
||||
styles.columnPlaceholder,
|
||||
styles.columnPlaceholderBefore
|
||||
)}
|
||||
/>
|
||||
}
|
||||
|
||||
<TableOptionsColumn
|
||||
name={name}
|
||||
label={typeof label === 'function' ? label() : label}
|
||||
isVisible={isVisible}
|
||||
isModifiable={isModifiable}
|
||||
index={index}
|
||||
isDragging={isDragging}
|
||||
isOver={isOver}
|
||||
connectDragSource={connectDragSource}
|
||||
onVisibleChange={onVisibleChange}
|
||||
/>
|
||||
|
||||
{
|
||||
isAfter &&
|
||||
<div
|
||||
className={classNames(
|
||||
styles.columnPlaceholder,
|
||||
styles.columnPlaceholderAfter
|
||||
)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableOptionsColumnDragSource.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isModifiable: PropTypes.bool.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool,
|
||||
isDraggingUp: PropTypes.bool,
|
||||
isDraggingDown: PropTypes.bool,
|
||||
isOver: PropTypes.bool,
|
||||
connectDragSource: PropTypes.func,
|
||||
connectDropTarget: PropTypes.func,
|
||||
onVisibleChange: PropTypes.func.isRequired,
|
||||
onColumnDragMove: PropTypes.func.isRequired,
|
||||
onColumnDragEnd: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DropTarget(
|
||||
TABLE_COLUMN,
|
||||
columnDropTarget,
|
||||
collectDropTarget
|
||||
)(DragSource(
|
||||
TABLE_COLUMN,
|
||||
columnDragSource,
|
||||
collectDragSource
|
||||
)(TableOptionsColumnDragSource));
|
||||
@@ -1,263 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { DndProvider } from 'react-dnd-multi-backend';
|
||||
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormInputHelpText from 'Components/Form/FormInputHelpText';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TableOptionsColumn from './TableOptionsColumn';
|
||||
import TableOptionsColumnDragPreview from './TableOptionsColumnDragPreview';
|
||||
import TableOptionsColumnDragSource from './TableOptionsColumnDragSource';
|
||||
import styles from './TableOptionsModal.css';
|
||||
|
||||
class TableOptionsModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPageSize: !!props.pageSize,
|
||||
pageSize: props.pageSize,
|
||||
pageSizeError: null,
|
||||
dragIndex: null,
|
||||
dropIndex: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.pageSize !== this.state.pageSize) {
|
||||
this.setState({ pageSize: this.props.pageSize });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPageSizeChange = ({ value }) => {
|
||||
let pageSizeError = null;
|
||||
const maxPageSize = this.props.maxPageSize ?? 250;
|
||||
|
||||
if (value < 5) {
|
||||
pageSizeError = translate('TablePageSizeMinimum', { minimumValue: '5' });
|
||||
} else if (value > maxPageSize) {
|
||||
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: `${maxPageSize}` });
|
||||
} else {
|
||||
this.props.onTableOptionChange({ pageSize: value });
|
||||
}
|
||||
|
||||
this.setState({
|
||||
pageSize: value,
|
||||
pageSizeError
|
||||
});
|
||||
};
|
||||
|
||||
onVisibleChange = ({ name, value }) => {
|
||||
const columns = _.cloneDeep(this.props.columns);
|
||||
|
||||
const column = _.find(columns, { name });
|
||||
column.isVisible = value;
|
||||
|
||||
this.props.onTableOptionChange({ columns });
|
||||
};
|
||||
|
||||
onColumnDragMove = (dragIndex, dropIndex) => {
|
||||
if (this.state.dragIndex !== dragIndex || this.state.dropIndex !== dropIndex) {
|
||||
this.setState({
|
||||
dragIndex,
|
||||
dropIndex
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onColumnDragEnd = ({ id }, didDrop) => {
|
||||
const {
|
||||
dragIndex,
|
||||
dropIndex
|
||||
} = this.state;
|
||||
|
||||
if (didDrop && dropIndex !== null) {
|
||||
const columns = _.cloneDeep(this.props.columns);
|
||||
const items = columns.splice(dragIndex, 1);
|
||||
columns.splice(dropIndex, 0, items[0]);
|
||||
|
||||
this.props.onTableOptionChange({ columns });
|
||||
}
|
||||
|
||||
this.setState({
|
||||
dragIndex: null,
|
||||
dropIndex: null
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
columns,
|
||||
canModifyColumns,
|
||||
optionsComponent: OptionsComponent,
|
||||
onTableOptionChange,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
hasPageSize,
|
||||
pageSize,
|
||||
pageSizeError,
|
||||
dragIndex,
|
||||
dropIndex
|
||||
} = this.state;
|
||||
|
||||
const isDragging = dropIndex !== null;
|
||||
const isDraggingUp = isDragging && dropIndex < dragIndex;
|
||||
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
||||
|
||||
return (
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
{
|
||||
isOpen ?
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('TableOptions')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
{
|
||||
hasPageSize ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TablePageSize')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="pageSize"
|
||||
value={pageSize || 0}
|
||||
helpText={translate('TablePageSizeHelpText')}
|
||||
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
|
||||
onChange={this.onPageSizeChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
OptionsComponent ?
|
||||
<OptionsComponent
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
/> : null
|
||||
}
|
||||
|
||||
{
|
||||
canModifyColumns ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TableColumns')}</FormLabel>
|
||||
|
||||
<div>
|
||||
<FormInputHelpText
|
||||
text={translate('TableColumnsHelpText')}
|
||||
/>
|
||||
|
||||
<div className={styles.columns}>
|
||||
{
|
||||
columns.map((column, index) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
columnLabel,
|
||||
isVisible,
|
||||
isModifiable
|
||||
} = column;
|
||||
|
||||
if (isModifiable !== false) {
|
||||
return (
|
||||
<TableOptionsColumnDragSource
|
||||
key={name}
|
||||
name={name}
|
||||
label={columnLabel || label}
|
||||
isVisible={isVisible}
|
||||
isModifiable={true}
|
||||
index={index}
|
||||
isDragging={isDragging}
|
||||
isDraggingUp={isDraggingUp}
|
||||
isDraggingDown={isDraggingDown}
|
||||
onVisibleChange={this.onVisibleChange}
|
||||
onColumnDragMove={this.onColumnDragMove}
|
||||
onColumnDragEnd={this.onColumnDragEnd}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableOptionsColumn
|
||||
key={name}
|
||||
name={name}
|
||||
label={columnLabel || label}
|
||||
isVisible={isVisible}
|
||||
index={index}
|
||||
isModifiable={false}
|
||||
onVisibleChange={this.onVisibleChange}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<TableOptionsColumnDragPreview />
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent> :
|
||||
null
|
||||
}
|
||||
</Modal>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
pageSize: PropTypes.number,
|
||||
maxPageSize: PropTypes.number,
|
||||
canModifyColumns: PropTypes.bool.isRequired,
|
||||
optionsComponent: PropTypes.elementType,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
TableOptionsModal.defaultProps = {
|
||||
canModifyColumns: true
|
||||
};
|
||||
|
||||
export default TableOptionsModal;
|
||||
@@ -0,0 +1,216 @@
|
||||
import _ from 'lodash';
|
||||
import { HTML5toTouch } from 'rdndmb-html5-to-touch';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { DndProvider } from 'react-dnd-multi-backend';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormInputHelpText from 'Components/Form/FormInputHelpText';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { CheckInputChanged, InputChanged } from 'typings/inputs';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import Column from '../Column';
|
||||
import TableOptionsColumn from './TableOptionsColumn';
|
||||
import styles from './TableOptionsModal.css';
|
||||
|
||||
interface TableOptionsModalProps {
|
||||
isOpen: boolean;
|
||||
columns: Column[];
|
||||
pageSize?: number;
|
||||
maxPageSize?: number;
|
||||
canModifyColumns: boolean;
|
||||
optionsComponent?: React.ElementType;
|
||||
onTableOptionChange: (payload: TableOptionsChangePayload) => void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function TableOptionsModal({
|
||||
isOpen,
|
||||
columns,
|
||||
canModifyColumns = true,
|
||||
optionsComponent: OptionsComponent,
|
||||
pageSize: propsPageSize,
|
||||
maxPageSize = 250,
|
||||
onTableOptionChange,
|
||||
onModalClose,
|
||||
}: TableOptionsModalProps) {
|
||||
const [pageSize, setPageSize] = useState(propsPageSize);
|
||||
const [pageSizeError, setPageSizeError] = useState<string | null>(null);
|
||||
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
||||
const [dropIndex, setDropIndex] = useState<number | null>(null);
|
||||
|
||||
const hasPageSize = !!propsPageSize;
|
||||
const isDragging = dropIndex !== null;
|
||||
const isDraggingUp =
|
||||
isDragging &&
|
||||
dropIndex != null &&
|
||||
dragIndex != null &&
|
||||
dropIndex < dragIndex;
|
||||
const isDraggingDown =
|
||||
isDragging &&
|
||||
dropIndex != null &&
|
||||
dragIndex != null &&
|
||||
dropIndex > dragIndex;
|
||||
|
||||
const handlePageSizeChange = useCallback(
|
||||
({ value }: InputChanged<number>) => {
|
||||
let error: string | null = null;
|
||||
|
||||
if (value < 5) {
|
||||
error = translate('TablePageSizeMinimum', {
|
||||
minimumValue: '5',
|
||||
});
|
||||
} else if (value > maxPageSize) {
|
||||
error = translate('TablePageSizeMaximum', {
|
||||
maximumValue: `${maxPageSize}`,
|
||||
});
|
||||
} else {
|
||||
onTableOptionChange({ pageSize: value });
|
||||
}
|
||||
|
||||
setPageSize(value);
|
||||
setPageSizeError(error);
|
||||
},
|
||||
[maxPageSize, onTableOptionChange]
|
||||
);
|
||||
|
||||
const handleVisibleChange = useCallback(
|
||||
({ name, value }: CheckInputChanged) => {
|
||||
const newColumns = columns.map((column) => {
|
||||
if (column.name === name) {
|
||||
return {
|
||||
...column,
|
||||
isVisible: value,
|
||||
};
|
||||
}
|
||||
|
||||
return column;
|
||||
});
|
||||
|
||||
onTableOptionChange({ columns: newColumns });
|
||||
},
|
||||
[columns, onTableOptionChange]
|
||||
);
|
||||
|
||||
const handleColumnDragMove = useCallback(
|
||||
(newDragIndex: number, newDropIndex: number) => {
|
||||
setDropIndex(newDropIndex);
|
||||
setDragIndex(newDragIndex);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleColumnDragEnd = useCallback(
|
||||
(didDrop: boolean) => {
|
||||
if (didDrop && dragIndex && dropIndex !== null) {
|
||||
const newColumns = [...columns];
|
||||
const items = newColumns.splice(dragIndex, 1);
|
||||
newColumns.splice(dropIndex, 0, items[0]);
|
||||
|
||||
onTableOptionChange({ columns: newColumns });
|
||||
}
|
||||
|
||||
setDragIndex(null);
|
||||
setDropIndex(null);
|
||||
},
|
||||
[dragIndex, dropIndex, columns, onTableOptionChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPageSize(propsPageSize);
|
||||
}, [propsPageSize]);
|
||||
|
||||
return (
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
{isOpen ? (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('TableOptions')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
{hasPageSize ? (
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TablePageSize')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="pageSize"
|
||||
value={pageSize || 0}
|
||||
helpText={translate('TablePageSizeHelpText')}
|
||||
errors={
|
||||
pageSizeError ? [{ message: pageSizeError }] : undefined
|
||||
}
|
||||
onChange={handlePageSizeChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
) : null}
|
||||
|
||||
{OptionsComponent ? (
|
||||
<OptionsComponent onTableOptionChange={onTableOptionChange} />
|
||||
) : null}
|
||||
|
||||
{canModifyColumns ? (
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TableColumns')}</FormLabel>
|
||||
|
||||
<div>
|
||||
<FormInputHelpText
|
||||
text={translate('TableColumnsHelpText')}
|
||||
/>
|
||||
|
||||
<div className={styles.columns}>
|
||||
{columns.map((column, index) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
columnLabel,
|
||||
isVisible,
|
||||
isModifiable = true,
|
||||
} = column;
|
||||
|
||||
return (
|
||||
<TableOptionsColumn
|
||||
key={name}
|
||||
name={name}
|
||||
label={columnLabel ?? label}
|
||||
isVisible={isVisible}
|
||||
isModifiable={isModifiable}
|
||||
index={index}
|
||||
isDraggingUp={isDraggingUp}
|
||||
isDraggingDown={isDraggingDown}
|
||||
onVisibleChange={handleVisibleChange}
|
||||
onColumnDragMove={handleColumnDragMove}
|
||||
onColumnDragEnd={handleColumnDragEnd}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
) : null}
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Close')}</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
) : null}
|
||||
</Modal>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
|
||||
TableOptionsModal.defaultProps = {
|
||||
canModifyColumns: true,
|
||||
};
|
||||
|
||||
export default TableOptionsModal;
|
||||
Reference in New Issue
Block a user