This is snapshot of custom node renderer for react-sortable-tree.
It likely won't be needed however.
This commit is contained in:
parent
6a7dab52eb
commit
baf9f61465
8 changed files with 715 additions and 193 deletions
|
@ -30,6 +30,7 @@
|
||||||
"react-dom": "^15.6.1",
|
"react-dom": "^15.6.1",
|
||||||
"react-i18next": "^4.6.1",
|
"react-i18next": "^4.6.1",
|
||||||
"react-router-dom": "^4.1.1",
|
"react-router-dom": "^4.1.1",
|
||||||
|
"react-sortable-tree": "^1.2.0",
|
||||||
"slugify": "^1.1.0",
|
"slugify": "^1.1.0",
|
||||||
"url-parse": "^1.1.9"
|
"url-parse": "^1.1.9"
|
||||||
},
|
},
|
||||||
|
@ -43,6 +44,8 @@
|
||||||
"babel-preset-stage-1": "^6.24.1",
|
"babel-preset-stage-1": "^6.24.1",
|
||||||
"css-loader": "^0.28.4",
|
"css-loader": "^0.28.4",
|
||||||
"i18next-conv": "^3.0.3",
|
"i18next-conv": "^3.0.3",
|
||||||
|
"node-sass": "^4.5.3",
|
||||||
|
"sass-loader": "^6.0.6",
|
||||||
"style-loader": "^0.18.2",
|
"style-loader": "^0.18.2",
|
||||||
"url-loader": "^0.5.9",
|
"url-loader": "^0.5.9",
|
||||||
"webpack": "^2.6.1"
|
"webpack": "^2.6.1"
|
||||||
|
|
|
@ -1,20 +1,3 @@
|
||||||
.mt-treetable-container .fancytree-container span.fancytree-node.mt-tree-end-drop {
|
|
||||||
height: 10px;
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: -10px;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container .fancytree-container span.fancytree-node.mt-tree-end-drop span.fancytree-title {
|
|
||||||
width: 15px;
|
|
||||||
margin-left: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container .fancytree-container span.fancytree-node.mt-tree-end-drop.mt-tree-end-drop-wide span.fancytree-title {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container .fancytree-container {
|
.mt-treetable-container .fancytree-container {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
@ -71,31 +54,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mt-treetable-container span.fancytree-node.fancytree-drag-source {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container #fancytree-drop-marker {
|
|
||||||
background-image: url("../../public/fancytree/skin-bootstrap/icons.gif");
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container #fancytree-drop-marker.fancytree-drop-over {
|
|
||||||
background-position: 0px -130px;
|
|
||||||
width: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container #fancytree-drop-marker.fancytree-drop-after, .mt-treetable-container #fancytree-drop-marker.fancytree-drop-before {
|
|
||||||
background-position: 0px -145px;
|
|
||||||
width: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-treetable-container span.fancytree-node.fancytree-drop-accept fancytree-expander {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.form-group .mt-treetable-container {
|
.form-group .mt-treetable-container {
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
@ -71,7 +71,6 @@ class TreeTable extends Component {
|
||||||
withHeader: PropTypes.bool,
|
withHeader: PropTypes.bool,
|
||||||
withDescription: PropTypes.bool,
|
withDescription: PropTypes.bool,
|
||||||
noTable: PropTypes.bool,
|
noTable: PropTypes.bool,
|
||||||
withDnd: PropTypes.bool,
|
|
||||||
withIcons: PropTypes.bool
|
withIcons: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,33 +175,6 @@ class TreeTable extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.withDnd) {
|
|
||||||
treeOpts.extensions.push('dnd');
|
|
||||||
treeOpts.dnd = {
|
|
||||||
autoExpandMS: 400,
|
|
||||||
focusOnClick: true,
|
|
||||||
preventVoidMoves: true,
|
|
||||||
preventRecursiveMoves: true,
|
|
||||||
dropMarkerOffsetX: -46, // -22
|
|
||||||
dropMarkerInsertOffsetX: 0,
|
|
||||||
dragStart: (node, data) => {
|
|
||||||
return node.key !== '__mt-tree-end-drop__';
|
|
||||||
},
|
|
||||||
dragEnter: (node, data) => {
|
|
||||||
if (node.folder) {
|
|
||||||
return ['before', 'over'];
|
|
||||||
} else {
|
|
||||||
return ['before'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dragDrop: (node, data) => {
|
|
||||||
console.log(node);
|
|
||||||
console.log(data);
|
|
||||||
data.otherNode.moveTo(node, data.hitMode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tree = jQuery(this.domTable).fancytree(treeOpts).fancytree("getTree");
|
this.tree = jQuery(this.domTable).fancytree(treeOpts).fancytree("getTree");
|
||||||
|
|
||||||
this.updateSelection();
|
this.updateSelection();
|
||||||
|
|
|
@ -10,7 +10,9 @@ import {
|
||||||
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
|
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
|
||||||
import {DeleteModalDialog} from "../../lib/delete";
|
import {DeleteModalDialog} from "../../lib/delete";
|
||||||
import interoperableErrors from '../../../../shared/interoperable-errors';
|
import interoperableErrors from '../../../../shared/interoperable-errors';
|
||||||
import {TreeSelectMode, TreeTable} from "../../lib/tree";
|
|
||||||
|
import SortableTree from 'react-sortable-tree';
|
||||||
|
import { getRuleTreeNodeRenderer } from './RuleTreeNodeRenderer';
|
||||||
|
|
||||||
@translate()
|
@translate()
|
||||||
@withForm
|
@withForm
|
||||||
|
@ -21,7 +23,110 @@ export default class CUD extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {};
|
this.compoundRuleTypes = [ 'all', 'some', 'none' ];
|
||||||
|
|
||||||
|
const allRule = {
|
||||||
|
type: 'all'
|
||||||
|
};
|
||||||
|
|
||||||
|
const otherRule = {
|
||||||
|
type: 'eq'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: 'a',
|
||||||
|
title: 'A',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'aa',
|
||||||
|
title: 'AA',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'aaa',
|
||||||
|
title:
|
||||||
|
<div><h4>sdfsdf</h4><div>xxx</div><div>yyy<NavButton label="ZZZ" linkTo="/lists"/></div></div>,
|
||||||
|
rule: otherRule
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'aab',
|
||||||
|
title: 'AAB',
|
||||||
|
subtitle: 'sdfwere',
|
||||||
|
rule: otherRule
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ab',
|
||||||
|
title: 'AB',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'aba',
|
||||||
|
title: 'ABA',
|
||||||
|
rule: otherRule
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'abb',
|
||||||
|
title: 'ABB',
|
||||||
|
rule: otherRule
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'b',
|
||||||
|
title: 'B',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'ba',
|
||||||
|
title: 'BA',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'baa',
|
||||||
|
title: 'BAA',
|
||||||
|
rule: otherRule
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bab',
|
||||||
|
title: 'BAB',
|
||||||
|
rule: otherRule
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bb',
|
||||||
|
title: 'BB',
|
||||||
|
expanded: true,
|
||||||
|
rule: allRule,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'bba',
|
||||||
|
title: 'BBA',
|
||||||
|
rule: otherRule
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bbb',
|
||||||
|
title: 'BBB',
|
||||||
|
rule: otherRule
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
this.initForm();
|
this.initForm();
|
||||||
}
|
}
|
||||||
|
@ -100,9 +205,9 @@ export default class CUD extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRuleSelectionChangedAsync(sel) {
|
onRuleSelectionClick(data) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedRule: sel
|
selectedRule: data.node.key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,119 +215,6 @@ export default class CUD extends Component {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
const isEdit = !!this.props.entity;
|
const isEdit = !!this.props.entity;
|
||||||
|
|
||||||
const treeEnd = {
|
|
||||||
key: '__mt-tree-end-drop__',
|
|
||||||
icon: false,
|
|
||||||
unselectable: true,
|
|
||||||
extraClasses: 'mt-tree-end-drop',
|
|
||||||
beforeActivate: () => false
|
|
||||||
};
|
|
||||||
|
|
||||||
const treeEndWide = { // This one is used after a non-folder sibling that has no children
|
|
||||||
key: '__mt-tree-end-drop__',
|
|
||||||
icon: false,
|
|
||||||
unselectable: true,
|
|
||||||
extraClasses: 'mt-tree-end-drop mt-tree-end-drop-wide',
|
|
||||||
beforeActivate: () => false
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleTreeData = [
|
|
||||||
{
|
|
||||||
key: 'a',
|
|
||||||
title: 'A',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'aa',
|
|
||||||
title: 'AA',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'aaa',
|
|
||||||
title: 'AAA',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'aab',
|
|
||||||
title: 'AAB',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'aab',
|
|
||||||
title: 'AAB',
|
|
||||||
folder: true
|
|
||||||
},
|
|
||||||
treeEnd
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ab',
|
|
||||||
title: 'AB',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'aba',
|
|
||||||
title: 'ABA'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'abb',
|
|
||||||
title: 'ABB'
|
|
||||||
},
|
|
||||||
treeEndWide
|
|
||||||
]
|
|
||||||
},
|
|
||||||
treeEnd
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'b',
|
|
||||||
title: 'B',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'ba',
|
|
||||||
title: 'BA',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'baa',
|
|
||||||
title: 'BAA'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'bab',
|
|
||||||
title: 'BAB'
|
|
||||||
},
|
|
||||||
treeEndWide
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'bb',
|
|
||||||
title: 'BB',
|
|
||||||
expanded: true,
|
|
||||||
folder: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'bba',
|
|
||||||
title: 'BBA'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'bbb',
|
|
||||||
title: 'BBB'
|
|
||||||
},
|
|
||||||
treeEndWide
|
|
||||||
]
|
|
||||||
},
|
|
||||||
treeEnd
|
|
||||||
]
|
|
||||||
},
|
|
||||||
treeEnd
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -253,10 +245,19 @@ export default class CUD extends Component {
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-6">
|
<div className="col-md-6" >
|
||||||
<TreeTable data={sampleTreeData} noTable withIcons withDnd format="wide" selectMode={TreeSelectMode.SINGLE} selection={this.state.selectedRule} onSelectionChangedAsync={::this.onRuleSelectionChangedAsync} />
|
<SortableTree
|
||||||
|
treeData={this.state.rules}
|
||||||
|
onChange={ rules => this.setState({rules}) }
|
||||||
|
isVirtualized={false}
|
||||||
|
nodeContentRenderer={getRuleTreeNodeRenderer({
|
||||||
|
onClick: ::this.onRuleSelectionClick,
|
||||||
|
isSelected: data => data.node.key === this.state.selectedRule
|
||||||
|
})}
|
||||||
|
canDrop={data => data.nextParent && this.compoundRuleTypes.includes(data.nextParent.rule.type)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-md-6">
|
||||||
<h3>{t('Selected Rule Options')}</h3>
|
<h3>{t('Selected Rule Options')}</h3>
|
||||||
<InputField id="name" label={t('Name')} format="wide" />
|
<InputField id="name" label={t('Name')} format="wide" />
|
||||||
</div>
|
</div>
|
||||||
|
|
252
client/src/lists/segments/RuleTreeNodeRenderer.js
Normal file
252
client/src/lists/segments/RuleTreeNodeRenderer.js
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
"use strict";
|
||||||
|
// Taken and adapted from https://github.com/fritz-c/react-sortable-tree/blob/cbca55b9c9800a114fa2749866fd057fc1eaeb9c/src/node-renderer-default.js
|
||||||
|
// It adds the onClick listener to .rowContents and wraps the whole class to a function to allow parameterization by the onClickHandler
|
||||||
|
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import baseStyles from './rule-tree.scss';
|
||||||
|
|
||||||
|
function getIEVersion() {
|
||||||
|
const match = navigator.userAgent.match(/(?:MSIE |Trident\/.*; rv:)(\d+)/);
|
||||||
|
return match ? parseInt(match[1], 10) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDescendant(older, younger) {
|
||||||
|
return (
|
||||||
|
!!older.children &&
|
||||||
|
typeof older.children !== 'function' &&
|
||||||
|
older.children.some(
|
||||||
|
child => child === younger || isDescendant(child, younger)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let styles = baseStyles;
|
||||||
|
// Add extra classes in browsers that don't support flex
|
||||||
|
if (getIEVersion < 10) {
|
||||||
|
styles = {
|
||||||
|
...baseStyles,
|
||||||
|
row: `${styles.row} ${styles.row_NoFlex}`,
|
||||||
|
rowContents: `${styles.rowContents} ${styles.rowContents_NoFlex}`,
|
||||||
|
rowLabel: `${styles.rowLabel} ${styles.rowLabel_NoFlex}`,
|
||||||
|
rowToolbar: `${styles.rowToolbar} ${styles.rowToolbar_NoFlex}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRuleTreeNodeRenderer(options) {
|
||||||
|
class RuleTreeNodeRenderer extends Component {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
scaffoldBlockPxWidth,
|
||||||
|
toggleChildrenVisibility,
|
||||||
|
connectDragPreview,
|
||||||
|
connectDragSource,
|
||||||
|
isDragging,
|
||||||
|
canDrop,
|
||||||
|
canDrag,
|
||||||
|
node,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
draggedNode,
|
||||||
|
path,
|
||||||
|
treeIndex,
|
||||||
|
isSearchMatch,
|
||||||
|
isSearchFocus,
|
||||||
|
buttons,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
didDrop,
|
||||||
|
isOver, // Not needed, but preserved for other renderers
|
||||||
|
parentNode, // Needed for dndManager
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
const nodeTitle = title || node.title;
|
||||||
|
const nodeSubtitle = subtitle || node.subtitle;
|
||||||
|
|
||||||
|
let handle;
|
||||||
|
if (canDrag) {
|
||||||
|
if (typeof node.children === 'function' && node.expanded) {
|
||||||
|
// Show a loading symbol on the handle when the children are expanded
|
||||||
|
// and yet still defined by a function (a callback to fetch the children)
|
||||||
|
handle = (
|
||||||
|
<div className={styles.loadingHandle}>
|
||||||
|
<div className={styles.loadingCircle}>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
<div className={styles.loadingCirclePoint}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Show the handle used to initiate a drag-and-drop
|
||||||
|
handle = connectDragSource(<div className={styles.moveHandle}/>, {
|
||||||
|
dropEffect: 'copy',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDraggedDescendant = draggedNode && isDescendant(draggedNode, node);
|
||||||
|
const isLandingPadActive = !didDrop && isDragging;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: '100%'}} {...otherProps}>
|
||||||
|
{toggleChildrenVisibility &&
|
||||||
|
node.children &&
|
||||||
|
node.children.length > 0 &&
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={node.expanded ? 'Collapse' : 'Expand'}
|
||||||
|
className={
|
||||||
|
node.expanded ? styles.collapseButton : styles.expandButton
|
||||||
|
}
|
||||||
|
style={{left: -0.5 * scaffoldBlockPxWidth}}
|
||||||
|
onClick={() =>
|
||||||
|
toggleChildrenVisibility({
|
||||||
|
node,
|
||||||
|
path,
|
||||||
|
treeIndex,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{node.expanded &&
|
||||||
|
!isDragging &&
|
||||||
|
<div
|
||||||
|
style={{width: scaffoldBlockPxWidth}}
|
||||||
|
className={styles.lineChildren}
|
||||||
|
/>}
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className={styles.rowWrapper}>
|
||||||
|
{/* Set the row preview to be used during drag and drop */}
|
||||||
|
{connectDragPreview(
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
styles.row +
|
||||||
|
(isLandingPadActive ? ` ${styles.rowLandingPad}` : '') +
|
||||||
|
(isLandingPadActive && !canDrop
|
||||||
|
? ` ${styles.rowCancelPad}`
|
||||||
|
: '') +
|
||||||
|
(isSearchMatch ? ` ${styles.rowSearchMatch}` : '') +
|
||||||
|
(isSearchFocus ? ` ${styles.rowSearchFocus}` : '') +
|
||||||
|
(className ? ` ${className}` : '')
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
opacity: isDraggedDescendant ? 0.5 : 1,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{handle}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
styles.rowContents +
|
||||||
|
(!canDrag ? ` ${styles.rowContentsDragDisabled}` : '') +
|
||||||
|
(options && options.isSelected && options.isSelected({path, node}) ? ` ${styles.rowContentsSelected}` : '') // This has been added compared to the original file
|
||||||
|
}
|
||||||
|
onClick={() => options && options.onClick && options.onClick({path, node})} // This has been added compared to the original file
|
||||||
|
>
|
||||||
|
<div className={styles.rowLabel}>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
styles.rowTitle +
|
||||||
|
(node.subtitle ? ` ${styles.rowTitleWithSubtitle}` : '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{typeof nodeTitle === 'function'
|
||||||
|
? nodeTitle({
|
||||||
|
node,
|
||||||
|
path,
|
||||||
|
treeIndex,
|
||||||
|
})
|
||||||
|
: nodeTitle}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{nodeSubtitle &&
|
||||||
|
<span className={styles.rowSubtitle}>
|
||||||
|
{typeof nodeSubtitle === 'function'
|
||||||
|
? nodeSubtitle({
|
||||||
|
node,
|
||||||
|
path,
|
||||||
|
treeIndex,
|
||||||
|
})
|
||||||
|
: nodeSubtitle}
|
||||||
|
</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.rowToolbar}>
|
||||||
|
{buttons.map((btn, index) =>
|
||||||
|
<div
|
||||||
|
key={index} // eslint-disable-line react/no-array-index-key
|
||||||
|
className={styles.toolbarButton}
|
||||||
|
>
|
||||||
|
{btn}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RuleTreeNodeRenderer.defaultProps = {
|
||||||
|
isSearchMatch: false,
|
||||||
|
isSearchFocus: false,
|
||||||
|
canDrag: false,
|
||||||
|
toggleChildrenVisibility: null,
|
||||||
|
buttons: [],
|
||||||
|
className: '',
|
||||||
|
style: {},
|
||||||
|
parentNode: null,
|
||||||
|
draggedNode: null,
|
||||||
|
canDrop: false,
|
||||||
|
title: null,
|
||||||
|
subtitle: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
RuleTreeNodeRenderer.propTypes = {
|
||||||
|
node: PropTypes.shape({}).isRequired,
|
||||||
|
title: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
||||||
|
subtitle: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
||||||
|
path: PropTypes.arrayOf(
|
||||||
|
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||||
|
).isRequired,
|
||||||
|
treeIndex: PropTypes.number.isRequired,
|
||||||
|
isSearchMatch: PropTypes.bool,
|
||||||
|
isSearchFocus: PropTypes.bool,
|
||||||
|
canDrag: PropTypes.bool,
|
||||||
|
scaffoldBlockPxWidth: PropTypes.number.isRequired,
|
||||||
|
toggleChildrenVisibility: PropTypes.func,
|
||||||
|
buttons: PropTypes.arrayOf(PropTypes.node),
|
||||||
|
className: PropTypes.string,
|
||||||
|
style: PropTypes.shape({}),
|
||||||
|
|
||||||
|
// Drag and drop API functions
|
||||||
|
// Drag source
|
||||||
|
connectDragPreview: PropTypes.func.isRequired,
|
||||||
|
connectDragSource: PropTypes.func.isRequired,
|
||||||
|
parentNode: PropTypes.shape({}), // Needed for dndManager
|
||||||
|
isDragging: PropTypes.bool.isRequired,
|
||||||
|
didDrop: PropTypes.bool.isRequired,
|
||||||
|
draggedNode: PropTypes.shape({}),
|
||||||
|
// Drop target
|
||||||
|
isOver: PropTypes.bool.isRequired,
|
||||||
|
canDrop: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
return RuleTreeNodeRenderer;
|
||||||
|
}
|
||||||
|
|
300
client/src/lists/segments/node-renderer-default.scss
Normal file
300
client/src/lists/segments/node-renderer-default.scss
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
// Taken from https://github.com/fritz-c/react-sortable-tree/blob/d6a9be9e4931b5f0dac7b87511d309694f42355e/src/node-renderer-default.scss
|
||||||
|
|
||||||
|
$row-padding: 10px;
|
||||||
|
|
||||||
|
.rowWrapper {
|
||||||
|
padding: $row-padding $row-padding $row-padding 0;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
height: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The outline of where the element will go if dropped, displayed while dragging
|
||||||
|
*/
|
||||||
|
.rowLandingPad {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
outline: none !important;
|
||||||
|
|
||||||
|
* {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: lightblue;
|
||||||
|
border: 3px dashed white;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternate appearance of the landing pad when the dragged location is invalid
|
||||||
|
*/
|
||||||
|
.rowCancelPad {
|
||||||
|
@extend .rowLandingPad;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #e6a8ad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nodes matching the search conditions are highlighted
|
||||||
|
*/
|
||||||
|
.rowSearchMatch {
|
||||||
|
outline: solid 3px #0080ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node that matches the search conditions and is currently focused
|
||||||
|
*/
|
||||||
|
.rowSearchFocus {
|
||||||
|
outline: solid 3px #fc6421;
|
||||||
|
}
|
||||||
|
|
||||||
|
%rowItem {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowContents {
|
||||||
|
@extend %rowItem;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
border: solid #bbb 1px;
|
||||||
|
border-left: none;
|
||||||
|
box-shadow: 0 2px 2px -2px;
|
||||||
|
padding: 0 5px 0 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
min-width: 230px;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowContentsDragDisabled {
|
||||||
|
border-left: solid #bbb 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowLabel {
|
||||||
|
@extend %rowItem;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowToolbar {
|
||||||
|
@extend %rowItem;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moveHandle {
|
||||||
|
@extend %rowItem;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 44px;
|
||||||
|
background: #d9d9d9
|
||||||
|
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MiIgaGVpZ2h0PSI0MiI+PGcgc3Ryb2tlPSIjRkZGIiBzdHJva2Utd2lkdGg9IjIuOSIgPjxwYXRoIGQ9Ik0xNCAxNS43aDE0LjQiLz48cGF0aCBkPSJNMTQgMjEuNGgxNC40Ii8+PHBhdGggZD0iTTE0IDI3LjFoMTQuNCIvPjwvZz4KPC9zdmc+')
|
||||||
|
no-repeat center;
|
||||||
|
border: solid #aaa 1px;
|
||||||
|
box-shadow: 0 2px 2px -2px;
|
||||||
|
cursor: move;
|
||||||
|
border-radius: 1px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingHandle {
|
||||||
|
@extend .moveHandle;
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pointFade {
|
||||||
|
0%,
|
||||||
|
19.999%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingCircle {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
margin: 10%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingCirclePoint {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
$point-count: 12;
|
||||||
|
$spin-animation-time: 800ms;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 11%;
|
||||||
|
height: 30%;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 30%;
|
||||||
|
animation: pointFade $spin-animation-time infinite ease-in-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through (($point-count + 1) / 2) {
|
||||||
|
&:nth-of-type(#{$i}) {
|
||||||
|
transform: rotate(360deg / $point-count * ($i - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(#{$i + $point-count / 2}) {
|
||||||
|
transform: rotate(180deg + 360deg / $point-count * ($i - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(#{$i}),
|
||||||
|
&:nth-of-type(#{$i + $point-count / 2}) {
|
||||||
|
&:before {
|
||||||
|
animation-delay: - $spin-animation-time + ($spin-animation-time /
|
||||||
|
$point-count * 2 * ($i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton {
|
||||||
|
@extend %rowItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowTitle {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowTitleWithSubtitle {
|
||||||
|
font-size: 85%;
|
||||||
|
display: block;
|
||||||
|
height: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowSubtitle {
|
||||||
|
font-size: 70%;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapseButton,
|
||||||
|
.expandButton {
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: 0 0 0 1px #000;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 1px #000, 0 0 1px 3px #83bef9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:active) {
|
||||||
|
background-size: 24px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapseButton {
|
||||||
|
background: #fff
|
||||||
|
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCI+PGNpcmNsZSBjeD0iOSIgY3k9IjkiIHI9IjgiIGZpbGw9IiNGRkYiLz48ZyBzdHJva2U9IiM5ODk4OTgiIHN0cm9rZS13aWR0aD0iMS45IiA+PHBhdGggZD0iTTQuNSA5aDkiLz48L2c+Cjwvc3ZnPg==')
|
||||||
|
no-repeat center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandButton {
|
||||||
|
background: #fff
|
||||||
|
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCI+PGNpcmNsZSBjeD0iOSIgY3k9IjkiIHI9IjgiIGZpbGw9IiNGRkYiLz48ZyBzdHJva2U9IiM5ODk4OTgiIHN0cm9rZS13aWR0aD0iMS45IiA+PHBhdGggZD0iTTQuNSA5aDkiLz48cGF0aCBkPSJNOSA0LjV2OSIvPjwvZz4KPC9zdmc+')
|
||||||
|
no-repeat center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes for IE9 and below
|
||||||
|
*/
|
||||||
|
%fixVertAlign {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row_NoFlex {
|
||||||
|
@extend %fixVertAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowContents_NoFlex {
|
||||||
|
@extend %fixVertAlign;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowLabel_NoFlex {
|
||||||
|
@extend %rowItem;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rowToolbar_NoFlex {
|
||||||
|
@extend %rowItem;
|
||||||
|
text-align: right;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line for under a node with children
|
||||||
|
*/
|
||||||
|
.lineChildren {
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: black;
|
||||||
|
width: 1px;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
height: $row-padding;
|
||||||
|
}
|
||||||
|
}
|
5
client/src/lists/segments/rule-tree.scss
Normal file
5
client/src/lists/segments/rule-tree.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@import './node-renderer-default';
|
||||||
|
|
||||||
|
.rowContents.rowContentsSelected {
|
||||||
|
background-color: #d3d3ff;
|
||||||
|
}
|
|
@ -16,9 +16,40 @@ module.exports = {
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{test: /\.(js|jsx)$/, use: 'babel-loader'},
|
{
|
||||||
{test: /\.css$/, loader: 'style-loader!css-loader'},
|
test: /\.(js|jsx)$/,
|
||||||
{test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192' } // inline base64 URLs for <=8k images, direct URLs for the rest
|
exclude: /(disposables)/ /* https://github.com/react-dnd/react-dnd/issues/407 */,
|
||||||
|
use: [ 'babel-loader' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [ 'style-loader', 'css-loader' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg|gif)$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
exclude: path.join(__dirname, 'node_modules'),
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
modules: true,
|
||||||
|
localIdentName: '[path][name]__[local]--[hash:base64:5]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'sass-loader' ]
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue