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