diff --git a/packages/xod-client/src/project/components/Link.jsx b/packages/xod-client/src/project/components/Link.jsx
index cec643d0..c6e2202b 100644
--- a/packages/xod-client/src/project/components/Link.jsx
+++ b/packages/xod-client/src/project/components/Link.jsx
@@ -2,10 +2,20 @@ import * as R from 'ramda';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import { mapIndexed } from 'xod-func-tools';
import { noop } from '../../utils/ramda';
import { PIN_RADIUS, LINK_HOTSPOT_SIZE } from '../nodeLayout';
+import TooltipHOC from '../../tooltip/components/TooltipHOC';
+
+// :: [Error] -> [ReactNode]
+const renderTooltipContent = mapIndexed((err, idx) => (
+
+ {err.message}
+
+));
+
class Link extends React.Component {
constructor(props) {
super(props);
@@ -61,33 +71,45 @@ class Link extends React.Component {
const linkEndRadius = PIN_RADIUS - 3;
return (
-
-
-
-
-
-
+ (
+
+
+
+
+
+
+ )}
+ />
);
}
}
@@ -98,6 +120,7 @@ Link.propTypes = {
to: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
dead: PropTypes.bool,
+ errors: PropTypes.arrayOf(PropTypes.instanceOf(Error)),
isSelected: PropTypes.bool,
isGhost: PropTypes.bool,
isOverlay: PropTypes.bool,
@@ -107,6 +130,7 @@ Link.propTypes = {
Link.defaultProps = {
dead: false,
+ errors: [],
isSelected: false,
isGhost: false,
isOverlay: false,
diff --git a/packages/xod-client/src/project/components/layers/LinksOverlayLayer.jsx b/packages/xod-client/src/project/components/layers/LinksOverlayLayer.jsx
index dd545e10..166d757a 100644
--- a/packages/xod-client/src/project/components/layers/LinksOverlayLayer.jsx
+++ b/packages/xod-client/src/project/components/layers/LinksOverlayLayer.jsx
@@ -20,6 +20,8 @@ const LinksOverlayLayer = ({ links, selection, hidden, onClick }) => (
from={link.from}
to={link.to}
type={link.type}
+ dead={link.dead}
+ errors={link.errors}
onClick={onClick}
isSelected={isLinkSelected(selection, link.id)}
/>
diff --git a/packages/xod-client/src/project/selectors.js b/packages/xod-client/src/project/selectors.js
index 39d353f9..d92bfb35 100644
--- a/packages/xod-client/src/project/selectors.js
+++ b/packages/xod-client/src/project/selectors.js
@@ -3,7 +3,7 @@ import { Maybe } from 'ramda-fantasy';
import { createSelector } from 'reselect';
import * as XP from 'xod-project';
-import { foldMaybe, foldEither } from 'xod-func-tools';
+import { foldMaybe, foldEither, explodeMaybe } from 'xod-func-tools';
import { createIndexFromPatches } from 'xod-patch-search';
import {
@@ -138,12 +138,12 @@ export const getCurrentPatch = createSelector(
(patchPath, project) => R.chain(XP.getPatchByPath(R.__, project), patchPath)
);
-// :: Error -> RenderableNode -> RenderableNode
-const addError = R.curry((error, node) =>
+// :: Error -> RenderableNode|RenderableLink -> RenderableNode|RenderableLink
+const addError = R.curry((error, renderableEntity) =>
R.compose(
R.over(R.lensProp('errors'), R.append(error)),
R.unless(R.has('errors'), R.assoc('errors', []))
- )(node)
+ )(renderableEntity)
);
// :: Project -> RenderableNode -> RenderableNode
@@ -235,39 +235,37 @@ export const getRenderableNodes = createMemoizedSelector(
// :: State -> StrMap RenderableLink
export const getRenderableLinks = createMemoizedSelector(
- [getRenderableNodes, getCurrentPatchLinks],
- [R.equals, R.equals],
- (nodes, links) =>
+ [getRenderableNodes, getCurrentPatchLinks, getCurrentPatch, getProject],
+ [R.equals, R.equals, R.equals, R.equals],
+ (nodes, links, curPatch, project) =>
R.compose(
addLinksPositioning(nodes),
- R.map(link => {
- const inputNodeId = XP.getLinkInputNodeId(link);
- const outputNodeId = XP.getLinkOutputNodeId(link);
- const inputPinKey = XP.getLinkInputPinKey(link);
- const outputPinKey = XP.getLinkOutputPinKey(link);
-
- const inputNodeIsDead = nodes[inputNodeId].dead;
- const outputNodeIsDead = nodes[outputNodeId].dead;
- const inputPinKeyHasDeadType = R.pathEq(
- [inputNodeId, 'pins', inputPinKey, 'type'],
- XP.PIN_TYPE.DEAD,
- nodes
- );
- const outputPinKeyHasDeadType = R.pathEq(
- [outputNodeId, 'pins', outputPinKey, 'type'],
- XP.PIN_TYPE.DEAD,
- nodes
- );
-
- return R.merge(link, {
- type: nodes[outputNodeId].pins[outputPinKey].type,
- dead:
- inputNodeIsDead ||
- outputNodeIsDead ||
- inputPinKeyHasDeadType ||
- outputPinKeyHasDeadType,
- });
- })
+ R.map(link =>
+ R.compose(
+ newLink => {
+ const outputNodeId = XP.getLinkOutputNodeId(link);
+ const outputPinKey = XP.getLinkOutputPinKey(link);
+ return R.assoc(
+ 'type',
+ nodes[outputNodeId].pins[outputPinKey].type,
+ newLink
+ );
+ },
+ foldEither(
+ R.pipe(addError(R.__, link), R.assoc('dead', true)),
+ R.identity
+ ),
+ R.map(R.always(link)),
+ XP.validateLinkPins
+ )(
+ link,
+ explodeMaybe(
+ 'Imposible error: RenderableLinks will be computed only for current patch',
+ curPatch
+ ),
+ project
+ )
+ )
)(links)
);
diff --git a/packages/xod-project/src/project.js b/packages/xod-project/src/project.js
index 4ae83492..7f0f47d6 100644
--- a/packages/xod-project/src/project.js
+++ b/packages/xod-project/src/project.js
@@ -475,15 +475,14 @@ const checkLinkPin = def(
/**
* Checks project for existence of patches and pins that used in link.
*
- * @private
- * @function checkPinKeys
+ * @function validateLinkPins
* @param {Link} link
* @param {Patch} patch
* @param {Project} project
* @returns {Either}
*/
-const checkPinKeys = def(
- 'checkPinKeys :: Link -> Patch -> Project -> Either Error Patch',
+export const validateLinkPins = def(
+ 'validateLinkPins :: Link -> Patch -> Project -> Either Error Patch',
(link, patch, project) => {
const checkInputPin = checkLinkPin(
Link.getLinkInputNodeId,
@@ -548,7 +547,7 @@ export const validatePatchContents = def(
R.compose(
R.map(R.always(patch)),
R.sequence(Either.of),
- R.map(checkPinKeys(R.__, patch, project))
+ R.map(validateLinkPins(R.__, patch, project))
)
),
Patch.listLinks