feat(modeling): use BPMN in Color for color setting

Additionally to custom bpmn.io properties, `modeling#setColor` will use
[BPMN in Color properties](https://github.com/bpmn-miwg/bpmn-in-color).
This commit is contained in:
Maciej Barelkowski 2021-05-26 10:58:24 +02:00 committed by fake-join[bot]
parent 4251e31af2
commit 439bc4ead0
6 changed files with 231 additions and 61 deletions

View File

@ -52,10 +52,13 @@ export default function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {
descriptor.di = {};
// fill and stroke will be set to DI
// colors will be set to DI
copyProperties(businessObject.di, descriptor.di, [
'fill',
'stroke'
'stroke',
'background-color',
'border-color',
'color'
]);
copyProperties(businessObject.di, descriptor, 'isExpanded');

View File

@ -1,6 +1,8 @@
import {
assign,
forEach
forEach,
isString,
pick
} from 'min-dash';
@ -12,6 +14,24 @@ var DEFAULT_COLORS = {
export default function SetColorHandler(commandStack) {
this._commandStack = commandStack;
this._normalizeColor = function(color) {
// Remove color for falsy values.
if (!color) {
return undefined;
}
if (isString(color)) {
var hexColor = colorToHex(color);
if (hexColor) {
return hexColor;
}
}
throw new Error('invalid color value: ' + color);
};
}
SetColorHandler.$inject = [
@ -28,21 +48,76 @@ SetColorHandler.prototype.postExecute = function(context) {
var di = {};
if ('fill' in colors) {
assign(di, { fill: colors.fill });
assign(di, {
'background-color': this._normalizeColor(colors.fill) });
}
if ('stroke' in colors) {
assign(di, { stroke: colors.stroke });
assign(di, {
'border-color': this._normalizeColor(colors.stroke) });
}
forEach(elements, function(element) {
var assignedDi = isConnection(element) ? pick(di, [ 'border-color' ]) : di;
// TODO @barmac: remove once we drop bpmn.io properties
ensureLegacySupport(assignedDi);
self._commandStack.execute('element.updateProperties', {
element: element,
properties: {
di: di
di: assignedDi
}
});
});
};
};
/**
* Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names and for colors
* with alpha less than 1.0. This depends on `<canvas>` serialization of the `context.fillStyle`.
* Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
*
* @example
* ```js
* var color = 'fuchsia';
* console.log(colorToHex(color));
* // "#ff00ff"
* color = 'rgba(1,2,3,0.4)';
* console.log(colorToHex(color));
* // null
* ```
*
* @param {string} color
* @returns {string|null}
*/
function colorToHex(color) {
var context = document.createElement('canvas').getContext('2d');
// (0) Start with transparent to account for browser default values.
context.fillStyle = 'transparent';
// (1) Assign color so that it's serialized.
context.fillStyle = color;
// (2) Return null for non-hex serialization result.
return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null;
}
function isConnection(element) {
return !!element.waypoints;
}
/**
* Add legacy properties if required.
* @param {{ 'border-color': string?, 'background-color': string? }} di
*/
function ensureLegacySupport(di) {
if ('border-color' in di) {
di.stroke = di['border-color'];
}
if ('background-color' in di) {
di.fill = di['background-color'];
}
}

View File

@ -264,10 +264,13 @@ export default function BpmnReplace(
newElement.di = {};
// fill and stroke will be set to DI
// colors will be set to DI
copyProperties(oldBusinessObject.di, newElement.di, [
'fill',
'stroke'
'stroke',
'background-color',
'border-color',
'color'
]);
newElement = replace.replaceElement(element, newElement, hints);

View File

@ -354,8 +354,8 @@ describe('features/copy-paste', function() {
// given
var task = elementRegistry.get('Task_1'),
rootElement = canvas.getRootElement(),
fill = 'red',
stroke = 'green';
fill = '#ff0000',
stroke = '#00ff00';
// when
@ -378,6 +378,10 @@ describe('features/copy-paste', function() {
var taskBo = getBusinessObject(task);
expect(taskBo.di.get('background-color')).to.equal(fill);
expect(taskBo.di.get('border-color')).to.equal(stroke);
// TODO @barmac: remove when we drop bpmn.io properties
expect(taskBo.di.fill).to.equal(fill);
expect(taskBo.di.stroke).to.equal(stroke);
})

View File

@ -6,6 +6,8 @@ import {
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
var FUCHSIA_HEX = '#ff00ff',
YELLOW_HEX = '#ffff00';
describe('features/modeling - set color', function() {
@ -30,7 +32,7 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape, { fill: 'FUCHSIA' });
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
}));
@ -44,7 +46,7 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape);
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
}));
@ -59,8 +61,8 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape, { fill: 'YELLOW' });
// then
expect(taskShape.businessObject.di.fill).to.equal('YELLOW');
expect(taskShape.businessObject.di.stroke).to.equal('YELLOW');
expect(taskShape.businessObject.di.get('background-color')).to.equal(YELLOW_HEX);
expect(taskShape.businessObject.di.get('border-color')).to.equal(YELLOW_HEX);
}
));
@ -76,8 +78,8 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape, { fill: undefined });
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.stroke).to.equal('YELLOW');
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).to.equal(YELLOW_HEX);
}
));
@ -93,8 +95,8 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape);
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
}
));
@ -108,8 +110,8 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape, { stroke: 'FUCHSIA' });
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
}));
@ -123,8 +125,8 @@ describe('features/modeling - set color', function() {
modeling.setColor(taskShape);
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
}));
@ -139,10 +141,10 @@ describe('features/modeling - set color', function() {
modeling.setColor([ taskShape, startEventShape ], { fill: 'FUCHSIA' });
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(startEventShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('border-color')).not.to.exist;
}
));
@ -159,8 +161,8 @@ describe('features/modeling - set color', function() {
modeling.setColor([ taskShape, startEventShape ]);
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(startEventShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -179,10 +181,10 @@ describe('features/modeling - set color', function() {
], { stroke: 'FUCHSIA' });
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(startEventShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -202,11 +204,53 @@ describe('features/modeling - set color', function() {
modeling.setColor([ taskShape, startEventShape ]);
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(startEventShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('border-color')).not.to.exist;
}
));
it('should not set background-color on BPMNEdge', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
modeling.setColor(sequenceFlow, { fill: 'FUCHSIA' });
// then
expect(sequenceFlow.businessObject.di.get('background-color')).not.to.exist;
}));
it('should throw for an invalid color', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
function setColor() {
modeling.setColor(sequenceFlow, { fill: 'INVALID_COLOR' });
}
// then
expect(setColor).to.throw(/^invalid color value/);
}));
it('should throw for a color with alpha', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
function setColor() {
modeling.setColor(sequenceFlow, { fill: 'rgba(0, 255, 0, 0.5)' });
}
// then
expect(setColor).to.throw(/^invalid color value/);
}));
});
@ -223,7 +267,7 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -240,7 +284,7 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
@ -256,7 +300,7 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
}
));
@ -273,7 +317,7 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
@ -290,8 +334,8 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(startEventShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -309,8 +353,8 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
@ -330,8 +374,8 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(startEventShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('border-color')).not.to.exist;
}
));
@ -349,8 +393,8 @@ describe('features/modeling - set color', function() {
commandStack.undo();
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
@ -371,7 +415,7 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
@ -389,7 +433,7 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -406,7 +450,7 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
@ -424,7 +468,7 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
}
));
@ -442,8 +486,8 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.fill).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('background-color')).to.equal(FUCHSIA_HEX);
}
));
@ -462,8 +506,8 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.fill).not.to.exist;
expect(startEventShape.businessObject.di.fill).not.to.exist;
expect(taskShape.businessObject.di.get('background-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('background-color')).not.to.exist;
}
));
@ -481,8 +525,8 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(startEventShape.businessObject.di.stroke).to.equal('FUCHSIA');
expect(taskShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
expect(startEventShape.businessObject.di.get('border-color')).to.equal(FUCHSIA_HEX);
}
));
@ -504,11 +548,48 @@ describe('features/modeling - set color', function() {
commandStack.redo();
// then
expect(taskShape.businessObject.di.stroke).not.to.exist;
expect(startEventShape.businessObject.di.stroke).not.to.exist;
expect(taskShape.businessObject.di.get('border-color')).not.to.exist;
expect(startEventShape.businessObject.di.get('border-color')).not.to.exist;
}
));
});
// TODO @barmac: remove once we drop bpmn.io properties
describe('legacy', function() {
it('should set both BPMN in Color and bpmn.io properties on BPMNShape', inject(
function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1');
// when
modeling.setColor(taskShape, { stroke: '#abcdef', fill: '#fedcba' });
// then
expect(taskShape.businessObject.di.get('border-color')).to.eql('#abcdef');
expect(taskShape.businessObject.di.get('stroke')).to.eql('#abcdef');
expect(taskShape.businessObject.di.get('background-color')).to.eql('#fedcba');
expect(taskShape.businessObject.di.get('fill')).to.eql('#fedcba');
}
));
it('should set both BPMN in Color and bpmn.io properties on BPMNEdge', inject(
function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
// when
modeling.setColor(sequenceFlow, { stroke: '#abcdef' });
// then
expect(sequenceFlow.businessObject.di.get('border-color')).to.eql('#abcdef');
expect(sequenceFlow.businessObject.di.get('stroke')).to.eql('#abcdef');
}
));
});
});

View File

@ -1631,8 +1631,8 @@ describe('features/replace - bpmn replace', function() {
var newElementData = {
type: 'bpmn:UserTask'
},
fill = 'red',
stroke = 'green';
fill = '#ff0000',
stroke = '#00ff00';
modeling.setColor(task, { fill: fill, stroke: stroke });
@ -1642,6 +1642,10 @@ describe('features/replace - bpmn replace', function() {
// then
var businessObject = newElement.businessObject;
expect(businessObject.di.get('background-color')).to.equal(fill);
expect(businessObject.di.get('border-color')).to.equal(stroke);
// TODO @barmac: remove when we drop bpmn.io properties
expect(businessObject.di.fill).to.equal(fill);
expect(businessObject.di.stroke).to.equal(stroke);
}));