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-i18next": "^4.6.1",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"react-sortable-tree": "^1.2.0",
|
||||
"slugify": "^1.1.0",
|
||||
"url-parse": "^1.1.9"
|
||||
},
|
||||
|
@ -43,6 +44,8 @@
|
|||
"babel-preset-stage-1": "^6.24.1",
|
||||
"css-loader": "^0.28.4",
|
||||
"i18next-conv": "^3.0.3",
|
||||
"node-sass": "^4.5.3",
|
||||
"sass-loader": "^6.0.6",
|
||||
"style-loader": "^0.18.2",
|
||||
"url-loader": "^0.5.9",
|
||||
"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 {
|
||||
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 {
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -71,7 +71,6 @@ class TreeTable extends Component {
|
|||
withHeader: PropTypes.bool,
|
||||
withDescription: PropTypes.bool,
|
||||
noTable: PropTypes.bool,
|
||||
withDnd: 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.updateSelection();
|
||||
|
|
|
@ -10,7 +10,9 @@ import {
|
|||
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
|
||||
import {DeleteModalDialog} from "../../lib/delete";
|
||||
import interoperableErrors from '../../../../shared/interoperable-errors';
|
||||
import {TreeSelectMode, TreeTable} from "../../lib/tree";
|
||||
|
||||
import SortableTree from 'react-sortable-tree';
|
||||
import { getRuleTreeNodeRenderer } from './RuleTreeNodeRenderer';
|
||||
|
||||
@translate()
|
||||
@withForm
|
||||
|
@ -21,7 +23,110 @@ export default class CUD extends Component {
|
|||
constructor(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();
|
||||
}
|
||||
|
@ -100,9 +205,9 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async onRuleSelectionChangedAsync(sel) {
|
||||
onRuleSelectionClick(data) {
|
||||
this.setState({
|
||||
selectedRule: sel
|
||||
selectedRule: data.node.key
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -110,119 +215,6 @@ export default class CUD extends Component {
|
|||
const t = this.props.t;
|
||||
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 (
|
||||
|
||||
<div>
|
||||
|
@ -253,10 +245,19 @@ export default class CUD extends Component {
|
|||
<hr />
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<TreeTable data={sampleTreeData} noTable withIcons withDnd format="wide" selectMode={TreeSelectMode.SINGLE} selection={this.state.selectedRule} onSelectionChangedAsync={::this.onRuleSelectionChangedAsync} />
|
||||
<div className="col-md-6" >
|
||||
<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 className="col-sm-6">
|
||||
<div className="col-md-6">
|
||||
<h3>{t('Selected Rule Options')}</h3>
|
||||
<InputField id="name" label={t('Name')} format="wide" />
|
||||
</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: {
|
||||
rules: [
|
||||
{test: /\.(js|jsx)$/, use: 'babel-loader'},
|
||||
{test: /\.css$/, loader: 'style-loader!css-loader'},
|
||||
{test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192' } // inline base64 URLs for <=8k images, direct URLs for the rest
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
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: {
|
||||
|
|
Loading…
Reference in a new issue