Skip to content

Commit

Permalink
feat(dia.Graph): add transferCellEmbeds() and transferCellConnectedLi…
Browse files Browse the repository at this point in the history
…nks() (#2752)

feat(dia.Graph): add `reparent` option to embed() (#2752)
  • Loading branch information
MartinKanera authored Oct 23, 2024
1 parent 6d12e65 commit b62479c
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 3 deletions.
17 changes: 15 additions & 2 deletions packages/joint-core/src/dia/Cell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,25 @@ export const Cell = Model.extend({
return this.set('parent', parent, opt);
},

embed: function(cell, opt) {
embed: function(cell, opt = {}) {
const cells = Array.isArray(cell) ? cell : [cell];
if (!this.canEmbed(cells)) {
throw new Error('Recursive embedding not allowed.');
}
if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
if (opt.reparent) {
const parents = uniq(cells.map(c => c.getParentCell()));

// Unembed cells from their current parents.
parents.forEach((parent) => {
// Cell doesn't have to be embedded.
if (!parent) return;

// Pass all the `cells` since the `dia.Cell._unembedCells` method can handle cases
// where not all elements of `cells` are embedded in the same parent.
parent._unembedCells(cells, opt);
});

} else if (cells.some(c => c.isEmbedded() && this.id !== c.parent())) {
throw new Error('Embedding of already embedded cells is not allowed.');
}
this._embedCells(cells, opt);
Expand Down
33 changes: 33 additions & 0 deletions packages/joint-core/src/dia/Graph.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,39 @@ export const Graph = Model.extend({
this.get('cells').remove(cell, { silent: true });
},

transferCellEmbeds: function(sourceCell, targetCell, opt = {}) {

const batchName = 'transfer-embeds';
this.startBatch(batchName);

// Embed children of the source cell in the target cell.
const children = sourceCell.getEmbeddedCells();
targetCell.embed(children, { ...opt, reparent: true });

this.stopBatch(batchName);
},

transferCellConnectedLinks: function(sourceCell, targetCell, opt = {}) {

const batchName = 'transfer-connected-links';
this.startBatch(batchName);

// Reconnect all the links connected to the old cell to the new cell.
const connectedLinks = this.getConnectedLinks(sourceCell, opt);
connectedLinks.forEach((link) => {

if (link.getSourceCell() === sourceCell) {
link.prop(['source', 'id'], targetCell.id, opt);
}

if (link.getTargetCell() === sourceCell) {
link.prop(['target', 'id'], targetCell.id, opt);
}
});

this.stopBatch(batchName);
},

// Get a cell by `id`.
getCell: function(id) {

Expand Down
30 changes: 30 additions & 0 deletions packages/joint-core/test/jointjs/cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,36 @@ QUnit.module('cell', function(hooks) {

assert.raises(() => { cell3.embed(cell2); }, /Embedding of already embedded cells is not allowed/, 'throws exception on embedding of embedded cell');
});

QUnit.test('opt.reparent = true', function(assert) {

const cell1 = new joint.shapes.standard.Rectangle({
position: { x: 20, y: 20 },
size: { width: 60, height: 60 }
});
const cell2 = new joint.shapes.standard.Rectangle({
position: { x: 20, y: 20 },
size: { width: 60, height: 60 }
});
const cell3 = new joint.shapes.standard.Rectangle({
position: { x: 20, y: 20 },
size: { width: 60, height: 60 }
});
const cell4 = new joint.shapes.standard.Rectangle({
position: { x: 20, y: 20 },
size: { width: 60, height: 60 }
});

this.graph.addCells([cell1, cell2, cell3, cell4]);

cell1.embed(cell2);
cell3.embed([cell2, cell4], { reparent: true });

assert.equal(cell1.getEmbeddedCells().length, 0);
assert.equal(cell2.parent(), cell3.id);
assert.equal(cell3.getEmbeddedCells()[0].id, cell2.id);
assert.equal(cell3.getEmbeddedCells()[1].id, cell4.id);
});
});

QUnit.module('remove attributes', function(hooks) {
Expand Down
105 changes: 105 additions & 0 deletions packages/joint-core/test/jointjs/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -1546,4 +1546,109 @@ QUnit.module('graph', function(hooks) {
assert.notOk(graph.hasActiveBatch());
});
});

QUnit.module('graph.transferCellEmbeds()', function() {

QUnit.test('should transfer embeds from one element to another', function(assert) {

const originalElement = new joint.shapes.standard.Rectangle();
const child = new joint.shapes.standard.Rectangle();
const replacementElement = new joint.shapes.standard.Rectangle();

originalElement.embed(child);

this.graph.addCells([originalElement, child, replacementElement]);
this.graph.transferCellEmbeds(originalElement, replacementElement);

assert.equal(replacementElement.getEmbeddedCells()[0], child);
assert.equal(originalElement.getEmbeddedCells().length, 0);
});

QUnit.test('should transfer embeds from an element to a link', function(assert) {

const link = new joint.shapes.standard.Link();
const child = new joint.shapes.standard.Rectangle();
const element = new joint.shapes.standard.Rectangle();

element.embed(child);

this.graph.addCells([link, child, element]);
this.graph.transferCellEmbeds(element, link);

assert.equal(link.getEmbeddedCells()[0], child);
assert.equal(element.getEmbeddedCells().length, 0);
});
});

QUnit.module('graph.transferCellConnectedLinks()', function() {

QUnit.test('should transfer links of an element', function(assert) {

const originalElement = new joint.shapes.standard.Rectangle();
const link1 = new joint.shapes.standard.Link({ source: { id: originalElement.id }});
const link2 = new joint.shapes.standard.Link({ target: { id: originalElement.id }});
const replacementElement = new joint.shapes.standard.Rectangle();

this.graph.addCells([originalElement, link1, link2, replacementElement]);
this.graph.transferCellConnectedLinks(originalElement, replacementElement);

assert.equal(link1.source().id, replacementElement.id);
assert.equal(link2.target().id, replacementElement.id);
});

QUnit.test('should transfer links of a link', function(assert) {

const originalLink = new joint.shapes.standard.Link();
const link1 = new joint.shapes.standard.Link({ source: { id: originalLink.id }});
const link2 = new joint.shapes.standard.Link({ target: { id: originalLink.id }});
const replacementLink = new joint.shapes.standard.Link();

this.graph.addCells([originalLink, link1, link2, replacementLink]);
this.graph.transferCellConnectedLinks(originalLink, replacementLink);

assert.equal(link1.source().id, replacementLink.id);
assert.equal(link2.target().id, replacementLink.id);
});

QUnit.test('should work when transferring links from a link to an element', function(assert) {

const originalLink = new joint.shapes.standard.Link();
const link1 = new joint.shapes.standard.Link({ source: { id: originalLink.id }});
const link2 = new joint.shapes.standard.Link({ target: { id: originalLink.id }});
const element = new joint.shapes.standard.Rectangle();

this.graph.addCells([originalLink, link1, link2, element]);
this.graph.transferCellConnectedLinks(originalLink, element);

assert.equal(link1.source().id, element.id);
assert.equal(link2.target().id, element.id);
});

QUnit.test('should work when transferring links from an element to a link', function(assert) {

const originalElement = new joint.shapes.standard.Rectangle();
const link1 = new joint.shapes.standard.Link({ source: { id: originalElement.id }});
const link2 = new joint.shapes.standard.Link({ target: { id: originalElement.id }});
const replacementLink = new joint.shapes.standard.Link();

this.graph.addCells([originalElement, link1, link2, replacementLink]);
this.graph.transferCellConnectedLinks(originalElement, replacementLink);

assert.equal(link1.source().id, replacementLink.id);
assert.equal(link2.target().id, replacementLink.id);
});

QUnit.test('should work with loop links', function(assert) {

const originalElement = new joint.shapes.standard.Rectangle();
const link = new joint.shapes.standard.Link({ source: { id: originalElement.id }, target: { id: originalElement.id }});
const replacementElement = new joint.shapes.standard.Rectangle();

this.graph.addCells([originalElement, link, replacementElement]);
this.graph.transferCellConnectedLinks(originalElement, replacementElement);

assert.equal(link.source().id, replacementElement.id);
assert.equal(link.target().id, replacementElement.id);
});
});
});
10 changes: 9 additions & 1 deletion packages/joint-core/types/joint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ export namespace dia {

removeCells(cells: Cell[], opt?: Cell.DisconnectableOptions): this;

transferCellEmbeds(sourceCell: Cell, targetCell: Cell, opt?: S): void;

transferCellConnectedLinks(sourceCell: Cell, targetCell: Cell, opt?: Graph.ConnectionOptions): void;

resize(width: number, height: number, opt?: S): this;

resizeCells(width: number, height: number, cells: Cell[], opt?: S): this;
Expand Down Expand Up @@ -307,6 +311,10 @@ export namespace dia {
[key: string]: any;
}

interface EmbedOptions extends Options {
reparent?: boolean;
}

interface EmbeddableOptions<T = boolean> extends Options {
deep?: T;
}
Expand Down Expand Up @@ -439,7 +447,7 @@ export namespace dia {

stopTransitions(path?: string, delim?: string): this;

embed(cell: Cell | Cell[], opt?: Graph.Options): this;
embed(cell: Cell | Cell[], opt?: Cell.EmbedOptions): this;

unembed(cell: Cell | Cell[], opt?: Graph.Options): this;

Expand Down

0 comments on commit b62479c

Please sign in to comment.