fix(ui): Update draggable components to ensure single instance of drag and drop context

pull/13290/head
Iris Scholten 2019-04-10 11:22:18 -07:00
parent a8c3fea540
commit 2537b0e2bf
5 changed files with 8 additions and 239 deletions

View File

@ -1,188 +0,0 @@
import React, {Component, ChangeEvent} from 'react'
import {findDOMNode} from 'react-dom'
import {
DragSourceSpec,
DropTargetConnector,
DragSourceMonitor,
DragSource,
DropTarget,
DragSourceConnector,
ConnectDragSource,
ConnectDropTarget,
ConnectDragPreview,
} from 'react-dnd'
import {ErrorHandling} from 'src/shared/decorators/errors'
const fieldType = 'field'
interface RenamableField {
internalName: string
displayName: string
visible: boolean
}
interface Props {
internalName: string
displayName: string
visible: boolean
index: number
id: string
key: string
onFieldUpdate: (field: RenamableField) => void
isDragging?: boolean
connectDragSource?: ConnectDragSource
connectDropTarget?: ConnectDropTarget
connectDragPreview?: ConnectDragPreview
moveField: (dragIndex: number, hoverIndex: number) => void
}
const fieldSource: DragSourceSpec<Props> = {
beginDrag(props) {
return {
id: props.id,
index: props.index,
}
},
}
const fieldTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index
const hoverIndex = props.index
const element = findDOMNode(component) as Element
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
// Determine rectangle on screen
const hoverBoundingRect = element.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.y - hoverBoundingRect.top
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// Time to actually perform the action
props.moveField(dragIndex, hoverIndex)
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex
},
}
function MyDropTarget(dropv1, dropv2, dropfunc1) {
return target => DropTarget(dropv1, dropv2, dropfunc1)(target) as any
}
function MyDragSource(dragv1, dragv2, dragfunc1) {
return target => DragSource(dragv1, dragv2, dragfunc1)(target) as any
}
@ErrorHandling
@MyDropTarget(fieldType, fieldTarget, (connect: DropTargetConnector) => ({
connectDropTarget: connect.dropTarget(),
}))
@MyDragSource(
fieldType,
fieldSource,
(connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
})
)
export default class GraphOptionsCustomizableField extends Component<Props> {
constructor(props) {
super(props)
this.handleFieldRename = this.handleFieldRename.bind(this)
this.handleToggleVisible = this.handleToggleVisible.bind(this)
}
public render(): JSX.Element | null {
const {
internalName,
displayName,
isDragging,
connectDragSource,
connectDragPreview,
connectDropTarget,
visible,
} = this.props
const fieldClass = `customizable-field${isDragging ? ' dragging' : ''}`
const labelClass = `${
visible
? 'customizable-field--label'
: 'customizable-field--label__hidden'
}`
const visibilityTitle = visible
? `Click to HIDE ${internalName}`
: `Click to SHOW ${internalName}`
return connectDragPreview(
connectDropTarget(
<div className={fieldClass}>
<div className={labelClass}>
{connectDragSource(
<div className="customizable-field--drag">
<span className="hamburger" />
</div>
)}
<div
className="customizable-field--visibility"
onClick={this.handleToggleVisible}
title={visibilityTitle}
>
<span className={visible ? 'icon eye-open' : 'icon eye-closed'} />
</div>
<div className="customizable-field--name">{internalName}</div>
</div>
<input
className="form-control input-sm customizable-field--input"
type="text"
spellCheck={false}
id="internalName"
value={displayName}
data-testid="custom-time-format"
onChange={this.handleFieldRename}
placeholder={`Rename ${internalName}`}
disabled={!visible}
/>
</div>
)
)
}
private handleFieldRename(e: ChangeEvent<HTMLInputElement>) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: e.target.value, visible})
}
private handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
}

View File

@ -1,45 +0,0 @@
import React, {PureComponent} from 'react'
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import GraphOptionsCustomizableField from 'src/dashboards/components/GraphOptionsCustomizableField'
interface RenamableField {
internalName: string
displayName: string
visible: boolean
}
interface Props {
fields: RenamableField[]
onFieldUpdate: (field: RenamableField) => void
moveField: (dragIndex: number, hoverIndex: number) => void
}
class GraphOptionsCustomizeFields extends PureComponent<Props> {
public render() {
const {fields, onFieldUpdate, moveField} = this.props
return (
<div className="graph-options-group">
<label className="form-label">Customize Fields</label>
<div>
{fields.map((field, i) => (
<GraphOptionsCustomizableField
key={field.internalName}
index={i}
id={field.internalName}
internalName={field.internalName}
displayName={field.displayName}
visible={field.visible}
onFieldUpdate={onFieldUpdate}
moveField={moveField}
/>
))}
</div>
</div>
)
}
}
export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields)

View File

@ -2,8 +2,6 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {isEmpty} from 'lodash'
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import classnames from 'classnames'
// Components
@ -35,6 +33,7 @@ import {ComponentSize} from '@influxdata/clockface'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {RemoteDataState} from 'src/types'
import DraggableDropdown from 'src/dashboards/components/variablesControlBar/DraggableDropdown'
import withDragDropContext from 'src/shared/decorators/withDragDropContext'
interface OwnProps {
dashboardID: string
@ -160,7 +159,7 @@ const mstp = (state: AppState, props: OwnProps): StateProps => {
return {variables, valuesStatus, variablesStatus, inPresentationMode}
}
export default DragDropContext(HTML5Backend)(
export default withDragDropContext(
connect<StateProps, DispatchProps, OwnProps>(
mstp,
mdtp

View File

@ -1,7 +1,5 @@
// Libraries
import React, {Component} from 'react'
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
// Components
import {Form, EmptyState, Grid} from '@influxdata/clockface'
@ -10,6 +8,7 @@ import DraggableColumn from 'src/shared/components/draggable_column/DraggableCol
// Types
import {FieldOption} from 'src/types/dashboards'
import {ComponentSize} from '@influxdata/clockface'
import withDragDropContext from 'src/shared/decorators/withDragDropContext'
interface Props {
className?: string
@ -59,4 +58,4 @@ class ColumnsOptions extends Component<Props> {
}
}
export default DragDropContext(HTML5Backend)(ColumnsOptions)
export default withDragDropContext(ColumnsOptions)

View File

@ -0,0 +1,4 @@
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
export default DragDropContext(HTML5Backend)