UNPKG

@knapsack/app

Version:

Build Design Systems on top of knapsack, by Basalt

249 lines (228 loc) • 7.69 kB
/** * Copyright (C) 2018 Basalt This file is part of Knapsack. Knapsack is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Knapsack is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Knapsack; if not, see <https://www.gnu.org/licenses>. */ import React from 'react'; import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types'; import { DragSource, DropTarget } from 'react-dnd'; import { FaChevronDown, FaChevronUp, FaTrashAlt, FaEdit, FaArrowsAlt, } from 'react-icons/fa'; import Template from '../../components/template'; import { DragTypes } from './DragTypes'; import './page-builder-slice.scss'; const PageBuilderSlice = ({ moveUp, moveDown, deleteMe, showEditForm, isBeingEdited, isFirst, isLast, templateId, patternId, data, hasVisibleControls, isChanged, connectDragSource, connectDropTarget, }) => connectDragSource( connectDropTarget( <div> <div className={`page-builder-slice ${ isChanged ? 'page-builder-slice--changed' : '' }`} style={{ border: isBeingEdited && hasVisibleControls ? 'solid 1px var(--c-active)' : 'none', }} > <div className={`page-builder-slice__icon-wrapper ei-content-block__button-tray ${ isBeingEdited ? 'page-builder-slice__icon-wrapper--active' : '' }`} style={{ display: hasVisibleControls ? 'block' : 'none', }} > <div className={`page-builder-slice__icon ${ isFirst ? 'page-builder-slice__icon--disabled' : '' }`} onKeyPress={() => !isFirst && moveUp()} onClick={() => !isFirst && moveUp()} role="button" aria-label="move item up" tabIndex="0" > <FaChevronUp fill={isFirst ? 'lightgrey' : 'black'} /> </div> <div className={`page-builder-slice__icon ${ isLast ? 'page-builder-slice__icon--disabled' : '' }`} onKeyPress={() => !isLast && moveDown()} onClick={() => !isLast && moveDown()} role="button" aria-label="move item down" tabIndex="0" > <FaChevronDown fill={isLast ? 'lightgrey' : 'black'} /> </div> <div className="ks-page-builder-slice__icon" onKeyPress={showEditForm} onClick={showEditForm} role="button" aria-label="being editing" tabIndex="0" > <FaEdit /> </div> <div className="ks-page-builder-slice__icon" onKeyPress={deleteMe} onClick={deleteMe} role="button" aria-label="delete component" tabIndex="0" > <FaTrashAlt /> </div> <div className="ks-page-builder-slice__icon"> <FaArrowsAlt /> </div> </div> <div style={{ flexGrow: 1 }}> <Template templateId={templateId} patternId={patternId} data={data} isResizable={false} /> </div> </div> </div>, ), ); PageBuilderSlice.defaultProps = { data: {}, }; PageBuilderSlice.propTypes = { template: PropTypes.string.isRequired, data: PropTypes.object, // eslint-disable-line react/no-unused-prop-types showEditForm: PropTypes.func.isRequired, deleteMe: PropTypes.func.isRequired, moveUp: PropTypes.func.isRequired, moveDown: PropTypes.func.isRequired, isBeingEdited: PropTypes.bool.isRequired, isFirst: PropTypes.bool.isRequired, isLast: PropTypes.bool.isRequired, hasVisibleControls: PropTypes.bool.isRequired, id: PropTypes.string.isRequired, index: PropTypes.number.isRequired, isChanged: PropTypes.bool.isRequired, }; const sliceSource = { beginDrag(props) { const x = { id: props.id, index: props.index, templateName: props.template, hasVisibleControls: props.hasVisibleControls, }; console.log('beginDrag', x); return x; }, endDrag(props, monitor) { const didDrop = monitor.didDrop(); const dropResult = monitor.getDropResult(); console.log('endDrag', { didDrop, dropResult }); }, // canDrag(props, monitor) { // return true; // }, }; const sliceTarget = { hover(props, monitor, component) { if (!component) { return null; } const dragIndex = monitor.getItem().index; const hoverIndex = props.index; // Don't replace items with themselves if (dragIndex === hoverIndex) { return; } // Determine rectangle on screen // eslint-disable-next-line const hoverBoundingRect = findDOMNode(component).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 console.log('moveSlice()', { dragIndex, hoverIndex }); props.moveSlice(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; }, }; // Sending our `PageBuilderSlice` component through two Higher Order Components: // HOC 1: Makes Draggable - http://react-dnd.github.io/react-dnd/docs-drag-source.html const DraggablePlaygroundSlice = DragSource( DragTypes.SLICE, sliceSource, // what this returns is passed into Component as `props` (connect, monitor) => ({ connectDragSource: connect.dragSource(), // used to wrap an element to make draggable isDragging: monitor.isDragging(), }), )(PageBuilderSlice); // <- HOC takes in a component here // HOC 2: Makes Droppable - http://react-dnd.github.io/react-dnd/docs-drop-target.html const DroppableDraggablePlaygroundSlice = DropTarget( DragTypes.SLICE, sliceTarget, // what this returns is passed into Component as `props` connect => ({ connectDropTarget: connect.dropTarget(), // used to wrap an element to make droppable }), )(DraggablePlaygroundSlice); // <- HOC takes in a component here // Now it's Draggable & Droppable (i.e. re-arrangable) export default DroppableDraggablePlaygroundSlice;