diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index 89ea3c9690..6d4ceb4486 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { size, filter, forEach, extend } from 'lodash'; import { react2angular } from 'react2angular'; -import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc'; +import { SortableContainer, SortableElement, DragHandle } from '@/components/sortable'; import { $location } from '@/services/ng'; import { Parameter } from '@/services/query'; import ParameterApplyButton from '@/components/ParameterApplyButton'; @@ -12,18 +12,6 @@ import { toHuman } from '@/filters'; import './Parameters.less'; -const DragHandle = sortableHandle(({ parameterName }) => ( -
-)); - -const SortableItem = sortableElement(({ className, parameterName, disabled, children }) => ( -
- {!disabled && } - {children} -
-)); -const SortableContainer = sortableContainer(({ children }) => children); - function updateUrl(parameters) { const params = extend({}, $location.search()); parameters.forEach((param) => { @@ -50,12 +38,12 @@ export class Parameters extends React.Component { onValuesChange: () => {}, onPendingValuesChange: () => {}, onParametersEdit: () => {}, - } + }; constructor(props) { super(props); const { parameters } = props; - this.state = { parameters, dragging: false }; + this.state = { parameters }; if (!props.disableUrlUpdate) { updateUrl(parameters); } @@ -101,11 +89,6 @@ export class Parameters extends React.Component { return { parameters }; }); } - this.setState({ dragging: false }); - }; - - onBeforeSortStart = () => { - this.setState({ dragging: true }); }; applyChanges = () => { @@ -170,32 +153,32 @@ export class Parameters extends React.Component { } render() { - const { parameters, dragging } = this.state; + const { parameters } = this.state; const { editable } = this.props; const dirtyParamCount = size(filter(parameters, 'hasPendingValue')); return ( -
- {parameters.map((param, index) => ( - + {parameters.map((param, index) => ( + +
+ {editable && } {this.renderParameter(param, index)} - - ))} - - -
+
+ + ))} +
); } diff --git a/client/app/components/Parameters.less b/client/app/components/Parameters.less index 304c1a8f86..338912fe80 100644 --- a/client/app/components/Parameters.less +++ b/client/app/components/Parameters.less @@ -1,32 +1,25 @@ @import '../assets/less/ant'; -.drag-handle { - background: linear-gradient(90deg, transparent 0px, white 1px, white 2px) - center, - linear-gradient(transparent 0px, white 1px, white 2px) center, #111111; - background-size: 2px 2px; - display: inline-block; - width: 6px; - height: 36px; - vertical-align: bottom; - margin-right: 5px; - cursor: move; -} - .parameter-block { display: inline-block; background: white; padding: 0 12px 6px 0; vertical-align: top; + z-index: 1; + + .drag-handle { + padding: 0 5px; + margin-left: -5px; + height: 36px; + } - .parameter-container[data-draggable] & { + .parameter-container.sortable-container & { margin: 4px 0 0 4px; padding: 3px 6px 6px; } &.parameter-dragged { box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15); - width: auto !important; } } @@ -53,20 +46,13 @@ .parameter-container { position: relative; - &[data-draggable] { + &.sortable-container { padding: 0 4px 4px 0; - transition: background-color 200ms ease-out; - transition-delay: 300ms; // short pause before returning to original bgcolor - } - - &[data-dragging] { - transition-delay: 0s; - background-color: #f6f8f9; } .parameter-apply-button { display: none; // default for mobile - + // "floating" on desktop @media (min-width: 768px) { position: absolute; @@ -83,7 +69,7 @@ display: block; pointer-events: none; // so tooltip doesn't remain after button hides } - + &[data-show="true"] { opacity: 1; display: block; @@ -118,7 +104,7 @@ line-height: 15px; background: #f77b74; border-radius: 7px; - box-shadow: 0px 0px 0 1px white, -1px 1px 0 1px #5d6f7d85; + box-shadow: 0 0 0 1px white, -1px 1px 0 1px #5d6f7d85; } } } diff --git a/client/app/components/sortable/index.jsx b/client/app/components/sortable/index.jsx new file mode 100644 index 0000000000..7365e64fae --- /dev/null +++ b/client/app/components/sortable/index.jsx @@ -0,0 +1,80 @@ +import { isFunction, wrap } from 'lodash'; +import React, { useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc'; + +import './style.less'; + +export const DragHandle = sortableHandle(({ className, ...restProps }) => ( +
+)); + +export const SortableContainerWrapper = sortableContainer(({ children }) => children); + +export const SortableElement = sortableElement(({ children }) => children); + +export function SortableContainer({ disabled, containerProps, children, ...wrapperProps }) { + const containerRef = useRef(); + const [isDragging, setIsDragging] = useState(false); + + wrapperProps = { ...wrapperProps }; + containerProps = { ...containerProps }; + + if (disabled) { + // Disabled state: + // - forbid drag'n'drop (and therefore no need to hook events + // - don't override anything on container element + wrapperProps.shouldCancelStart = () => true; + } else { + // Enabled state: + + // - use container element as a default helper element + wrapperProps.helperContainer = wrap(wrapperProps.helperContainer, helperContainer => ( + isFunction(helperContainer) ? + helperContainer(containerRef.current) : + containerRef.current + )); + + // - hook drag start/end events + wrapperProps.updateBeforeSortStart = wrap(wrapperProps.updateBeforeSortStart, (updateBeforeSortStart, ...args) => { + setIsDragging(true); + if (isFunction(updateBeforeSortStart)) { + updateBeforeSortStart(...args); + } + }); + wrapperProps.onSortEnd = wrap(wrapperProps.onSortEnd, (onSortEnd, ...args) => { + setIsDragging(false); + if (isFunction(onSortEnd)) { + onSortEnd(...args); + } + }); + + // - update container element: add classes and take a ref + containerProps.className = cx( + 'sortable-container', + { 'sortable-container-dragging': isDragging }, + containerProps.className, + ); + containerProps.ref = containerRef; + } + + // order of props matters - we override some of them + return ( + +
{children}
+
+ ); +} + +SortableContainer.propTypes = { + disabled: PropTypes.bool, + containerProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types + children: PropTypes.node, +}; + +SortableContainer.defaultProps = { + disabled: false, + containerProps: {}, + children: null, +}; diff --git a/client/app/components/sortable/style.less b/client/app/components/sortable/style.less new file mode 100644 index 0000000000..1277df8e9b --- /dev/null +++ b/client/app/components/sortable/style.less @@ -0,0 +1,30 @@ +.drag-handle { + vertical-align: bottom; + cursor: move; + + display: inline-flex; + align-items: stretch; + justify-content: center; + + &:before { + content: ''; + display: block; + width: 6px; + + background: + linear-gradient(90deg, transparent 0px, white 1px, white 2px) center, + linear-gradient(transparent 0px, white 1px, white 2px) center, + #111111; + background-size: 2px 2px; + } +} + +.sortable-container { + transition: background-color 200ms ease-out; + transition-delay: 300ms; // short pause before returning to original bgcolor + + &.sortable-container-dragging { + transition-delay: 0s; + background-color: #f6f8f9; + } +} diff --git a/package-lock.json b/package-lock.json index 7ebb282402..296687724a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15004,9 +15004,9 @@ } }, "react-sortable-hoc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.9.1.tgz", - "integrity": "sha512-2VeofjRav8+eZeE5Nm/+b8mrA94rQ+gBsqhXi8pRBSjOWNqslU3ZEm+0XhSlfoXJY2lkgHipfYAUuJbDtCixRg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.10.1.tgz", + "integrity": "sha512-eVyv5rrK6qY9bG60bboRY78In7OpdRRg+hxp4QMLIjC/UJaFSU7exTYd0764GtXvBqh+b+faYGzren5/ffRYKw==", "requires": { "@babel/runtime": "^7.2.0", "invariant": "^2.2.4", @@ -15014,9 +15014,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", - "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", + "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", "requires": { "regenerator-runtime": "^0.13.2" } diff --git a/package.json b/package.json index afd15ea52a..c351ca84d7 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "react-dom": "^16.8.3", "react-grid-layout": "git+https://github.com/getredash/react-grid-layout.git", "react-pivottable": "^0.9.0", - "react-sortable-hoc": "^1.9.1", + "react-sortable-hoc": "^1.10.1", "react2angular": "^3.2.1", "tinycolor2": "^1.4.1", "ui-select": "^0.19.8"