| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814 |
- /**
- * The code in the <project-logo></project-logo> area
- * must not be changed.
- *
- * @see http://bpmn.io/license for more information.
- */
- import {
- assign,
- find,
- isNumber,
- omit
- } from 'min-dash';
- import {
- domify,
- assignStyle,
- query as domQuery,
- remove as domRemove
- } from 'min-dom';
- import {
- innerSVG
- } from 'tiny-svg';
- import Diagram from 'diagram-js';
- import BpmnModdle from 'bpmn-moddle';
- import inherits from 'inherits-browser';
- import {
- importBpmnDiagram
- } from './import/Importer';
- /**
- * @template T
- *
- * @typedef {import('diagram-js/lib/core/EventBus').EventBusEventCallback<T>} EventBusEventCallback
- */
- /**
- * @typedef {import('didi').ModuleDeclaration} ModuleDeclaration
- *
- * @typedef {import('./model/Types').Moddle} Moddle
- * @typedef {import('./model/Types').ModdleElement} ModdleElement
- * @typedef {import('./model/Types').ModdleExtension} ModdleExtension
- *
- * @typedef { {
- * width?: number|string;
- * height?: number|string;
- * position?: string;
- * container?: string|HTMLElement;
- * moddleExtensions?: ModdleExtensions;
- * additionalModules?: ModuleDeclaration[];
- * } & Record<string, any> } BaseViewerOptions
- *
- * @typedef {Record<string, ModdleElement>} ModdleElementsById
- *
- * @typedef { {
- * [key: string]: ModdleExtension;
- * } } ModdleExtensions
- *
- * @typedef { {
- * warnings: string[];
- * } } ImportXMLResult
- *
- * @typedef {ImportXMLResult & Error} ImportXMLError
- *
- * @typedef {ImportXMLResult} ImportDefinitionsResult
- *
- * @typedef {ImportXMLError} ImportDefinitionsError
- *
- * @typedef {ImportXMLResult} OpenResult
- *
- * @typedef {ImportXMLError} OpenError
- *
- * @typedef { {
- * format?: boolean;
- * preamble?: boolean;
- * } } SaveXMLOptions
- *
- * @typedef { {
- * xml?: string;
- * error?: Error;
- * } } SaveXMLResult
- *
- * @typedef { {
- * svg: string;
- * } } SaveSVGResult
- *
- * @typedef { {
- * xml: string;
- * } } ImportParseStartEvent
- *
- * @typedef { {
- * error?: ImportXMLError;
- * definitions?: ModdleElement;
- * elementsById?: ModdleElementsById;
- * references?: ModdleElement[];
- * warnings: string[];
- * } } ImportParseCompleteEvent
- *
- * @typedef { {
- * error?: ImportXMLError;
- * warnings: string[];
- * } } ImportDoneEvent
- *
- * @typedef { {
- * definitions: ModdleElement;
- * } } SaveXMLStartEvent
- *
- * @typedef {SaveXMLResult} SaveXMLDoneEvent
- *
- * @typedef { {
- * error?: Error;
- * svg: string;
- * } } SaveSVGDoneEvent
- */
- /**
- * A base viewer for BPMN 2.0 diagrams.
- *
- * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
- * bundles that include actual features.
- *
- * @param {BaseViewerOptions} [options] The options to configure the viewer.
- */
- export default function BaseViewer(options) {
- /**
- * @type {BaseViewerOptions}
- */
- options = assign({}, DEFAULT_OPTIONS, options);
- /**
- * @type {Moddle}
- */
- this._moddle = this._createModdle(options);
- /**
- * @type {HTMLElement}
- */
- this._container = this._createContainer(options);
- /* <project-logo> */
- addProjectLogo(this._container);
- /* </project-logo> */
- this._init(this._container, this._moddle, options);
- }
- inherits(BaseViewer, Diagram);
- /**
- * Parse and render a BPMN 2.0 diagram.
- *
- * Once finished the viewer reports back the result to the
- * provided callback function with (err, warnings).
- *
- * ## Life-Cycle Events
- *
- * During import the viewer will fire life-cycle events:
- *
- * * import.parse.start (about to read model from XML)
- * * import.parse.complete (model read; may have worked or not)
- * * import.render.start (graphical import start)
- * * import.render.complete (graphical import finished)
- * * import.done (everything done)
- *
- * You can use these events to hook into the life-cycle.
- *
- * @throws {ImportXMLError} An error thrown during the import of the XML.
- *
- * @fires BaseViewer#ImportParseStartEvent
- * @fires BaseViewer#ImportParseCompleteEvent
- * @fires Importer#ImportRenderStartEvent
- * @fires Importer#ImportRenderCompleteEvent
- * @fires BaseViewer#ImportDoneEvent
- *
- * @param {string} xml The BPMN 2.0 XML to be imported.
- * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or Id of the BPMN diagram to open.
- *
- * @return {Promise<ImportXMLResult>} A promise resolving with warnings that were produced during the import.
- */
- BaseViewer.prototype.importXML = async function importXML(xml, bpmnDiagram) {
- const self = this;
- function ParseCompleteEvent(data) {
- return self.get('eventBus').createEvent(data);
- }
- let aggregatedWarnings = [];
- try {
- // hook in pre-parse listeners +
- // allow xml manipulation
- /**
- * A `import.parse.start` event.
- *
- * @event BaseViewer#ImportParseStartEvent
- * @type {ImportParseStartEvent}
- */
- xml = this._emit('import.parse.start', { xml: xml }) || xml;
- let parseResult;
- try {
- parseResult = await this._moddle.fromXML(xml, 'bpmn:Definitions');
- } catch (error) {
- this._emit('import.parse.complete', {
- error
- });
- throw error;
- }
- let definitions = parseResult.rootElement;
- const references = parseResult.references;
- const parseWarnings = parseResult.warnings;
- const elementsById = parseResult.elementsById;
- aggregatedWarnings = aggregatedWarnings.concat(parseWarnings);
- // hook in post parse listeners +
- // allow definitions manipulation
- /**
- * A `import.parse.complete` event.
- *
- * @event BaseViewer#ImportParseCompleteEvent
- * @type {ImportParseCompleteEvent}
- */
- definitions = this._emit('import.parse.complete', ParseCompleteEvent({
- error: null,
- definitions: definitions,
- elementsById: elementsById,
- references: references,
- warnings: aggregatedWarnings
- })) || definitions;
- const importResult = await this.importDefinitions(definitions, bpmnDiagram);
- aggregatedWarnings = aggregatedWarnings.concat(importResult.warnings);
- /**
- * A `import.parse.complete` event.
- *
- * @event BaseViewer#ImportDoneEvent
- * @type {ImportDoneEvent}
- */
- this._emit('import.done', { error: null, warnings: aggregatedWarnings });
- return { warnings: aggregatedWarnings };
- } catch (err) {
- let error = err;
- aggregatedWarnings = aggregatedWarnings.concat(error.warnings || []);
- addWarningsToError(error, aggregatedWarnings);
- error = checkValidationError(error);
- this._emit('import.done', { error, warnings: error.warnings });
- throw error;
- }
- };
- /**
- * Import parsed definitions and render a BPMN 2.0 diagram.
- *
- * Once finished the viewer reports back the result to the
- * provided callback function with (err, warnings).
- *
- * ## Life-Cycle Events
- *
- * During import the viewer will fire life-cycle events:
- *
- * * import.render.start (graphical import start)
- * * import.render.complete (graphical import finished)
- *
- * You can use these events to hook into the life-cycle.
- *
- * @throws {ImportDefinitionsError} An error thrown during the import of the definitions.
- *
- * @param {ModdleElement} definitions The definitions.
- * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or ID of the BPMN diagram to open.
- *
- * @return {Promise<ImportDefinitionsResult>} A promise resolving with warnings that were produced during the import.
- */
- BaseViewer.prototype.importDefinitions = async function importDefinitions(definitions, bpmnDiagram) {
- this._setDefinitions(definitions);
- const result = await this.open(bpmnDiagram);
- return { warnings: result.warnings };
- };
- /**
- * Open diagram of previously imported XML.
- *
- * Once finished the viewer reports back the result to the
- * provided callback function with (err, warnings).
- *
- * ## Life-Cycle Events
- *
- * During switch the viewer will fire life-cycle events:
- *
- * * import.render.start (graphical import start)
- * * import.render.complete (graphical import finished)
- *
- * You can use these events to hook into the life-cycle.
- *
- * @throws {OpenError} An error thrown during opening.
- *
- * @param {ModdleElement|string} bpmnDiagramOrId The diagram or Id of the BPMN diagram to open.
- *
- * @return {Promise<OpenResult>} A promise resolving with warnings that were produced during opening.
- */
- BaseViewer.prototype.open = async function open(bpmnDiagramOrId) {
- const definitions = this._definitions;
- let bpmnDiagram = bpmnDiagramOrId;
- if (!definitions) {
- const error = new Error('no XML imported');
- addWarningsToError(error, []);
- throw error;
- }
- if (typeof bpmnDiagramOrId === 'string') {
- bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
- if (!bpmnDiagram) {
- const error = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
- addWarningsToError(error, []);
- throw error;
- }
- }
- // clear existing rendered diagram
- // catch synchronous exceptions during #clear()
- try {
- this.clear();
- } catch (error) {
- addWarningsToError(error, []);
- throw error;
- }
- // perform graphical import
- const { warnings } = await importBpmnDiagram(this, definitions, bpmnDiagram);
- return { warnings };
- };
- /**
- * Export the currently displayed BPMN 2.0 diagram as
- * a BPMN 2.0 XML document.
- *
- * ## Life-Cycle Events
- *
- * During XML saving the viewer will fire life-cycle events:
- *
- * * saveXML.start (before serialization)
- * * saveXML.serialized (after xml generation)
- * * saveXML.done (everything done)
- *
- * You can use these events to hook into the life-cycle.
- *
- * @throws {Error} An error thrown during export.
- *
- * @fires BaseViewer#SaveXMLStart
- * @fires BaseViewer#SaveXMLDone
- *
- * @param {SaveXMLOptions} [options] The options.
- *
- * @return {Promise<SaveXMLResult>} A promise resolving with the XML.
- */
- BaseViewer.prototype.saveXML = async function saveXML(options) {
- options = options || {};
- let definitions = this._definitions,
- error, xml;
- try {
- if (!definitions) {
- throw new Error('no definitions loaded');
- }
- // allow to fiddle around with definitions
- /**
- * A `saveXML.start` event.
- *
- * @event BaseViewer#SaveXMLStartEvent
- * @type {SaveXMLStartEvent}
- */
- definitions = this._emit('saveXML.start', {
- definitions
- }) || definitions;
- const result = await this._moddle.toXML(definitions, options);
- xml = result.xml;
- xml = this._emit('saveXML.serialized', {
- xml
- }) || xml;
- } catch (err) {
- error = err;
- }
- const result = error ? { error } : { xml };
- /**
- * A `saveXML.done` event.
- *
- * @event BaseViewer#SaveXMLDoneEvent
- * @type {SaveXMLDoneEvent}
- */
- this._emit('saveXML.done', result);
- if (error) {
- throw error;
- }
- return result;
- };
- /**
- * Export the currently displayed BPMN 2.0 diagram as
- * an SVG image.
- *
- * ## Life-Cycle Events
- *
- * During SVG saving the viewer will fire life-cycle events:
- *
- * * saveSVG.start (before serialization)
- * * saveSVG.done (everything done)
- *
- * You can use these events to hook into the life-cycle.
- *
- * @throws {Error} An error thrown during export.
- *
- * @fires BaseViewer#SaveSVGDone
- *
- * @return {Promise<SaveSVGResult>} A promise resolving with the SVG.
- */
- BaseViewer.prototype.saveSVG = async function saveSVG() {
- this._emit('saveSVG.start');
- let svg, err;
- try {
- const canvas = this.get('canvas');
- const contentNode = canvas.getActiveLayer(),
- defsNode = domQuery('defs', canvas._svg);
- const contents = innerSVG(contentNode),
- defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
- const 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;
- }
- /**
- * A `saveSVG.done` event.
- *
- * @event BaseViewer#SaveSVGDoneEvent
- * @type {SaveSVGDoneEvent}
- */
- this._emit('saveSVG.done', {
- error: err,
- svg: svg
- });
- if (err) {
- throw err;
- }
- return { svg };
- };
- BaseViewer.prototype._setDefinitions = function(definitions) {
- this._definitions = definitions;
- };
- /**
- * Return modules to instantiate with.
- *
- * @return {ModuleDeclaration[]} The modules.
- */
- BaseViewer.prototype.getModules = function() {
- return this._modules;
- };
- /**
- * Remove all drawn elements from the viewer.
- *
- * After calling this method the viewer can still be reused for opening another
- * diagram.
- */
- BaseViewer.prototype.clear = function() {
- if (!this.getDefinitions()) {
- // no diagram to clear
- return;
- }
- // remove drawn elements
- Diagram.prototype.clear.call(this);
- };
- /**
- * Destroy the viewer instance and remove all its remainders from the document
- * tree.
- */
- BaseViewer.prototype.destroy = function() {
- // diagram destroy
- Diagram.prototype.destroy.call(this);
- // dom detach
- domRemove(this._container);
- };
- /**
- * Register an event listener.
- *
- * Remove an event listener via {@link BaseViewer#off}.
- *
- * @template T
- *
- * @param {string|string[]} events The event(s) to listen to.
- * @param {number} [priority] The priority with which to listen.
- * @param {EventBusEventCallback<T>} callback The callback.
- * @param {any} [that] Value of `this` the callback will be called with.
- */
- BaseViewer.prototype.on = function(events, priority, callback, that) {
- return this.get('eventBus').on(events, priority, callback, that);
- };
- /**
- * Remove an event listener.
- *
- * @param {string|string[]} events The event(s).
- * @param {Function} [callback] The callback.
- */
- BaseViewer.prototype.off = function(events, callback) {
- this.get('eventBus').off(events, callback);
- };
- /**
- * Attach the viewer to an HTML element.
- *
- * @param {HTMLElement} parentNode The parent node to attach to.
- */
- BaseViewer.prototype.attachTo = function(parentNode) {
- if (!parentNode) {
- throw new Error('parentNode required');
- }
- // ensure we detach from the
- // previous, old parent
- this.detach();
- // unwrap jQuery if provided
- if (parentNode.get && parentNode.constructor.prototype.jquery) {
- parentNode = parentNode.get(0);
- }
- if (typeof parentNode === 'string') {
- parentNode = domQuery(parentNode);
- }
- parentNode.appendChild(this._container);
- this._emit('attach', {});
- this.get('canvas').resized();
- };
- /**
- * Get the definitions model element.
- *
- * @return {ModdleElement} The definitions model element.
- */
- BaseViewer.prototype.getDefinitions = function() {
- return this._definitions;
- };
- /**
- * Detach the viewer.
- *
- * @fires BaseViewer#DetachEvent
- */
- BaseViewer.prototype.detach = function() {
- const container = this._container,
- parentNode = container.parentNode;
- if (!parentNode) {
- return;
- }
- /**
- * A `detach` event.
- *
- * @event BaseViewer#DetachEvent
- * @type {Object}
- */
- this._emit('detach', {});
- parentNode.removeChild(container);
- };
- BaseViewer.prototype._init = function(container, moddle, options) {
- const baseModules = options.modules || this.getModules(options),
- additionalModules = options.additionalModules || [],
- staticModules = [
- {
- bpmnjs: [ 'value', this ],
- moddle: [ 'value', moddle ]
- }
- ];
- const diagramModules = [].concat(staticModules, baseModules, additionalModules);
- const diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
- canvas: assign({}, options.canvas, { container: container }),
- modules: diagramModules
- });
- // invoke diagram constructor
- Diagram.call(this, diagramOptions);
- if (options && options.container) {
- this.attachTo(options.container);
- }
- };
- /**
- * Emit an event on the underlying {@link EventBus}
- *
- * @param {string} type
- * @param {Object} event
- *
- * @return {Object} The return value after calling all event listeners.
- */
- BaseViewer.prototype._emit = function(type, event) {
- return this.get('eventBus').fire(type, event);
- };
- /**
- * @param {BaseViewerOptions} options
- *
- * @return {HTMLElement}
- */
- BaseViewer.prototype._createContainer = function(options) {
- const container = domify('<div class="bjs-container"></div>');
- assignStyle(container, {
- width: ensureUnit(options.width),
- height: ensureUnit(options.height),
- position: options.position
- });
- return container;
- };
- /**
- * @param {BaseViewerOptions} options
- *
- * @return {Moddle}
- */
- BaseViewer.prototype._createModdle = function(options) {
- const moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
- return new BpmnModdle(moddleOptions);
- };
- 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
- // (in case he or the exporting tool did not get that right)
- const pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
- const match = pattern.exec(err.message);
- if (match) {
- err.message =
- 'unparsable content <' + match[1] + '> detected; ' +
- 'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
- }
- return err;
- }
- const DEFAULT_OPTIONS = {
- width: '100%',
- height: '100%',
- position: 'relative'
- };
- /**
- * Ensure the passed argument is a proper unit (defaulting to px)
- */
- function ensureUnit(val) {
- return val + (isNumber(val) ? 'px' : '');
- }
- /**
- * Find BPMNDiagram in definitions by ID
- *
- * @param {ModdleElement<Definitions>} definitions
- * @param {string} diagramId
- *
- * @return {ModdleElement<BPMNDiagram>|null}
- */
- function findBPMNDiagram(definitions, diagramId) {
- if (!diagramId) {
- return null;
- }
- return find(definitions.diagrams, function(element) {
- return element.id === diagramId;
- }) || null;
- }
- /* <project-logo> */
- import {
- open as openPoweredBy,
- BPMNIO_IMG,
- LOGO_STYLES,
- LINK_STYLES
- } from './util/PoweredByUtil';
- import {
- event as domEvent
- } from 'min-dom';
- /**
- * Adds the project logo to the diagram container as
- * required by the bpmn.io license.
- *
- * @see http://bpmn.io/license
- *
- * @param {Element} container
- */
- function addProjectLogo(container) {
- const img = BPMNIO_IMG;
- const linkMarkup =
- '<a href="http://bpmn.io" ' +
- 'target="_blank" ' +
- 'class="bjs-powered-by" ' +
- 'title="Powered by bpmn.io" ' +
- '>' +
- img +
- '</a>';
- const linkElement = domify(linkMarkup);
- assignStyle(domQuery('svg', linkElement), LOGO_STYLES);
- assignStyle(linkElement, LINK_STYLES, {
- position: 'absolute',
- bottom: '15px',
- right: '15px',
- zIndex: '100'
- });
- container.appendChild(linkElement);
- domEvent.bind(linkElement, 'click', function(event) {
- openPoweredBy();
- event.preventDefault();
- });
- }
- /* </project-logo> */
|