/*
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);