/* The MIT License Copyright (c) 2017-2019 EclipseSource Munich https://github.com/eclipsesource/jsonforms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // tslint:disable:jsx-no-multiline-js import head from 'lodash/head'; import isEmpty from 'lodash/isEmpty'; import React from 'react'; import { connect } from 'react-redux'; import { getData, JsonFormsState, Paths, resolveData } from '@jsonforms/core'; import ObjectListItem from './ObjectListItem'; const { DropTarget } = require('react-dnd'); import { canDropDraggedItem, CSS_DELAY, DragInfo, DropResult, mapDispatchToTreeListProps, Types } from './dnd.util'; import { Property } from '../services/property.util'; import { matchContainerProperty } from '../helpers/container.util'; import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles'; import { compose } from 'recompose'; export interface ExpandArrayProps { rootData: any; containmentProps?: Property[]; path: string; selection: any; handlers: any; filterPredicate: any; labelProvider: any; imageProvider: any; } /** * Expands the given data array by expanding every element. * If the parent data containing the array is provided, * a suitable delete function for the expanded elements is created. * * @param data the array to expand * @param {@link Property} property It describes a single property. * It is used to match a given data element with a schema by searching * a list of properties. * * @param parentPath the instance path where data can be obtained from */ export const ExpandArray = ( { rootData, containmentProps, path, selection, handlers, filterPredicate, labelProvider, imageProvider }: ExpandArrayProps ) => { const data = resolveData(rootData, path); if (data === undefined || data === null) { // return 'No data'; return ''; } return ( data.map((element: any, index: number) => { const composedPath = Paths.compose(path, index.toString()); const property = matchContainerProperty(element, containmentProps, filterPredicate); if (property === undefined) { return
  • No ContainmentProperty
  • ; } return ( ); }) ); }; export interface ExpandArrayContainerProps extends ExpandArrayProps { connectDropTarget?: any; /** True if drag and drop is currently in progress. */ isDragging?: boolean; /** The dragged element is over this expanded array excluding nested arrays. */ isOver?: boolean; /** Whether this list is a valid drop target for the currently dragged element. */ validDropTarget?: boolean; classes?: any; moveListItem(data: any, oldPath: string, newPath: string): boolean; } export interface ExandArrayContainerState { /** * Defines whether CSS to highlight the list being a valid or invalid drop target should be shown. * This is necessary to prevent a Chrome bug that may cancel drag and drop prematurely * if the DOM is changed during the 'begin drag' event of HTML5. */ setCss: boolean; } const styles = createStyles({ currentTarget: { borderWidth: 'medium' }, validTarget: { borderStyle: 'dashed', borderWidth: 'thin', borderColor: 'rgb(88, 199, 23)', minHeight: '1em' }, invalidTarget: { borderStyle: 'dashed', borderWidth: 'thin', borderColor: 'rgb(189, 0, 0)' } }); class ExpandArrayContainer extends React.Component, any> { constructor(props: ExpandArrayContainerProps & WithStyles<'currentTarget' | 'validTarget' | 'invalidTarget'>) { super(props); this.state = { setCss: false }; } componentWillReceiveProps(nextProps: ExpandArrayContainerProps) { if (this.props.isDragging && !nextProps.isDragging) { this.setState({ setCss: false }); } else if (!this.props.isDragging && nextProps.isDragging) { setTimeout(() => this.setState({ setCss: true }), CSS_DELAY); } } render() { const { rootData, containmentProps, path, selection, handlers, // Drag and Drop Parameters connectDropTarget, isOver, validDropTarget, filterPredicate, labelProvider, imageProvider, classes }: ExpandArrayContainerProps = this.props; if (isEmpty(containmentProps)) { return undefined; } let className = ''; // Only apply D&D CSS if the flag has been set if (this.state.setCss) { if (validDropTarget) { className = classes.validTarget; if (isOver) { className = `${className} ${classes.currentTarget}`; } } else if (isOver) { className = `${classes.invalidTarget} ${classes.currentTarget}`; } } return connectDropTarget( ); } } const mapStateToProps = (state: JsonFormsState) => ({ rootData: getData(state) }); /** * Injects drag and drop related properties into an expanded array */ // TODO: typings const collect = (dndConnect: any, monitor: any) => { return { connectDropTarget: dndConnect.dropTarget(), isOver: monitor.isOver({ shallow: true }), validDropTarget: monitor.canDrop(), isDragging: monitor.getItem() !== null }; }; /** * The drop target contract for an expanded array. * The specified methods are executed by React DnD when appropriate * (e.g. a draggable component hovers over an expanded array). */ const arrayDropTarget = { /** * Tests wether the currently dragged object list item can be dropped in this list * by checking whether the item's schema id matches with a containment property of this list. */ canDrop: (props: ExpandArrayContainerProps, monitor: any) => { return canDropDraggedItem(props.containmentProps, monitor.getItem() as DragInfo); }, /** * Called when an item was dropped at a valid location (canDrop() === true) * The calls also go up the chain when the drop occurred in a nested component. * The most nested one is called first, return results are available * from the before called component. */ drop: (props: any, monitor: any) => { // drop was handled by a nested list if (monitor.didDrop()) { return monitor.getDropResult(); } const dropInfo: DropResult = { isHandled: true, move: true, moveTarget: Paths.compose(props.path, '0') }; return dropInfo; } }; const DnDExandArrayContainer = compose( withStyles(styles, { name: 'ExpandArrayContainer' }), DropTarget(Types.TREE_DND, arrayDropTarget, collect) )(ExpandArrayContainer); export default connect( mapStateToProps, mapDispatchToTreeListProps )(DnDExandArrayContainer);