Skip to content

Commit

Permalink
NEW Adding drag and drop functionality to elemental
Browse files Browse the repository at this point in the history
  • Loading branch information
ScopeyNZ committed Oct 23, 2018
1 parent a1af5b8 commit 019a0e5
Show file tree
Hide file tree
Showing 17 changed files with 356 additions and 32 deletions.
2 changes: 2 additions & 0 deletions client/src/boot/registerComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Summary from 'components/ElementEditor/Summary';
import InlineEditForm from 'components/ElementEditor/InlineEditForm';
import AddElementPopover from 'components/ElementEditor/AddElementPopover';
import HoverBar from 'components/ElementEditor/HoverBar';
import DragPositionIndicator from 'components/ElementEditor/DragPositionIndicator';

export default () => {
Injector.component.registerMany({
Expand All @@ -26,5 +27,6 @@ export default () => {
ElementInlineEditForm: InlineEditForm,
AddElementPopover,
HoverBar,
DragPositionIndicator,
});
};
14 changes: 14 additions & 0 deletions client/src/components/ElementEditor/DragPositionIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { PureComponent } from 'react';

// eslint-disable-next-line react/prefer-stateless-function
class DragPositionIndicator extends PureComponent {
render() {
return (
<div className="elemental-editor-drag-indicator">
<div className="elemental-editor-drag-indicator__ball" />
</div>
);
}
}

export default DragPositionIndicator;
15 changes: 15 additions & 0 deletions client/src/components/ElementEditor/DragPositionIndicator.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.elemental-editor-drag-indicator {
height: 3px;
margin: -2px 0 -1px 0;
background-color: $info;

&__ball {
position: relative;
height: 7px;
width: 7px;
top: -2px;
left: -3px;
border-radius: 3.5px;
background-color: $info;
}
}
71 changes: 69 additions & 2 deletions client/src/components/ElementEditor/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,37 @@ import { connect } from 'react-redux';
import { loadElementFormStateName } from 'state/editor/loadElementFormStateName';
import { loadElementSchemaValue } from 'state/editor/loadElementSchemaValue';
import * as TabsActions from 'state/tabs/TabsActions';
import { DragSource, DropTarget } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';

const elementSource = {
beginDrag(props) {
const { element, onDragStart } = props;
if (onDragStart) {
onDragStart(element);
}
return element;
}
};

const elementTarget = {
drop(props) {
const { element, onDragDrop } = props;

if (onDragDrop) {
onDragDrop(element);
}
},

hover(props) {
const { element, onDragOver } = props;

if (onDragOver) {
onDragOver(element);
}
}
};


/**
* The Element component used in the context of an ElementEditor shows the summary
Expand All @@ -32,6 +63,19 @@ class Element extends Component {
};
}

componentDidMount() {
const { connectDragPreview } = this.props;
if (connectDragPreview) {
// Use empty image as a drag preview so browsers don't draw it
// and we can draw whatever we want on the custom drag layer instead.
connectDragPreview(getEmptyImage(), {
// IE fallback: specify that we'd rather screenshot the node
// when it already knows it's being dragged so we can hide it with CSS.
captureDraggingState: true,
});
}
}

/**
* Returns the applicable versioned state class names for the element
*
Expand Down Expand Up @@ -157,6 +201,10 @@ class Element extends Component {
link,
editTabs,
activeTab,
connectDragSource,
connectDropTarget,
isDragging,
isOver,
} = this.props;

const { previewExpanded } = this.state;
Expand All @@ -174,11 +222,13 @@ class Element extends Component {
'element-editor__element',
{
'element-editor__element--expandable': element.InlineEditable,
'element-editor__element--dragging': isDragging,
'element-editor__element--dragged-over': isOver,
},
this.getVersionedStateClassName()
);

return (
return connectDropTarget(connectDragSource(
<div
className={elementClassNames}
onClick={this.handleExpand}
Expand Down Expand Up @@ -214,7 +264,7 @@ class Element extends Component {
handleLoadingError={this.handleLoadingError}
/>
</div>
);
));
}
}

Expand Down Expand Up @@ -269,6 +319,14 @@ Element.propTypes = {
activeTab: PropTypes.string,
tabSetName: PropTypes.string,
onActivateTab: PropTypes.func,
connectDragSource: PropTypes.func.isRequired,
connectDragPreview: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
isOver: PropTypes.bool.isRequired,
onDragOver: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
onDragDrop: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
onDragStart: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
};

Element.defaultProps = {
Expand All @@ -278,6 +336,15 @@ Element.defaultProps = {
export { Element as Component };

export default compose(
DropTarget('element', elementTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
})),
DragSource('element', elementSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
})),
connect(mapStateToProps, mapDispatchToProps),
inject(
['ElementHeader', 'ElementContent'],
Expand Down
12 changes: 12 additions & 0 deletions client/src/components/ElementEditor/Element.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@
outline-width: 0;
}

&:hover {
.element-editor-header__drag-handle {
display: block;
}
}

&:last-child {
border-bottom: 0;
}

&--dragging {
opacity: 0.3;
cursor: grabbing;
cursor: -webkit-grabbing;
}
}
61 changes: 61 additions & 0 deletions client/src/components/ElementEditor/ElementDragPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { Component, PropTypes } from 'react';
import Header from 'components/ElementEditor/Header';
import { DragLayer } from 'react-dnd';
import { elementType } from 'types/elementType';

// eslint-disable-next-line react/prefer-stateless-function
class ElementDragPreview extends Component {
render() {
const { isDragging, element, currentOffset } = this.props;

if (!isDragging || !currentOffset) {
return null;
}

const { x, y } = currentOffset;

const thing = element || {
ID: 2,
Title: 'Something blah',
Version: 5,
IsLiveVersion: true,
IsPublished: true,
BlockSchema: { iconClass: 'font-icon-block-form' },
};

const transform = `translate(${x}px, ${y}px)`;
const style = {
transform,
WebkitTransform: transform,
};

return (
<div className="element-editor-drag-preview" style={style}>
<Header
id={thing.ID}
title={thing.Title}
version={thing.Version}
isLiveVersion={thing.IsLiveVersion}
isPublished={thing.IsPublished}
fontIcon={thing.BlockSchema.iconClass}
simple
/>
</div>
);
}
}

ElementDragPreview.propTypes = {
element: elementType,
isDragging: PropTypes.bool,
currentOffset: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}),
};

export default DragLayer(monitor => ({
element: monitor.getItem(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
}))(ElementDragPreview);
11 changes: 11 additions & 0 deletions client/src/components/ElementEditor/ElementDragPreview.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.element-editor-drag-preview {
top: 0;
left: 0;
position: fixed;
pointer-events: none;
z-index: 100; // Higher than CMS tree view
background-color: $white;
border: 1px solid $border-color;
padding: $spacer-sm $panel-padding-x;
box-shadow: $z-depth-1;
}
59 changes: 58 additions & 1 deletion client/src/components/ElementEditor/ElementEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,55 @@ import { elementTypeType } from 'types/elementTypeType';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { loadElementFormStateName } from 'state/editor/loadElementFormStateName';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import classnames from 'classnames';

import ElementDragPreview from 'components/ElementEditor/ElementDragPreview';

/**
* The ElementEditor is used in the CMS to manage a list or nested lists of
* elements for a page or other DataObject.
*/
class ElementEditor extends PureComponent {
constructor(props) {
super(props);

this.state = {
dragTargetElementId: false,
isDragging: false,
draggedElement: null,
};

this.handleDragOver = this.handleDragOver.bind(this);
this.handleDragDrop = this.handleDragDrop.bind(this);
this.handleDragStart = this.handleDragStart.bind(this);
}

handleDragStart(element) {
this.setState({
draggedElement: element,
});
}

handleDragOver(element) {
const id = element ? element.ID : false;

if (this.state.dragTargetElementId !== id) {
this.setState({
dragTargetElementId: id,
isDragging: true,
});
}
}

handleDragDrop() {
this.setState({
dragTargetElementId: false,
isDragging: false,
});
}

render() {
const {
fieldName,
Expand All @@ -20,18 +63,31 @@ class ElementEditor extends PureComponent {
elementalAreaId,
elementTypes,
} = this.props;
const { dragTargetElementId, isDragging } = this.state;

const classNames = classnames('element-editor', {
'element-editor--dragging': isDragging,
});

return (
<div className="element-editor">
<div className={classNames}>
<ToolbarComponent
elementTypes={elementTypes}
elementalAreaId={elementalAreaId}
onDragOver={this.handleDragOver}
onDragDrop={this.handleDragDrop}
/>
<ListComponent
elementTypes={elementTypes}
pageId={pageId}
elementalAreaId={elementalAreaId}
onDragOver={this.handleDragOver}
onDragDrop={this.handleDragDrop}
onDragStart={this.handleDragStart}
dragTargetElementId={dragTargetElementId}
isDragging={isDragging}
/>
<ElementDragPreview />
<input name={fieldName} type="hidden" value={JSON.stringify(formState)} />
</div>
);
Expand Down Expand Up @@ -67,6 +123,7 @@ function mapStateToProps(state) {

export { ElementEditor as Component };
export default compose(
DragDropContext(HTML5Backend),
connect(mapStateToProps),
inject(
['ElementToolbar', 'ElementList'],
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/ElementEditor/ElementEditor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.element-editor {
&--dragging {
cursor: grabbing;
}
}
Loading

0 comments on commit 019a0e5

Please sign in to comment.