Merge pull request #402 from xodio/feat-394-editor-rendering-order

Feat 394 editor rendering order
This commit is contained in:
Evgeny Kochetkov
2017-03-01 16:08:52 +03:00
committed by GitHub
14 changed files with 322 additions and 119 deletions

View File

@@ -1,33 +1,40 @@
@mixin link-parts-coloring($type-color) {
.line {
stroke: $type-color;
}
.end {
fill: $type-color;
}
}
.Link {
stroke: grey;
stroke-width: 2px;
.line {
stroke: grey;
stroke-width: 2px;
}
&.string {
stroke: $color-datatype-string;
@include link-parts-coloring($color-datatype-string);
}
&.number {
stroke: $color-datatype-number;
@include link-parts-coloring($color-datatype-number);
}
&.bool {
stroke: $color-datatype-bool;
@include link-parts-coloring($color-datatype-bool);
}
&.pulse {
stroke: $color-datatype-pulse;
@include link-parts-coloring($color-datatype-pulse);
}
&:hover {
.line {
stroke: white;
}
@include link-parts-coloring(white);
}
&.is-selected {
.line {
stroke: #4ed5ec;
}
@include link-parts-coloring($color-canvas-selected);
}
}

View File

@@ -1,17 +1,8 @@
@mixin symbol-coloring($type-color) {
stroke: $type-color;
$hover-color: lighten($type-color, 25%);
&:hover {
stroke: $hover-color
}
&.is-connected {
fill: $type-color;
&:hover {
fill: $hover-color
}
}
}
@@ -22,10 +13,6 @@
stroke-width: 2px;
filter: url(#dropshadow);
&:hover {
fill: color-canvas-hover($color-pin-fill);
}
&.string {
@include symbol-coloring($color-datatype-string);
}
@@ -43,20 +30,24 @@
}
}
.label {
font-family: $font-family-condensed;
font-weight: 500;
font-size: 12px;
dominant-baseline: central;
}
&:hover {
.symbol {
&.string {
@include symbol-coloring(lighten($color-datatype-string, 25%));
}
.label.input {
fill: $color-pinlabel-input;
}
&.number {
@include symbol-coloring(lighten($color-datatype-number, 25%));
}
.label.output {
fill: $color-pinlabel-output;
font-weight: 700; // black font looks thinner
&.bool {
@include symbol-coloring(lighten($color-datatype-bool, 25%));
}
&.pulse {
@include symbol-coloring(lighten($color-datatype-pulse, 25%));
}
}
}
.linkingHighlight {
@@ -77,6 +68,4 @@
fill: $color-canvas-selected;
}
}
}

View File

@@ -0,0 +1,15 @@
.PinLabel {
font-family: $font-family-condensed;
font-weight: 500;
font-size: 12px;
dominant-baseline: central;
}
.PinLabel.input {
fill: $color-pinlabel-input;
}
.PinLabel.output {
fill: $color-pinlabel-output;
font-weight: 700; // black font looks thinner
}

View File

@@ -26,6 +26,7 @@
'components/Node',
'components/NodeText',
'components/Pin',
'components/PinLabel',
'components/Link',
'components/PopupAlert',
'components/PopupPrompt',

View File

@@ -18,6 +18,7 @@ import NodesLayer from '../../project/components/NodesLayer';
import LinksLayer from '../../project/components/LinksLayer';
import GhostsLayer from '../../project/components/GhostsLayer';
import SnappingPreviewLayer from '../../project/components/SnappingPreviewLayer';
import DraggedNodeLayer from '../../project/components/DraggedNodeLayer';
import {
addNodesPositioning,
addLinksPositioning,
@@ -47,6 +48,9 @@ class Patch extends React.Component {
this.onLinkClick = this.onLinkClick.bind(this);
this.deselectAll = this.deselectAll.bind(this);
this.getDraggedNodeId = this.getDraggedNodeId.bind(this);
this.extendNodesByPinValidness = this.extendNodesByPinValidness.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
@@ -171,8 +175,11 @@ class Patch extends React.Component {
}
getNodes() {
const nodes = R.values(this.props.nodes);
return this.extendNodesByPinValidness(nodes);
return R.compose(
this.extendNodesByPinValidness,
R.values,
R.omit([this.getDraggedNodeId()]) // we are rendering dragged node in a separate layer
)(this.props.nodes);
}
getLinks() {
return R.values(this.props.links);
@@ -259,6 +266,7 @@ class Patch extends React.Component {
}
render() {
const draggedNodeId = this.getDraggedNodeId();
const nodes = this.getNodes();
const links = this.getLinks();
@@ -276,19 +284,23 @@ class Patch extends React.Component {
height={this.props.size.height}
onClick={this.deselectAll}
/>
<NodesLayer
nodes={nodes}
onMouseDown={this.onNodeMouseDown}
onPinMouseDown={this.onPinMouseDown}
onPinMouseUp={this.onPinMouseUp}
/>
<SnappingPreviewLayer
draggedNodeId={this.getDraggedNodeId()}
draggedNodeId={draggedNodeId}
nodes={this.props.nodes}
/>
<LinksLayer
links={links}
onClick={this.onLinkClick}
/>
<NodesLayer
nodes={nodes}
onMouseDown={this.onNodeMouseDown}
onPinMouseDown={this.onPinMouseDown}
onPinMouseUp={this.onPinMouseUp}
<DraggedNodeLayer
draggedNodeId={draggedNodeId}
nodes={this.props.nodes}
/>
<GhostsLayer
mousePosition={this.state.mousePosition}

View File

@@ -0,0 +1,53 @@
import React from 'react';
import SVGLayer from './SVGLayer';
import Node from './Node';
class DraggedNodeLayer extends React.Component {
constructor(props) {
super(props);
this.displayName = 'DraggedNodeLayer';
}
shouldComponentUpdate(nextProps) {
return !!(nextProps.draggedNodeId || this.props.draggedNodeId);
}
render() {
const {
nodes,
draggedNodeId,
} = this.props;
if (!draggedNodeId) return null;
const node = nodes[draggedNodeId];
return (
<SVGLayer
name="DraggedNode"
className="DraggedNodeLayer"
>
<Node
key={node.id}
id={node.id}
label={node.label}
position={node.position}
size={node.size}
outputPinsSectionHeight={node.outputPinsSectionHeight}
pins={node.pins}
width={node.width}
isSelected={node.isSelected}
isGhost={node.isGhost}
/>
</SVGLayer>
);
}
}
DraggedNodeLayer.propTypes = {
draggedNodeId: React.PropTypes.any,
nodes: React.PropTypes.any,
};
export default DraggedNodeLayer;

View File

@@ -3,6 +3,7 @@ import classNames from 'classnames';
import { SIZE } from 'xod-core';
import { noop } from '../../utils/ramda';
import { PIN_RADIUS } from '../nodeLayout';
class Link extends React.Component {
constructor(props) {
@@ -48,6 +49,8 @@ class Link extends React.Component {
const clickable = this.isClickable();
const pointerEvents = (clickable) ? 'all' : 'none';
const linkEndRadius = PIN_RADIUS - 3;
return (
<g
className={cls}
@@ -64,6 +67,20 @@ class Link extends React.Component {
className="line"
{...coords}
/>
<circle
className="end"
cx={coords.x1}
cy={coords.y1}
r={linkEndRadius}
fill="black"
/>
<circle
className="end"
cx={coords.x2}
cy={coords.y2}
r={linkEndRadius}
fill="black"
/>
</g>
);
}

View File

@@ -3,6 +3,7 @@ import React from 'react';
import classNames from 'classnames';
import Pin from './Pin';
import PinLabel from './PinLabel';
import NodeText from './NodeText';
import { noop } from '../../utils/ramda';
@@ -12,13 +13,8 @@ class Node extends React.Component {
constructor(props) {
super(props);
this.id = this.props.id;
// needed for distinguishing between mouseDown events on pins and on node body
this.pinListRef = null;
this.assignPinListRef = this.assignPinListRef.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onPinMouseUp = this.onPinMouseUp.bind(this);
this.onPinMouseDown = this.onPinMouseDown.bind(this);
}
@@ -28,11 +24,6 @@ class Node extends React.Component {
}
onMouseDown(event) {
if (this.pinListRef && this.pinListRef.contains(event.target)) {
event.preventDefault();
return;
}
this.props.onMouseDown(event, this.id);
}
@@ -44,10 +35,6 @@ class Node extends React.Component {
this.props.onPinMouseDown(this.id, pinId);
}
assignPinListRef(ref) {
this.pinListRef = ref;
}
render() {
const {
size,
@@ -76,16 +63,12 @@ class Node extends React.Component {
return (
<svg
key={this.id}
className={cls}
style={{ overflow: 'visible' }}
{...position}
{...size}
viewBox={`0 0 ${size.width} ${size.height}`}
onMouseDown={this.onMouseDown}
>
<g
onMouseOver={this.handleOver}
onMouseOut={this.handleOut}
>
<g className={cls} onMouseDown={this.onMouseDown}>
<clipPath id={maskId}>
<rect
className="mask"
@@ -112,15 +95,23 @@ class Node extends React.Component {
{label}
</NodeText>
</g>
<g className="pinlist" ref={this.assignPinListRef}>
<g className="pins">
{pinsArr.map(pin =>
<Pin
keyName={pin.key}
key={pin.key}
{...pin}
onMouseUp={this.onPinMouseUp}
onMouseDown={this.onPinMouseDown}
/>
<g key={pin.key}>
<Pin
{...pin}
keyName={pin.key}
key={`pin_${pin.key}`}
onMouseUp={this.onPinMouseUp}
onMouseDown={this.onPinMouseDown}
/>
<PinLabel
{...pin}
keyName={pin.key}
key={`pinlabel_${pin.key}`}
/>
</g>
)}
</g>
</svg>

View File

@@ -1,10 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import { PIN_DIRECTION, PIN_VALIDITY } from 'xod-core';
import { PIN_VALIDITY } from 'xod-core';
import { noop } from '../../utils/ramda';
const PIN_RADIUS = 6;
const TEXT_OFFSET_FROM_PIN_BORDER = 10;
import { PIN_RADIUS } from '../nodeLayout';
export default class Pin extends React.Component {
constructor(props) {
@@ -57,39 +55,11 @@ export default class Pin extends React.Component {
};
}
getTextProps() {
const textVerticalOffset = PIN_RADIUS + TEXT_OFFSET_FROM_PIN_BORDER;
const pos = this.getPinCenter();
return {
x: pos.x,
y: pos.y + (textVerticalOffset * (this.isInput() ? 1 : -1)),
textAnchor: 'middle',
};
}
getDirection() {
return this.props.direction;
}
isInput() {
return (this.getDirection() === PIN_DIRECTION.INPUT);
}
isInjected() {
return !!this.props.injected;
}
render() {
const pinLabel = this.props.pinLabel ? (
<text
className={`label ${this.isInput() ? 'input' : 'output'}`}
key={`pinText_${this.props.keyName}`}
{...this.getTextProps()}
>
{this.props.pinLabel}
</text>
) : null;
const cls = classNames('Pin', {
'is-property': this.isInjected(),
'is-selected': this.props.isSelected,
@@ -125,7 +95,6 @@ export default class Pin extends React.Component {
r="15"
/>
{symbol}
{pinLabel}
</g>
);
}
@@ -134,9 +103,7 @@ export default class Pin extends React.Component {
Pin.propTypes = {
keyName: React.PropTypes.string.isRequired,
injected: React.PropTypes.bool,
pinLabel: React.PropTypes.string,
type: React.PropTypes.string,
direction: React.PropTypes.string.isRequired,
position: React.PropTypes.object.isRequired,
onMouseUp: React.PropTypes.func.isRequired,
onMouseDown: React.PropTypes.func.isRequired,

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { PIN_DIRECTION } from 'xod-core';
import { PIN_RADIUS, TEXT_OFFSET_FROM_PIN_BORDER } from '../nodeLayout';
export default class Pin extends React.Component {
getPinCenter() {
return this.props.position;
}
getTextProps() {
const textVerticalOffset = PIN_RADIUS + TEXT_OFFSET_FROM_PIN_BORDER;
const pos = this.getPinCenter();
return {
x: pos.x,
y: pos.y + (textVerticalOffset * (this.isInput() ? 1 : -1)),
textAnchor: 'middle',
};
}
getDirection() {
return this.props.direction;
}
isInput() {
return (this.getDirection() === PIN_DIRECTION.INPUT);
}
render() {
return this.props.pinLabel ? (
<text
className={`PinLabel ${this.isInput() ? 'input' : 'output'}`}
key={`pinText_${this.props.keyName}`}
{...this.getTextProps()}
>
{this.props.pinLabel}
</text>
) : null;
}
}
Pin.propTypes = {
keyName: React.PropTypes.string.isRequired,
pinLabel: React.PropTypes.string,
direction: React.PropTypes.string.isRequired,
position: React.PropTypes.object.isRequired,
};

View File

@@ -20,6 +20,8 @@ export const PIN_MARGIN = {
};
export const NODE_CORNER_RADIUS = 5;
export const PIN_RADIUS = 6;
export const TEXT_OFFSET_FROM_PIN_BORDER = 10;
/**
* @param {number} pinCount

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import '../src/core/styles/main.scss';
import XODLink from '../src/project/components/Link';
const pFrom = { x: 30, y: 30 };
const pTo = { x: 120, y: 120 };
const baseProps = {
id: 'qwerty',
from: pFrom,
to: pTo,
isGhost: false,
isSelected: false,
type: 'string',
};
storiesOf('Link', module)
.addDecorator(story => (
<svg>
<rect width={(pFrom.x * 2) + pTo.x} height={(pFrom.y * 2) + pTo.x} fill="#676767" />
<circle
cx={pFrom.x}
cy={pFrom.y}
r="1"
fill="red"
/>
<circle
cx={pTo.x}
cy={pTo.y}
r="1"
fill="red"
/>
{story()}
</svg>
))
.add('string', () => (
<XODLink
{...baseProps}
type="string"
/>
))
.add('bool', () => (
<XODLink
{...baseProps}
type="bool"
/>
))
.add('number', () => (
<XODLink
{...baseProps}
type="number"
/>
))
.add('pulse', () => (
<XODLink
{...baseProps}
type="pulse"
/>
))
.add('selected', () => (
<XODLink
{...baseProps}
isSelected
/>
));

View File

@@ -34,16 +34,9 @@ storiesOf('Pin', module)
{story()}
</PatchSVG>
))
.add('input', () => (
.add('default', () => (
<Pin
{...baseProps}
direction="input"
/>
))
.add('output', () => (
<Pin
{...baseProps}
direction="output"
/>
))
.add('selected', () => (

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import '../src/core/styles/main.scss';
import PinLabel from '../src/project/components/PinLabel';
const pinCenter = { x: 70, y: 70 };
const baseProps = {
keyName: "my pin's keyname",
pinLabel: 'PIN',
direction: 'input',
position: pinCenter,
};
storiesOf('PinLabel', module)
.addDecorator(story => (
<svg>
<rect width={pinCenter.x * 2} height={pinCenter.y * 2} fill="#676767" />
<line x1={pinCenter.x} y1="0" x2={pinCenter.x} y2={pinCenter.y * 2} stroke="#373737" />
<line x1="0" y1={pinCenter.y} x2={pinCenter.x * 2} y2={pinCenter.y} stroke="#373737" />
<text y=".6em" fontSize="11" dy="0" fill="white">
<tspan x=".3em" dy=".6em">cross line indicates center</tspan>
<tspan x=".3em" dy="1.2em">position passed to pin</tspan>
</text>
{story()}
</svg>
))
.add('input', () => (
<PinLabel
{...baseProps}
direction="input"
/>
))
.add('output', () => (
<PinLabel
{...baseProps}
direction="output"
/>
));