feat(lib): Promisify public APIs

This commit promisifies following APIs:

   BaseViewer#importXML
   BaseViewer#importDefinitions
   BaseViewer#open
   BaseViewer#saveXML
   BaseViewer#saveSVG
   Modeler#createDiagram

Related to https://github.com/bpmn-io/bpmn-js/issues/812

BREAKING CHANGES:

* Users are now expected to have Promises either by default or
polyfilled as the APIs return a Promise now.
This commit is contained in:
Nico Rehwaldt
2020-04-29 11:02:42 +02:00
committed by Nico Rehwaldt
parent ef2a6d0cd0
commit 04ca31fac9
23 changed files with 1961 additions and 1049 deletions

View File

@ -7,7 +7,6 @@
import {
assign,
find,
isFunction,
isNumber,
omit
} from 'min-dash';
@ -68,6 +67,21 @@ export default function BaseViewer(options) {
inherits(BaseViewer, Diagram);
/**
* The importXML result.
*
* @typedef {Object} ImportXMLResult
*
* @property {Array<string>} warnings
*/
/**
* The importXML error.
*
* @typedef {Error} ImportXMLError
*
* @property {Array<string>} warnings
*/
/**
* Parse and render a BPMN 2.0 diagram.
@ -89,65 +103,83 @@ inherits(BaseViewer, Diagram);
*
* @param {string} xml the BPMN 2.0 xml
* @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
* @param {Function} [done] invoked with (err, warnings=[])
*
* Returns {Promise<ImportXMLResult, ImportXMLError>}
*/
BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram, done) {
if (isFunction(bpmnDiagram)) {
done = bpmnDiagram;
bpmnDiagram = null;
}
// done is optional
done = done || function() {};
BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) {
var self = this;
// hook in pre-parse listeners +
// allow xml manipulation
xml = this._emit('import.parse.start', { xml: xml }) || xml;
return new Promise(function(resolve, reject) {
this._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) {
var definitions = result.rootElement;
var references = result.references;
var parseWarnings = result.warnings;
var elementsById = result.elementsById;
// hook in pre-parse listeners +
// allow xml manipulation
xml = self._emit('import.parse.start', { xml: xml }) || xml;
var context = {
elementsById: elementsById,
references: references,
warnings: parseWarnings
};
self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) {
var definitions = result.rootElement;
var references = result.references;
var parseWarnings = result.warnings;
var elementsById = result.elementsById;
// hook in post parse listeners +
// allow definitions manipulation
definitions = self._emit('import.parse.complete', {
error: null,
definitions: definitions,
context: context
}) || definitions;
var context = {
elementsById: elementsById,
references: references,
warnings: parseWarnings
};
self.importDefinitions(definitions, bpmnDiagram, function(err, importWarnings) {
var allWarnings = [].concat(parseWarnings, importWarnings || []);
// hook in post parse listeners +
// allow definitions manipulation
definitions = self._emit('import.parse.complete', {
error: null,
definitions: definitions,
context: context
}) || definitions;
self._emit('import.done', { error: err, warnings: allWarnings });
self.importDefinitions(definitions, bpmnDiagram).then(function(result) {
var allWarnings = [].concat(parseWarnings, result.warnings || []);
done(err, allWarnings);
self._emit('import.done', { error: null, warnings: allWarnings });
return resolve({ warnings: allWarnings });
}).catch(function(err) {
var allWarnings = [].concat(parseWarnings, err.warnings || []);
self._emit('import.done', { error: err, warnings: allWarnings });
return reject(addWarningsToError(err, allWarnings));
});
}).catch(function(err) {
self._emit('import.parse.complete', {
error: err
});
err = checkValidationError(err);
self._emit('import.done', { error: err, warnings: err.warnings });
return reject(err);
});
}).catch(function(err) {
self._emit('import.parse.complete', {
error: err
});
err = checkValidationError(err);
self._emit('import.done', { error: err, warnings: err.warnings });
return done(err, err.warnings);
});
});
/**
* The importDefinitions result.
*
* @typedef {Object} ImportDefinitionsResult
*
* @property {Array<string>} warnings
*/
/**
* The importDefinitions error.
*
* @typedef {Error} ImportDefinitionsError
*
* @property {Array<string>} warnings
*/
/**
* Import parsed definitions and render a BPMN 2.0 diagram.
*
@ -165,23 +197,45 @@ BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bp
*
* @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
* @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
* @param {Function} [done] invoked with (err, warnings=[])
*
* Returns {Promise<ImportDefinitionsResult, ImportDefinitionsError>}
*/
BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram, done) {
BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) {
if (isFunction(bpmnDiagram)) {
done = bpmnDiagram;
bpmnDiagram = null;
}
var self = this;
// done is optional
done = done || function() {};
return new Promise(function(resolve, reject) {
this._setDefinitions(definitions);
self._setDefinitions(definitions);
return this.open(bpmnDiagram, done);
self.open(bpmnDiagram).then(function(result) {
var warnings = result.warnings;
return resolve({ warnings: warnings });
}).catch(function(err) {
return reject(err);
});
});
});
/**
* The open result.
*
* @typedef {Object} OpenResult
*
* @property {Array<string>} warnings
*/
/**
* The open error.
*
* @typedef {Error} OpenError
*
* @property {Array<string>} warnings
*/
/**
* Open diagram of previously imported XML.
*
@ -198,45 +252,63 @@ BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDef
* You can use these events to hook into the life-cycle.
*
* @param {string|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
* @param {Function} [done] invoked with (err, warnings=[])
*
* Returns {Promise<OpenResult, OpenError>}
*/
BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId, done) {
if (isFunction(bpmnDiagramOrId)) {
done = bpmnDiagramOrId;
bpmnDiagramOrId = null;
}
BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) {
var definitions = this._definitions;
var bpmnDiagram = bpmnDiagramOrId;
// done is optional
done = done || function() {};
var self = this;
if (!definitions) {
return done(new Error('no XML imported'));
}
return new Promise(function(resolve, reject) {
if (!definitions) {
var err1 = new Error('no XML imported');
if (typeof bpmnDiagramOrId === 'string') {
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
if (!bpmnDiagram) {
return done(new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'));
return reject(addWarningsToError(err1, []));
}
}
// clear existing rendered diagram
// catch synchronous exceptions during #clear()
try {
this.clear();
} catch (error) {
return done(error);
}
if (typeof bpmnDiagramOrId === 'string') {
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
// perform graphical import
return importBpmnDiagram(this, definitions, bpmnDiagram, done);
if (!bpmnDiagram) {
var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
return reject(addWarningsToError(err2, []));
}
}
// clear existing rendered diagram
// catch synchronous exceptions during #clear()
try {
self.clear();
} catch (error) {
return reject(addWarningsToError(error, []));
}
// perform graphical import
importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) {
var warnings = result.warnings;
return resolve({ warnings: warnings });
}).catch(function(err) {
return reject(err);
});
});
});
/**
* The saveXML result.
*
* @typedef {Object} SaveXMLResult
*
* @property {string} xml
*/
/**
* Export the currently displayed BPMN 2.0 diagram as
* a BPMN 2.0 XML document.
@ -255,52 +327,63 @@ BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId,
* @param {boolean} [options.format=false] output formatted XML
* @param {boolean} [options.preamble=true] output preamble
*
* @param {Function} done invoked with (err, xml)
* Returns {Promise<SaveXMLResult, Error>}
*/
BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options, done) {
BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) {
if (!done) {
done = options;
options = {};
}
options = options || {};
var self = this;
var definitions = this._definitions;
if (!definitions) {
return done(new Error('no definitions loaded'));
}
return new Promise(function(resolve, reject) {
// allow to fiddle around with definitions
definitions = this._emit('saveXML.start', {
definitions: definitions
}) || definitions;
if (!definitions) {
var err = new Error('no definitions loaded');
this._moddle.toXML(definitions, options).then(function(result) {
var xml = result.xml;
try {
xml = self._emit('saveXML.serialized', {
error: null,
xml: xml
}) || xml;
self._emit('saveXML.done', {
error: null,
xml: xml
});
} catch (e) {
console.error('error in saveXML life-cycle listener', e);
return reject(err);
}
done(null, xml);
}).catch(function(err) {
done(err);
// allow to fiddle around with definitions
definitions = self._emit('saveXML.start', {
definitions: definitions
}) || definitions;
self._moddle.toXML(definitions, options).then(function(result) {
var xml = result.xml;
try {
xml = self._emit('saveXML.serialized', {
error: null,
xml: xml
}) || xml;
self._emit('saveXML.done', {
error: null,
xml: xml
});
} catch (e) {
console.error('error in saveXML life-cycle listener', e);
}
return resolve({ xml: xml });
}).catch(function(err) {
return reject(err);
});
});
});
/**
* The saveSVG result.
*
* @typedef {Object} SaveSVGResult
*
* @property {string} svg
*/
/**
* Export the currently displayed BPMN 2.0 diagram as
* an SVG image.
@ -315,49 +398,56 @@ BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options, do
* You can use these events to hook into the life-cycle.
*
* @param {Object} [options]
* @param {Function} done invoked with (err, svgStr)
*
* Returns {Promise<SaveSVGResult, Error>}
*/
BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options, done) {
BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) {
if (!done) {
done = options;
options = {};
}
options = options || {};
this._emit('saveSVG.start');
var self = this;
var svg, err;
return new Promise(function(resolve, reject) {
try {
var canvas = this.get('canvas');
self._emit('saveSVG.start');
var contentNode = canvas.getDefaultLayer(),
defsNode = domQuery('defs', canvas._svg);
var svg, err;
var contents = innerSVG(contentNode),
defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
try {
var canvas = self.get('canvas');
var bbox = contentNode.getBBox();
var contentNode = canvas.getDefaultLayer(),
defsNode = domQuery('defs', canvas._svg);
svg =
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<!-- created with bpmn-js / http://bpmn.io -->\n' +
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'width="' + bbox.width + '" height="' + bbox.height + '" ' +
'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
defs + contents +
'</svg>';
} catch (e) {
err = e;
}
var contents = innerSVG(contentNode),
defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
this._emit('saveSVG.done', {
error: err,
svg: svg
var bbox = contentNode.getBBox();
svg =
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<!-- created with bpmn-js / http://bpmn.io -->\n' +
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'width="' + bbox.width + '" height="' + bbox.height + '" ' +
'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
defs + contents +
'</svg>';
} catch (e) {
err = e;
}
self._emit('saveSVG.done', {
error: err,
svg: svg
});
if (!err) {
return resolve({ svg: svg });
}
return reject(err);
});
done(err, svg);
});
/**
@ -574,6 +664,11 @@ BaseViewer.prototype._modules = [];
// helpers ///////////////
function addWarningsToError(err, warningsAry) {
err.warnings = warningsAry;
return err;
}
function checkValidationError(err) {
// check if we can help the user by indicating wrong BPMN 2.0 xml