Skip to content

Commit 8d7c087

Browse files
authored
fix(dia.Element): keep port ids that differ only in type apart (#3546)
1 parent e3862a8 commit 8d7c087

12 files changed

Lines changed: 321 additions & 66 deletions

File tree

‎.changeset/float-port-id.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@joint/core": patch
3+
---
4+
5+
dia.ElementView - fix `getLinkEnd()` to keep a non-integer numeric port id, e.g. `1.5`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@joint/core": patch
3+
---
4+
5+
dia.Paper - fix `multiLinks: false` to tell a link to port `0` apart from links to other ports

‎.changeset/port-id-lookups.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@joint/core": patch
3+
---
4+
5+
dia.Element - fix port lookups to match a port id by value and type, so `5` and `'5'` are two different ports and `0` is a valid id

‎packages/joint-core/src/connectionPoints/index.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function rectangleIntersection(line, view, magnet, opt) {
116116
function getNodeModelBBox(elementView, magnet, rotate) {
117117
const element = elementView.model;
118118

119-
const portId = elementView.findAttribute('port', magnet);
119+
const portId = elementView.findNodePortId(magnet);
120120
if (element.hasPort(portId)) {
121121
if (rotate) {
122122
return element.getPortBBox(portId, { rotate });

‎packages/joint-core/src/dia/CellView.mjs‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,22 +432,34 @@ export const CellView = View.extend({
432432
return link.findView(paper);
433433
},
434434

435+
/**
436+
* @public
437+
* @internal not documented
438+
* @description Finds the id of the port the node belongs to (the closest
439+
* `port` attribute). A numeric port id is restored from the string it is
440+
* stored as in the DOM.
441+
* @param {Element} node
442+
* @returns {string|number|null} The port id or `null` if the node does not belong to a port.
443+
*/
444+
findNodePortId: function(node) {
445+
const portNode = this.findAttributeNode('port', node);
446+
if (!portNode) return null;
447+
const port = portNode.getAttribute('port');
448+
return (portNode.getAttribute('port-id-type') === 'number') ? Number(port) : port;
449+
},
450+
435451
getLinkEnd: function(magnet, ...args) {
436452

437453
const model = this.model;
438454
const id = model.id;
439-
// Find a node with the `port` attribute set on it.
440-
const portNode = this.findAttributeNode('port', magnet);
455+
// Find the port the magnet belongs to.
456+
const port = this.findNodePortId(magnet);
441457
// Find a unique `selector` of the element under pointer that is a magnet.
442458
const selector = magnet.getAttribute('joint-selector');
443459

444460
const end = { id: id };
445461
if (selector != null) end.magnet = selector;
446-
if (portNode != null) {
447-
let port = portNode.getAttribute('port');
448-
if (portNode.getAttribute('port-id-type') === 'number') {
449-
port = parseInt(port, 10);
450-
}
462+
if (port != null) {
451463
end.port = port;
452464
if (!model.hasPort(port) && !selector) {
453465
// port created via the `port` attribute (not API)

‎packages/joint-core/src/dia/Element.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ export const Element = Cell.extend({
555555
var endDef = link.get(endType);
556556
if (!endDef) return center;
557557
var portId = endDef.port;
558-
if (!portId || !this.hasPort(portId)) return center;
558+
if (portId == null || !this.hasPort(portId)) return center;
559559
return this.getPortCenter(portId);
560560
}
561561

‎packages/joint-core/src/dia/ElementView.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,8 @@ export const ElementView = CellView.extend({
566566
el || (el = this.el);
567567
const nodeSelector = el.getAttribute(`${type}-selector`);
568568
if (nodeSelector) {
569-
const port = this.findAttribute('port', el);
570-
if (port) {
569+
const port = this.findNodePortId(el);
570+
if (port != null) {
571571
const proxyPortNode = this.findPortNode(port, nodeSelector);
572572
if (proxyPortNode) return proxyPortNode;
573573
} else {

‎packages/joint-core/src/dia/Graph.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,9 +1571,9 @@ export const Graph = Model.extend({
15711571

15721572
var { source: _source, target: _target } = _link.attributes;
15731573
return _source && _source.id === source.id &&
1574-
(!_source.port || (_source.port === source.port)) &&
1574+
(_source.port == null || (_source.port === source.port)) &&
15751575
_target && _target.id === target.id &&
1576-
(!_target.port || (_target.port === target.port));
1576+
(_target.port == null || (_target.port === target.port));
15771577

15781578
});
15791579

‎packages/joint-core/src/dia/ports.mjs‎

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const PortData = function(model) {
1414

1515
const clonedData = util.cloneDeep(model.get('ports')) || {};
1616
this.ports = [];
17-
this.portsMap = {};
17+
this.portsMap = new Map();
1818
this.groups = {};
1919
this.portLayoutNamespace = portLayoutNamespace;
2020
this.portLabelLayoutNamespace = portLabelLayoutNamespace;
@@ -27,11 +27,11 @@ const PortData = function(model) {
2727
PortData.prototype = {
2828

2929
hasPort: function(id) {
30-
return id in this.portsMap;
30+
return this.portsMap.has(id);
3131
},
3232

3333
getPort: function(id) {
34-
const port = this.portsMap[id];
34+
const port = this.portsMap.get(id);
3535
if (port) return port;
3636
throw new Error('Element: unable to find port with id ' + id);
3737
},
@@ -100,22 +100,22 @@ PortData.prototype = {
100100

101101
const accumulator = {
102102
ports: ports,
103-
result: {}
103+
result: new Map()
104104
};
105105

106106
// For each individual port transformation, find the information necessary to calculate SVG transformations:
107107
util.toArray(groupPortTransformations).reduce((res, portTransformation, index) => {
108108
const port = res.ports[index];
109109
const portId = port.id;
110-
res.result[portId] = {
110+
res.result.set(portId, {
111111
index,
112112
portId,
113113
portTransformation: portTransformation,
114114
labelTransformation: this._getPortLabelTransformation(port, Point(portTransformation), elBBox),
115115
portAttrs: port.attrs,
116116
portSize: port.size,
117117
labelSize: port.label.size
118-
};
118+
});
119119
return res;
120120
}, accumulator);
121121

@@ -160,7 +160,7 @@ PortData.prototype = {
160160
for (var j = 0, m = ports.length; j < m; j++) {
161161
const resolvedPort = this._evaluatePort(ports[j]);
162162
this.ports.push(resolvedPort);
163-
this.portsMap[resolvedPort.id] = resolvedPort;
163+
this.portsMap.set(resolvedPort.id, resolvedPort);
164164
}
165165
},
166166

@@ -376,34 +376,34 @@ export const elementPortPrototype = {
376376
_processRemovedPort: function() {
377377

378378
var current = this.get('ports') || {};
379-
var currentItemsMap = {};
379+
var currentIds = new Set();
380380

381381
util.toArray(current.items).forEach(function(item) {
382-
currentItemsMap[item.id] = true;
382+
currentIds.add(item.id);
383383
});
384384

385385
var previous = this.previous('ports') || {};
386-
var removed = {};
386+
var removed = new Set();
387387

388388
util.toArray(previous.items).forEach(function(item) {
389-
if (!currentItemsMap[item.id]) {
390-
removed[item.id] = true;
389+
if (!currentIds.has(item.id)) {
390+
removed.add(item.id);
391391
}
392392
});
393393

394394
var graph = this.graph;
395-
if (graph && !util.isEmpty(removed)) {
395+
if (graph && removed.size > 0) {
396396

397397
var inboundLinks = graph.getConnectedLinks(this, { inbound: true });
398398
inboundLinks.forEach(function(link) {
399399

400-
if (removed[link.get('target').port]) link.remove();
400+
if (removed.has(link.get('target').port)) link.remove();
401401
});
402402

403403
var outboundLinks = graph.getConnectedLinks(this, { outbound: true });
404404
outboundLinks.forEach(function(link) {
405405

406-
if (removed[link.get('source').port]) link.remove();
406+
if (removed.has(link.get('source').port)) link.remove();
407407
});
408408
}
409409
},
@@ -417,7 +417,7 @@ export const elementPortPrototype = {
417417
},
418418

419419
/**
420-
* @param {string} id
420+
* @param {string|number} id
421421
* @returns {boolean}
422422
*/
423423
hasPort: function(id) {
@@ -442,11 +442,11 @@ export const elementPortPrototype = {
442442
},
443443

444444
/**
445-
* @param {string} id
445+
* @param {string|number} id
446446
* @returns {object}
447447
*/
448448
getPort: function(id) {
449-
const port = util.toArray(this.prop('ports/items')).find(port => port.id && port.id === id);
449+
const port = util.toArray(this.prop('ports/items')).find(port => port.id != null && port.id === id);
450450
return util.cloneDeep(port);
451451
},
452452

@@ -461,11 +461,14 @@ export const elementPortPrototype = {
461461
getPortsPositions: function(groupName) {
462462

463463
const portsMetrics = this.getGroupPortsMetrics(groupName);
464+
// TODO: Return a `Map` keyed by the port id in the next major version.
465+
// A plain object keys the ports by the id as a string, so ports whose
466+
// ids differ only in type (`5` and `'5'`) overwrite each other here.
464467
const portsPosition = {};
465-
for (const portId in portsMetrics) {
468+
for (const [portId, metrics] of portsMetrics) {
466469
const {
467470
portTransformation: { x, y, angle },
468-
} = portsMetrics[portId];
471+
} = metrics;
469472
portsPosition[portId] = {
470473
x: x,
471474
y: y,
@@ -477,7 +480,7 @@ export const elementPortPrototype = {
477480

478481
getPortMetrics: function(portId) {
479482
const port = this._portSettingsData.getPort(portId);
480-
return this.getGroupPortsMetrics(port.group)[portId];
483+
return this.getGroupPortsMetrics(port.group).get(portId);
481484
},
482485

483486
getGroupPortsMetrics: function(groupName) {
@@ -505,7 +508,7 @@ export const elementPortPrototype = {
505508
},
506509

507510
/**
508-
* @param {string} portId
511+
* @param {string|number} portId
509512
* @returns {Point}
510513
* @description Returns the port center in the graph coordinate system.
511514
* The port center is in the graph coordinate system, and the position
@@ -521,7 +524,7 @@ export const elementPortPrototype = {
521524
},
522525

523526
/**
524-
* @param {string} portId
527+
* @param {string|number} portId
525528
* @param {object} [opt]
526529
* @param {boolean} [opt.rotate] - If true, the port bounding box is rotated
527530
* around the port center.
@@ -547,7 +550,7 @@ export const elementPortPrototype = {
547550
},
548551

549552
/**
550-
* @param {string|Port} port port id or port
553+
* @param {string|number|Port} port port id or port
551554
* @returns {number} port index
552555
*/
553556
getPortIndex: function(port) {
@@ -602,7 +605,7 @@ export const elementPortPrototype = {
602605
},
603606

604607
/**
605-
* @param {string} portId
608+
* @param {string|number} portId
606609
* @param {string|object=} path
607610
* @param {*=} value
608611
* @param {object=} opt
@@ -794,7 +797,7 @@ export const elementViewPortPrototype = {
794797
'fill': '#000000'
795798
}
796799
}],
797-
/** @type {Object<string, {portElement: Vectorizer, portLabelElement: Vectorizer}>} */
800+
/** @type {Map<string|number, {portElement: Vectorizer, portLabelElement: Vectorizer}>} */
798801
_portElementsCache: null,
799802

800803
/**
@@ -826,7 +829,7 @@ export const elementViewPortPrototype = {
826829
},
827830

828831
_cleanPortsCache: function() {
829-
this._portElementsCache = {};
832+
this._portElementsCache = new Map();
830833
},
831834

832835
/**
@@ -899,14 +902,15 @@ export const elementViewPortPrototype = {
899902
*/
900903
_getPortElement: function(port) {
901904

902-
if (this._portElementsCache[port.id]) {
903-
return this._portElementsCache[port.id].portElement;
905+
const portCache = this._portElementsCache.get(port.id);
906+
if (portCache) {
907+
return portCache.portElement;
904908
}
905909
return this._createPortElement(port);
906910
},
907911

908912
findPortNodes: function(portId, selector) {
909-
const portCache = this._portElementsCache[portId];
913+
const portCache = this._portElementsCache.get(portId);
910914
if (!portCache) return [];
911915
if (!selector) return [portCache.portContentElement.node];
912916
const portRoot = portCache.portElement.node;
@@ -935,7 +939,7 @@ export const elementViewPortPrototype = {
935939
* @private
936940
*/
937941
_removePorts: function() {
938-
util.invoke(this._portElementsCache, 'portElement.remove');
942+
this._portElementsCache.forEach(portCache => portCache.portElement.remove());
939943
},
940944

941945
/**
@@ -1047,14 +1051,14 @@ export const elementViewPortPrototype = {
10471051
portContainerElement.append(labelElement.addClass('joint-port-label'));
10481052
}
10491053

1050-
this._portElementsCache[port.id] = {
1054+
this._portElementsCache.set(port.id, {
10511055
portElement: portContainerElement,
10521056
portLabelElement: labelElement,
10531057
portSelectors: portContainerSelectors,
10541058
portLabelSelectors: labelSelectors,
10551059
portContentElement: portElement,
10561060
portContentSelectors: portSelectors
1057-
};
1061+
});
10581062

10591063
return portContainerElement;
10601064
},
@@ -1066,12 +1070,9 @@ export const elementViewPortPrototype = {
10661070
_updatePortGroup: function(groupName) {
10671071

10681072
const portsMetrics = this.model.getGroupPortsMetrics(groupName);
1069-
const portsIds = Object.keys(portsMetrics);
10701073

1071-
for (let i = 0, n = portsIds.length; i < n; i++) {
1072-
const portId = portsIds[i];
1073-
const metrics = portsMetrics[portId];
1074-
const cached = this._portElementsCache[portId] || {};
1074+
for (const [portId, metrics] of portsMetrics) {
1075+
const cached = this._portElementsCache.get(portId) || {};
10751076
const portTransformation = metrics.portTransformation;
10761077
const labelTransformation = metrics.labelTransformation;
10771078
if (labelTransformation && cached.portLabelElement) {

0 commit comments

Comments
 (0)