Skip to content

Commit

Permalink
feat: add complex preview feature
Browse files Browse the repository at this point in the history
foo
  • Loading branch information
philippfromme committed Oct 5, 2023
1 parent c5a2ea2 commit 9a63a7b
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 0 deletions.
155 changes: 155 additions & 0 deletions lib/features/complex-preview/ComplexPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {
clear as svgClear,
create as svgCreate
} from 'tiny-svg';

import { getVisual } from '../../util/GraphicsUtil';

import { isConnection } from '../../util/ModelUtil';

import { translate } from '../../util/SvgTransformUtil';

/**
* @typedef {import('../../model/Types').Element} Element
* @typedef {import('../../model/Types').Shape} Shape
* @typedef {import('../../util/Types').Point} Point
* @typedef {import('../../util/Types').Rect} Rect
*
* @typedef { { element: Element, delta: Point } } MovedOption
* @typedef { { shape: Shape, bounds: Rect } } ResizedOption
*
* @typedef { {
* created?: Element[],
* removed?: Element[],
* moved?: MovedOption[],
* resized?: ResizedOption[]
* } } CreateOptions
*/

const LAYER_NAME = 'complex-preview';

/**
* Complex preview for shapes and connections.
*/
export default class ComplexPreview {
constructor(canvas, graphicsFactory, previewSupport) {
this._canvas = canvas;
this._graphicsFactory = graphicsFactory;
this._previewSupport = previewSupport;

this._markers = [];
}

/**
* Create complex preview.
*
* @param {CreateOptions} options
*/
create(options) {

// there can only be one complex preview at a time
this.cleanUp();

const {
created = [],
moved = [],
removed = [],
resized = []
} = options;

const layer = this._canvas.getLayer(LAYER_NAME);

// shapes and connections to be created
created.filter(element => !isHidden(element)).forEach(element => {
let gfx;

if (isConnection(element)) {
gfx = this._graphicsFactory._createContainer('connection', svgCreate('g'));

this._graphicsFactory.drawConnection(getVisual(gfx), element);
} else {
gfx = this._graphicsFactory._createContainer('shape', svgCreate('g'));

this._graphicsFactory.drawShape(getVisual(gfx), element);

translate(gfx, element.x, element.y);
}

this._previewSupport.addDragger(element, layer, gfx);
});

// elements to be moved
moved.forEach(({ element, delta }) => {
this._previewSupport.addDragger(element, layer, undefined, 'djs-dragging');

this._canvas.addMarker(element, 'djs-element-hidden');

this._markers.push([ element, 'djs-element-hidden' ]);

const dragger = this._previewSupport.addDragger(element, layer);

if (isConnection(element)) {
translate(dragger, delta.x, delta.y);
} else {
translate(dragger, element.x + delta.x, element.y + delta.y);
}
});

// elements to be removed
removed.forEach(element => {
this._previewSupport.addDragger(element, layer, undefined, 'djs-dragging');

this._canvas.addMarker(element, 'djs-element-hidden');

this._markers.push([ element, 'djs-element-hidden' ]);
});

// elements to be resized
resized.forEach(({ shape, bounds }) => {
this._canvas.addMarker(shape, 'djs-hidden');

this._markers.push([ shape, 'djs-hidden' ]);

this._previewSupport.addDragger(shape, layer, undefined, 'djs-dragging');

const gfx = this._graphicsFactory._createContainer('shape', svgCreate('g'));

this._graphicsFactory.drawShape(getVisual(gfx), shape, {
width: bounds.width,
height: bounds.height
});

translate(gfx, bounds.x, bounds.y);

this._previewSupport.addDragger(shape, layer, gfx);
});
}

cleanUp() {
svgClear(this._canvas.getLayer(LAYER_NAME));

this._markers.forEach(([ element, marker ]) => this._canvas.removeMarker(element, marker));

this._markers = [];

this._previewSupport.cleanUp();
}

show() {
this._canvas.showLayer(LAYER_NAME);
}

hide() {
this._canvas.hideLayer(LAYER_NAME);
}
}

ComplexPreview.$inject = [
'canvas',
'graphicsFactory',
'previewSupport'
];

function isHidden(element) {
return element.hidden;
}
12 changes: 12 additions & 0 deletions lib/features/complex-preview/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PreviewSupportModule from '../preview-support';

import ComplexPreview from './ComplexPreview';

/**
* @type { import('didi').ModuleDeclaration }
*/
export default {
__depends__: [ PreviewSupportModule ],
__init__: [ 'complexPreview' ],
complexPreview: [ 'type', ComplexPreview ]
};
4 changes: 4 additions & 0 deletions lib/features/preview-support/PreviewSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ PreviewSupport.prototype.addDragger = function(element, group, gfx, className =

svgAppend(group, dragger);

svgAttr(dragger, 'data-complex-preview-element-id', element.id);

return dragger;
};

Expand All @@ -141,6 +143,8 @@ PreviewSupport.prototype.addFrame = function(shape, group) {

svgAppend(group, frame);

svgAttr(frame, 'data-complex-preview-element-id', shape.id);

return frame;
};

Expand Down
168 changes: 168 additions & 0 deletions test/spec/features/complex-preview/ComplexPreviewSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {
bootstrapDiagram,
inject
} from 'test/TestHelper';

import complexPreviewModule from 'lib/features/complex-preview';
import modelingModule from 'lib/features/modeling';

import { queryAll as domQueryAll } from 'min-dom';

var testModules = [
complexPreviewModule,
modelingModule
];


describe('features/complex-preview - ComplexPreviewSpec', function() {

var root,
shape1,
shape2,
shape3,
connection,
newShape;

function setupDiagram(elementFactory, canvas) {
root = elementFactory.createRoot({
id: 'root'
});

canvas.setRootElement(root);

shape1 = elementFactory.createShape({
id: 'shape1',
x: 0,
y: 0,
width: 100,
height: 100
});

canvas.addShape(shape1, root);

shape2 = elementFactory.createShape({
id: 'shape2',
x: 200,
y: 0,
width: 100,
height: 100
});

canvas.addShape(shape2, root);

shape3 = elementFactory.createShape({
id: 'shape3',
x: 0,
y: 200,
width: 100,
height: 100
});

canvas.addShape(shape3, root);

connection = elementFactory.createConnection({
id: 'connection',
source: shape1,
target: shape2,
waypoints: [
{ x: 100, y: 50 },
{ x: 200, y: 50 }
]
});

canvas.addConnection(connection, root);

newShape = elementFactory.createShape({
id: 'newShape',
x: 400,
y: 0,
width: 100,
height: 100
});
}


beforeEach(bootstrapDiagram({
modules: testModules
}));

beforeEach(inject(setupDiagram));


beforeEach(inject(function(complexPreview) {
complexPreview.create({
created: [
newShape
],
removed: [
shape1,
connection
],
moved: [
{
element: shape2,
delta: {
x: 100,
y: 100
}
}
],
resized: [
{
shape: shape3,
bounds: {
x: 0,
y: 200,
width: 200,
height: 200
}
}
]
});
}));


it('should create preview', inject(function(canvas) {

// given

// when

// then
const layer = canvas.getLayer('complex-preview');

expect(layer).to.exist;

// created or removed (1 preview)
expect(queryPreview('newShape', layer)).to.have.length(1);
expect(queryPreview('connection', layer)).to.have.length(1);
expect(queryPreview('shape1', layer)).to.have.length(1);

// moved (2 previews)
expect(queryPreview('shape2', layer)).to.have.length(2);

// resized (2 previews)
expect(queryPreview('shape3', layer)).to.have.length(2);
}));


it('should clean up preview', inject(function(canvas, complexPreview) {

// given

// when
complexPreview.cleanUp();

// then
const layer = canvas.getLayer('complex-preview');

expect(layer).to.exist;

expect(layer.childNodes).to.have.length(0);
}));

});

function queryPreview(id, layer) {
return domQueryAll('[data-complex-preview-element-id="' + id + '"]', layer);
}

0 comments on commit 9a63a7b

Please sign in to comment.