src/visual/VisualStep.js (109 lines of code) (raw):
import _ from 'lodash';
import joint from 'jointjs';
const cDefaultWidth = 100;
const cMinHeight = 100;
const cPixelPerSymbol = 10;
const cLabelMargin = 20;
const cHeightPerPort = 50;
function findMaxLen(strList) {
return _.reduce(strList, (maxLen, str) => Math.max(str.length, maxLen), 0);
}
/** Class that provides graphical representation for the Step.
* @private
*/
export default class VisualStep extends joint.shapes.devs.Model {
/**
* Creates new Step visual representation. Accepts all options for
* joint.shapes.dev.Model and various custom parameters.
* @param {object=} opts - Action description.
* @param {Object.<string, Step>} opts.step - Step instance.
* @param {Object.<string, int>} [opts.x=0] - Center x coordinate.
* @param {Object.<string, int>} [opts.y=0] - Center y coordinate.
*/
constructor(opts = { step: null, x: 0, y: 0, portsEnabled: true }) {
super(_.defaultsDeep(opts, {
position: {
x: (opts.x - cDefaultWidth / 2) || 0,
y: (opts.y - cMinHeight / 2) || 0,
},
attrs: {
'.label': {
text: opts.step.namespace ? `${opts.step.namespace}.${opts.step.name}` : opts.step.name,
},
},
type: 'VisualStep',
portsEnabled: true,
}));
this.iPortsOn = {};
this.oPortsOn = {};
this.step = opts.step;
this.update();
}
/**
* Updates visual step according to the model.
*/
update() {
const step = this.step;
const inNames = Object.keys(step.i);
const outNames = Object.keys(step.o);
const size = this.attributes.size;
const height = Math.max(cMinHeight, cHeightPerPort * Math.max(inNames.length, outNames.length), size.height);
const label = this._getLabel();
const width = Math.max(cDefaultWidth, label.length * cPixelPerSymbol + 2 * cLabelMargin, size.width);
this.set({
inPorts: inNames,
outPorts: outNames,
size: {
height,
width,
},
});
this.attr('.label', {
text: label,
});
const iPortsOn = this._updatePortsState(this.iPortsOn, step.i);
const oPortsOn = this._updatePortsState(this.oPortsOn, step.o);
this.iPortsOn = iPortsOn;
this.oPortsOn = oPortsOn;
const ports = this.getPorts();
_.forEach(ports, (port) => {
const stepPort = port.group === 'in' ? step.i[port.id] : step.o[port.id];
const isEmpty = (_.size(stepPort.inputs) + _.size(stepPort.outputs)) === 0;
const isOn = port.group === 'in' ? iPortsOn[port.id] : oPortsOn[port.id];
let newVal = 'port-body';
if (isEmpty) {
newVal += ' empty';
}
if (!isOn) {
newVal += ' disabled';
}
const propVal = this.portProp(port.id, 'attrs/circle/class');
if (!propVal || newVal !== propVal) {
this.portProp(port.id, 'attrs/circle/class', newVal);
}
});
}
_updatePortsState(portsOn, ports) {
const defaultOnValue = this.attributes.portsEnabled;
const omitBy = _.omitBy || _.omit; // be prepared for legacy lodash 3.10.1
portsOn = omitBy(portsOn, (val, name) => _.isUndefined(ports[name]));
_.forEach(ports, (port, name) => {
if (_.isUndefined(portsOn[name])) {
portsOn[name] = defaultOnValue;
}
});
return portsOn;
}
togglePort(isInput, portName, value) {
const portsOn = isInput ? this.iPortsOn : this.oPortsOn;
if (_.isUndefined(portsOn[portName])) {
return;
}
value = _.isUndefined(value) ? !portsOn[portName] : value;
portsOn[portName] = value;
}
isPortEnabled(isInput, portName) {
const portsOn = isInput ? this.iPortsOn : this.oPortsOn;
return portsOn[portName];
}
/**
* Obtains bounding box os the element. Overrides Model method.
* @param opts options, see joint.shapes.devs.Model.getBBox
* @returns {*}
*/
getBBox(opts) {
const bbox = super.getBBox(opts);
const step = this.step;
if (step) {
const leftOffset = cPixelPerSymbol * findMaxLen(Object.keys(step.i));
const rightOffset = cPixelPerSymbol * findMaxLen(Object.keys(step.o));
bbox.x -= leftOffset;
bbox.width += leftOffset + rightOffset;
}
return bbox;
}
_getLabel() {
return this.step.namespace ? `${this.step.namespace}.${this.step.name}` : this.step.name;
}
}