mirror of
https://github.com/xodio/xod.git
synced 2026-03-14 12:46:54 +01:00
Merge pull request #402 from xodio/feat-394-editor-rendering-order
Feat 394 editor rendering order
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
15
packages/xod-client/src/core/styles/components/PinLabel.scss
Normal file
15
packages/xod-client/src/core/styles/components/PinLabel.scss
Normal 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
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
'components/Node',
|
||||
'components/NodeText',
|
||||
'components/Pin',
|
||||
'components/PinLabel',
|
||||
'components/Link',
|
||||
'components/PopupAlert',
|
||||
'components/PopupPrompt',
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
47
packages/xod-client/src/project/components/PinLabel.jsx
Normal file
47
packages/xod-client/src/project/components/PinLabel.jsx
Normal 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,
|
||||
};
|
||||
@@ -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
|
||||
|
||||
68
packages/xod-client/stories/Link.jsx
Normal file
68
packages/xod-client/stories/Link.jsx
Normal 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
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -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', () => (
|
||||
|
||||
41
packages/xod-client/stories/PinLabel.jsx
Normal file
41
packages/xod-client/stories/PinLabel.jsx
Normal 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"
|
||||
/>
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user