From 333219cb689015fc374b10bf2a44ff818c7c1107 Mon Sep 17 00:00:00 2001 From: Mikhail Rodichenko Date: Thu, 31 Aug 2023 14:47:30 +0200 Subject: [PATCH] GUI Wdl pipelines graph: actual WDL versions support, general improvements --- client/public/index.html | 2 + client/public/pipeline-builder/pipeline.css | 391 + .../public/pipeline-builder/pipeline.css.map | 1 + client/public/pipeline-builder/pipeline.js | 113872 +++++++++++++++ .../public/pipeline-builder/pipeline.js.map | 1 + .../public/pipeline-builder/pipeline.min.js | 109 + .../pipeline-builder/pipeline.min.js.map | 1 + .../version/graph/visualization/Graph.css | 49 +- .../version/graph/visualization/Graph.js | 4 +- .../version/graph/visualization/WdlGraph.js | 1482 +- .../visualization/forms/WDLItemProperties.js | 404 - .../form-items/WDLInstanceTypeFormItem.js | 176 - .../forms/form-items/WDLItemPortFormItem.js | 298 - .../forms/form-items/WDLItemPortsFormItem.js | 337 - .../form-items/WDLRuntimeDockerFormItem.js | 162 - .../wdl-document-properties/index.js | 136 + .../wdl-document-properties.css | 42 + .../forms/form-items/wdl-issues/index.js | 99 + .../form-items/wdl-issues/wdl-issues.css | 30 + .../forms/form-items/wdl-parameter/index.js | 223 + .../wdl-parameter/wdl-parameter.css | 54 + .../forms/form-items/wdl-runtime-docker.js | 123 + .../forms/form-items/wdl-runtime-node.js | 118 + .../forms/form-items/wdl-tasks/index.js | 312 + .../forms/form-items/wdl-tasks/wdl-tasks.css | 47 + .../forms/form-items/wdl-type-selector.js | 109 + .../utilities/extract-entity-properties.js | 247 + .../forms/utilities/string-utilities.js | 33 + .../forms/utilities/workflow-utilities.js | 167 + .../forms/wdl-properties-form.css | 101 + .../forms/wdl-properties-form.js | 1063 + .../special/instance-type-info/index.js | 80 +- client/src/themes/default.theme.less | 1 + client/src/themes/index.js | 10 +- client/src/themes/styles/wdl.less | 112 + .../themes/utilities/theme.less.template.js | 77 + client/src/utils/pipeline-builder/index.js | 93 + 37 files changed, 118210 insertions(+), 2356 deletions(-) create mode 100644 client/public/pipeline-builder/pipeline.css create mode 100644 client/public/pipeline-builder/pipeline.css.map create mode 100644 client/public/pipeline-builder/pipeline.js create mode 100644 client/public/pipeline-builder/pipeline.js.map create mode 100644 client/public/pipeline-builder/pipeline.min.js create mode 100644 client/public/pipeline-builder/pipeline.min.js.map delete mode 100644 client/src/components/pipelines/version/graph/visualization/forms/WDLItemProperties.js delete mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/WDLInstanceTypeFormItem.js delete mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/WDLItemPortFormItem.js delete mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/WDLItemPortsFormItem.js delete mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/WDLRuntimeDockerFormItem.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-document-properties/index.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-document-properties/wdl-document-properties.css create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-issues/index.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-issues/wdl-issues.css create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-parameter/index.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-parameter/wdl-parameter.css create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-runtime-docker.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-runtime-node.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-tasks/index.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-tasks/wdl-tasks.css create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/form-items/wdl-type-selector.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/utilities/extract-entity-properties.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/utilities/string-utilities.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/utilities/workflow-utilities.js create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/wdl-properties-form.css create mode 100644 client/src/components/pipelines/version/graph/visualization/forms/wdl-properties-form.js create mode 100644 client/src/themes/styles/wdl.less create mode 100644 client/src/utils/pipeline-builder/index.js diff --git a/client/public/index.html b/client/public/index.html index dd8d463cfe..aea0d7fc3c 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -6,8 +6,10 @@ + + { top: 5, right: 5, bottom: 5, left: 5 } +// normalizeSides({ horizontal: 5 }) --> { top: 0, right: 5, bottom: 0, left: 5 } +// normalizeSides({ left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 } +// normalizeSides({ horizontal: 10, left: 5 }) --> { top: 0, right: 10, bottom: 0, left: 5 } +// normalizeSides({ horizontal: 0, left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 } +const normalizeSides = function(box) { + + if (Object(box) !== box) { // `box` is not an object + var val = 0; // `val` left as 0 if `box` cannot be understood as finite number + if (isFinite(box)) val = +box; // actually also accepts string numbers (e.g. '100') + + return { top: val, right: val, bottom: val, left: val }; + } + + // `box` is an object + var top, right, bottom, left; + top = right = bottom = left = 0; + + if (isFinite(box.vertical)) top = bottom = +box.vertical; + if (isFinite(box.horizontal)) right = left = +box.horizontal; + + if (isFinite(box.top)) top = +box.top; // overwrite vertical + if (isFinite(box.right)) right = +box.right; // overwrite horizontal + if (isFinite(box.bottom)) bottom = +box.bottom; // overwrite vertical + if (isFinite(box.left)) left = +box.left; // overwrite horizontal + + return { top: top, right: right, bottom: bottom, left: left }; +}; + +const timing = { + + linear: function(t) { + return t; + }, + + quad: function(t) { + return t * t; + }, + + cubic: function(t) { + return t * t * t; + }, + + inout: function(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t; + var t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + }, + + exponential: function(t) { + return Math.pow(2, 10 * (t - 1)); + }, + + bounce: function(t) { + for (var a = 0, b = 1; 1; a += b, b /= 2) { + if (t >= (7 - 4 * a) / 11) { + var q = (11 - 6 * a - 11 * t) / 4; + return -q * q + b * b; + } + } + }, + + reverse: function(f) { + return function(t) { + return 1 - f(1 - t); + }; + }, + + reflect: function(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); + }; + }, + + clamp: function(f, n, x) { + n = n || 0; + x = x || 1; + return function(t) { + var r = f(t); + return r < n ? n : r > x ? x : r; + }; + }, + + back: function(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + }, + + elastic: function(x) { + if (!x) x = 1.5; + return function(t) { + return Math.pow(2, 10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t); + }; + } +}; + +const interpolate = { + + number: function(a, b) { + var d = b - a; + return function(t) { + return a + d * t; + }; + }, + + object: function(a, b) { + var s = Object.keys(a); + return function(t) { + var i, p; + var r = {}; + for (i = s.length - 1; i != -1; i--) { + p = s[i]; + r[p] = a[p] + (b[p] - a[p]) * t; + } + return r; + }; + }, + + hexColor: function(a, b) { + + var ca = parseInt(a.slice(1), 16); + var cb = parseInt(b.slice(1), 16); + var ra = ca & 0x0000ff; + var rd = (cb & 0x0000ff) - ra; + var ga = ca & 0x00ff00; + var gd = (cb & 0x00ff00) - ga; + var ba = ca & 0xff0000; + var bd = (cb & 0xff0000) - ba; + + return function(t) { + + var r = (ra + rd * t) & 0x000000ff; + var g = (ga + gd * t) & 0x0000ff00; + var b = (ba + bd * t) & 0x00ff0000; + + return '#' + (1 << 24 | r | g | b).toString(16).slice(1); + }; + }, + + unit: function(a, b) { + + var r = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/; + var ma = r.exec(a); + var mb = r.exec(b); + var p = mb[1].indexOf('.'); + var f = p > 0 ? mb[1].length - p - 1 : 0; + a = +ma[1]; + var d = +mb[1] - a; + var u = ma[2]; + + return function(t) { + return (a + d * t).toFixed(f) + u; + }; + } +}; + +// SVG filters. +// (values in parentheses are default values) +const util_filter = { + + // `color` ... outline color ('blue') + // `width`... outline width (1) + // `opacity` ... outline opacity (1) + // `margin` ... gap between outline and the element (2) + outline: function(args) { + + var tpl = ''; + + var margin = Number.isFinite(args.margin) ? args.margin : 2; + var width = Number.isFinite(args.width) ? args.width : 1; + + return template(tpl)({ + color: args.color || 'blue', + opacity: Number.isFinite(args.opacity) ? args.opacity : 1, + outerRadius: margin + width, + innerRadius: margin + }); + }, + + // `color` ... color ('red') + // `width`... width (1) + // `blur` ... blur (0) + // `opacity` ... opacity (1) + highlight: function(args) { + + var tpl = ''; + + return template(tpl)({ + color: args.color || 'red', + width: Number.isFinite(args.width) ? args.width : 1, + blur: Number.isFinite(args.blur) ? args.blur : 0, + opacity: Number.isFinite(args.opacity) ? args.opacity : 1 + }); + }, + + // `x` ... horizontal blur (2) + // `y` ... vertical blur (optional) + blur: function(args) { + + var x = Number.isFinite(args.x) ? args.x : 2; + + return template('')({ + stdDeviation: Number.isFinite(args.y) ? [x, args.y] : x + }); + }, + + // `dx` ... horizontal shift (0) + // `dy` ... vertical shift (0) + // `blur` ... blur (4) + // `color` ... color ('black') + // `opacity` ... opacity (1) + dropShadow: function(args) { + + var tpl = 'SVGFEDropShadowElement' in window + ? '' + : ''; + + return template(tpl)({ + dx: args.dx || 0, + dy: args.dy || 0, + opacity: Number.isFinite(args.opacity) ? args.opacity : 1, + color: args.color || 'black', + blur: Number.isFinite(args.blur) ? args.blur : 4 + }); + }, + + // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely grayscale. A value of 0 leaves the input unchanged. + grayscale: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return template('')({ + a: 0.2126 + 0.7874 * (1 - amount), + b: 0.7152 - 0.7152 * (1 - amount), + c: 0.0722 - 0.0722 * (1 - amount), + d: 0.2126 - 0.2126 * (1 - amount), + e: 0.7152 + 0.2848 * (1 - amount), + f: 0.0722 - 0.0722 * (1 - amount), + g: 0.2126 - 0.2126 * (1 - amount), + h: 0.0722 + 0.9278 * (1 - amount) + }); + }, + + // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely sepia. A value of 0 leaves the input unchanged. + sepia: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return template('')({ + a: 0.393 + 0.607 * (1 - amount), + b: 0.769 - 0.769 * (1 - amount), + c: 0.189 - 0.189 * (1 - amount), + d: 0.349 - 0.349 * (1 - amount), + e: 0.686 + 0.314 * (1 - amount), + f: 0.168 - 0.168 * (1 - amount), + g: 0.272 - 0.272 * (1 - amount), + h: 0.534 - 0.534 * (1 - amount), + i: 0.131 + 0.869 * (1 - amount) + }); + }, + + // `amount` ... the proportion of the conversion (1). A value of 0 is completely un-saturated. A value of 1 (default) leaves the input unchanged. + saturate: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return template('')({ + amount: 1 - amount + }); + }, + + // `angle` ... the number of degrees around the color circle the input samples will be adjusted (0). + hueRotate: function(args) { + + return template('')({ + angle: args.angle || 0 + }); + }, + + // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely inverted. A value of 0 leaves the input unchanged. + invert: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return template('')({ + amount: amount, + amount2: 1 - amount + }); + }, + + // `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged. + brightness: function(args) { + + return template('')({ + amount: Number.isFinite(args.amount) ? args.amount : 1 + }); + }, + + // `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged. + contrast: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return template('')({ + amount: amount, + amount2: .5 - amount / 2 + }); + } +}; + +const format = { + + // Formatting numbers via the Python Format Specification Mini-language. + // See http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // Heavilly inspired by the D3.js library implementation. + number: function(specifier, value, locale) { + + locale = locale || { + + currency: ['$', ''], + decimal: '.', + thousands: ',', + grouping: [3] + }; + + // See Python format specification mini-language: http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // [[fill]align][sign][symbol][0][width][,][.precision][type] + var re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + + var match = re.exec(specifier); + var fill = match[1] || ' '; + var align = match[2] || '>'; + var sign = match[3] || ''; + var symbol = match[4] || ''; + var zfill = match[5]; + var width = +match[6]; + var comma = match[7]; + var precision = match[8]; + var type = match[9]; + var scale = 1; + var prefix = ''; + var suffix = ''; + var integer = false; + + if (precision) precision = +precision.substring(1); + + if (zfill || fill === '0' && align === '=') { + zfill = fill = '0'; + align = '='; + if (comma) width -= Math.floor((width - 1) / 4); + } + + switch (type) { + case 'n': + comma = true; + type = 'g'; + break; + case '%': + scale = 100; + suffix = '%'; + type = 'f'; + break; + case 'p': + scale = 100; + suffix = '%'; + type = 'r'; + break; + case 'b': + case 'o': + case 'x': + case 'X': + if (symbol === '#') prefix = '0' + type.toLowerCase(); + break; + case 'c': + case 'd': + integer = true; + precision = 0; + break; + case 's': + scale = -1; + type = 'r'; + break; + } + + if (symbol === '$') { + prefix = locale.currency[0]; + suffix = locale.currency[1]; + } + + // If no precision is specified for `'r'`, fallback to general notation. + if (type == 'r' && !precision) type = 'g'; + + // Ensure that the requested precision is in the supported range. + if (precision != null) { + if (type == 'g') precision = Math.max(1, Math.min(21, precision)); + else if (type == 'e' || type == 'f') precision = Math.max(0, Math.min(20, precision)); + } + + var zcomma = zfill && comma; + + // Return the empty string for floats formatted as ints. + if (integer && (value % 1)) return ''; + + // Convert negative to positive, and record the sign prefix. + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, '-') : sign; + + var fullSuffix = suffix; + + // Apply the scale, computing it from the value's exponent for si format. + // Preserve the existing suffix, if any, such as the currency symbol. + if (scale < 0) { + var unit = this.prefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + + // Convert to the desired precision. + value = this.convert(type, value, precision); + + // Break the value into the integer part (before) and decimal part (after). + var i = value.lastIndexOf('.'); + var before = i < 0 ? value : value.substring(0, i); + var after = i < 0 ? '' : locale.decimal + value.substring(i + 1); + + function formatGroup(value) { + + var i = value.length; + var t = []; + var j = 0; + var g = locale.grouping[0]; + while (i > 0 && g > 0) { + t.push(value.substring(i -= g, i + g)); + g = locale.grouping[j = (j + 1) % locale.grouping.length]; + } + return t.reverse().join(locale.thousands); + } + + // If the fill character is not `'0'`, grouping is applied before padding. + if (!zfill && comma && locale.grouping) { + + before = formatGroup(before); + } + + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length); + var padding = length < width ? new Array(length = width - length + 1).join(fill) : ''; + + // If the fill character is `'0'`, grouping is applied after padding. + if (zcomma) before = formatGroup(padding + before); + + // Apply prefix. + negative += prefix; + + // Rejoin integer and decimal parts. + value = before + after; + + return (align === '<' ? negative + value + padding + : align === '>' ? padding + negative + value + : align === '^' ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) + : negative + (zcomma ? value : padding + value)) + fullSuffix; + }, + + // Formatting string via the Python Format string. + // See https://docs.python.org/2/library/string.html#format-string-syntax) + string: function(formatString, value) { + + var fieldDelimiterIndex; + var fieldDelimiter = '{'; + var endPlaceholder = false; + var formattedStringArray = []; + + while ((fieldDelimiterIndex = formatString.indexOf(fieldDelimiter)) !== -1) { + + var pieceFormattedString, formatSpec, fieldName; + + pieceFormattedString = formatString.slice(0, fieldDelimiterIndex); + + if (endPlaceholder) { + formatSpec = pieceFormattedString.split(':'); + fieldName = formatSpec.shift().split('.'); + pieceFormattedString = value; + + for (var i = 0; i < fieldName.length; i++) + pieceFormattedString = pieceFormattedString[fieldName[i]]; + + if (formatSpec.length) + pieceFormattedString = this.number(formatSpec, pieceFormattedString); + } + + formattedStringArray.push(pieceFormattedString); + + formatString = formatString.slice(fieldDelimiterIndex + 1); + endPlaceholder = !endPlaceholder; + fieldDelimiter = (endPlaceholder) ? '}' : '{'; + } + formattedStringArray.push(formatString); + + return formattedStringArray.join(''); + }, + + convert: function(type, value, precision) { + + switch (type) { + case 'b': + return value.toString(2); + case 'c': + return String.fromCharCode(value); + case 'o': + return value.toString(8); + case 'x': + return value.toString(16); + case 'X': + return value.toString(16).toUpperCase(); + case 'g': + return value.toPrecision(precision); + case 'e': + return value.toExponential(precision); + case 'f': + return value.toFixed(precision); + case 'r': + return (value = this.round(value, this.precision(value, precision))).toFixed(Math.max(0, Math.min(20, this.precision(value * (1 + 1e-15), precision)))); + default: + return value + ''; + } + }, + + round: function(value, precision) { + + return precision + ? Math.round(value * (precision = Math.pow(10, precision))) / precision + : Math.round(value); + }, + + precision: function(value, precision) { + + return precision - (value ? Math.ceil(Math.log(value) / Math.LN10) : 1); + }, + + prefix: function(value, precision) { + + var prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'].map(function(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + }); + + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = this.round(value, this.precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return prefixes[8 + i / 3]; + } +}; + +/* + Pre-compile the HTML to be used as a template. +*/ +const template = function(html) { + + /* + Must support the variation in templating syntax found here: + https://lodash.com/docs#template + */ + var regex = /<%= ([^ ]+) %>|\$\{ ?([^{} ]+) ?\}|\{\{([^{} ]+)\}\}/g; + + return function(data) { + + data = data || {}; + + return html.replace(regex, function(match) { + + var args = Array.from(arguments); + var attr = args.slice(1, 4).find(function(_attr) { + return !!_attr; + }); + + var attrArray = attr.split('.'); + var value = data[attrArray.shift()]; + + while (value !== undefined && attrArray.length) { + value = value[attrArray.shift()]; + } + + return value !== undefined ? value : ''; + }); + }; +}; + +/** + * @param {Element} el Element, which content is intent to display in full-screen mode, 'window.top.document.body' is default. + */ +const toggleFullScreen = function(el) { + + var topDocument = window.top.document; + el = el || topDocument.body; + + function prefixedResult(el, prop) { + + var prefixes = ['webkit', 'moz', 'ms', 'o', '']; + for (var i = 0; i < prefixes.length; i++) { + var prefix = prefixes[i]; + var propName = prefix ? (prefix + prop) : (prop.substr(0, 1).toLowerCase() + prop.substr(1)); + if (el[propName] !== undefined) { + return isFunction(el[propName]) ? el[propName]() : el[propName]; + } + } + } + + if (prefixedResult(topDocument, 'FullscreenElement') || prefixedResult(topDocument, 'FullScreenElement')) { + prefixedResult(topDocument, 'ExitFullscreen') || // Spec. + prefixedResult(topDocument, 'CancelFullScreen'); // Firefox + } else { + prefixedResult(el, 'RequestFullscreen') || // Spec. + prefixedResult(el, 'RequestFullScreen'); // Firefox + } +}; + + + +const noop = function() { +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/layout/ports/port.mjs + + + + +function portTransformAttrs(point, angle, opt) { + + var trans = point.toJSON(); + + trans.angle = angle || 0; + + return defaults({}, opt, trans); +} + +function lineLayout(ports, p1, p2, elBBox) { + return ports.map(function(port, index, ports) { + var p = this.pointAt(((index + 0.5) / ports.length)); + // `dx`,`dy` per port offset option + if (port.dx || port.dy) { + p.offset(port.dx || 0, port.dy || 0); + } + return portTransformAttrs(p.round(), 0, argTransform(elBBox, port)); + }, line_line(p1, p2)); +} + +function ellipseLayout(ports, elBBox, startAngle, stepFn) { + + var center = elBBox.center(); + var ratio = elBBox.width / elBBox.height; + var p1 = elBBox.topMiddle(); + + var ellipse = Ellipse.fromRect(elBBox); + + return ports.map(function(port, index, ports) { + + var angle = startAngle + stepFn(index, ports.length); + var p2 = p1.clone() + .rotate(center, -angle) + .scale(ratio, 1, center); + + var theta = port.compensateRotation ? -ellipse.tangentTheta(p2) : 0; + + // `dx`,`dy` per port offset option + if (port.dx || port.dy) { + p2.offset(port.dx || 0, port.dy || 0); + } + + // `dr` delta radius option + if (port.dr) { + p2.move(center, port.dr); + } + + return portTransformAttrs(p2.round(), theta, argTransform(elBBox, port)); + }); +} + + +function argTransform(bbox, args) { + let { x, y, angle } = args; + if (isPercentage(x)) { + x = parseFloat(x) / 100 * bbox.width; + } else if (isCalcAttribute(x)) { + x = Number(evalCalcAttribute(x, bbox)); + } + if (isPercentage(y)) { + y = parseFloat(y) / 100 * bbox.height; + } else if (isCalcAttribute(y)) { + y = Number(evalCalcAttribute(y, bbox)); + } + return { x, y, angle }; +} + +// Creates a point stored in arguments +function argPoint(bbox, args) { + const { x, y } = argTransform(bbox, args); + return new Point(x || 0, y || 0); +} + + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const absolute = function(ports, elBBox) { + return ports.map(port => { + const transformation = argPoint(elBBox, port).round().toJSON(); + transformation.angle = port.angle || 0; + return transformation; + }); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const fn = function(ports, elBBox, opt) { + return opt.fn(ports, elBBox, opt); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const line = function(ports, elBBox, opt) { + + var start = argPoint(elBBox, opt.start || elBBox.origin()); + var end = argPoint(elBBox, opt.end || elBBox.corner()); + + return lineLayout(ports, start, end, elBBox); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const left = function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.origin(), elBBox.bottomLeft(), elBBox); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const right = function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.topRight(), elBBox.corner(), elBBox); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const port_top = function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.origin(), elBBox.topRight(), elBBox); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ +const bottom = function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.bottomLeft(), elBBox.corner(), elBBox); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt Group options + * @returns {Array} + */ +const ellipseSpread = function(ports, elBBox, opt) { + + var startAngle = opt.startAngle || 0; + var stepAngle = opt.step || 360 / ports.length; + + return ellipseLayout(ports, elBBox, startAngle, function(index) { + return index * stepAngle; + }); +}; + +/** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt Group options + * @returns {Array} + */ +const port_ellipse = function(ports, elBBox, opt) { + + var startAngle = opt.startAngle || 0; + var stepAngle = opt.step || 20; + + return ellipseLayout(ports, elBBox, startAngle, function(index, count) { + return (index + 0.5 - count / 2) * stepAngle; + }); +}; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/layout/ports/portLabel.mjs + + + +function labelAttributes(opt1, opt2) { + + // use value from `opt2` if it is missing in `opt1` + // use value from this object if it is missing in `opt2` as well + return defaultsDeep({}, opt1, opt2, { + x: 0, + y: 0, + angle: 0, + attrs: {} + }); +} + +function getBBoxAngles(elBBox) { + + var center = elBBox.center(); + + var tl = center.theta(elBBox.origin()); + var bl = center.theta(elBBox.bottomLeft()); + var br = center.theta(elBBox.corner()); + var tr = center.theta(elBBox.topRight()); + + return [tl, tr, br, bl]; +} + +function outsideLayout(portPosition, elBBox, autoOrient, opt) { + + opt = defaults({}, opt, { offset: 15 }); + var angle = elBBox.center().theta(portPosition); + + var tx, ty, y, textAnchor; + var offset = opt.offset; + var orientAngle = 0; + + const [topLeftAngle, bottomLeftAngle, bottomRightAngle, topRightAngle] = getBBoxAngles(elBBox); + if ((angle < bottomLeftAngle) || (angle > bottomRightAngle)) { + y = '.3em'; + tx = offset; + ty = 0; + textAnchor = 'start'; + } else if (angle < topLeftAngle) { + tx = 0; + ty = -offset; + if (autoOrient) { + orientAngle = -90; + textAnchor = 'start'; + y = '.3em'; + } else { + textAnchor = 'middle'; + y = '0'; + } + } else if (angle < topRightAngle) { + y = '.3em'; + tx = -offset; + ty = 0; + textAnchor = 'end'; + } else { + tx = 0; + ty = offset; + if (autoOrient) { + orientAngle = 90; + textAnchor = 'start'; + y = '.3em'; + } else { + textAnchor = 'middle'; + y = '.6em'; + } + } + + var round = Math.round; + return labelAttributes(opt, { + x: round(tx), + y: round(ty), + angle: orientAngle, + attrs: { labelText: { y, textAnchor }} + }); +} + +function insideLayout(portPosition, elBBox, autoOrient, opt) { + + opt = defaults({}, opt, { offset: 15 }); + var angle = elBBox.center().theta(portPosition); + + var tx, ty, y, textAnchor; + var offset = opt.offset; + var orientAngle = 0; + + const [topLeftAngle, bottomLeftAngle, bottomRightAngle, topRightAngle] = getBBoxAngles(elBBox); + if ((angle < bottomLeftAngle) || (angle > bottomRightAngle)) { + y = '.3em'; + tx = -offset; + ty = 0; + textAnchor = 'end'; + } else if (angle < topLeftAngle) { + tx = 0; + ty = offset; + if (autoOrient) { + orientAngle = 90; + textAnchor = 'start'; + y = '.3em'; + } else { + textAnchor = 'middle'; + y = '.6em'; + } + } else if (angle < topRightAngle) { + y = '.3em'; + tx = offset; + ty = 0; + textAnchor = 'start'; + } else { + tx = 0; + ty = -offset; + if (autoOrient) { + orientAngle = -90; + textAnchor = 'start'; + y = '.3em'; + } else { + textAnchor = 'middle'; + y = '0'; + } + } + + var round = Math.round; + return labelAttributes(opt, { + x: round(tx), + y: round(ty), + angle: orientAngle, + attrs: { labelText: { y, textAnchor }} + }); +} + +function radialLayout(portCenterOffset, autoOrient, opt) { + + opt = defaults({}, opt, { offset: 20 }); + + var origin = point_point(0, 0); + var angle = -portCenterOffset.theta(origin); + var orientAngle = angle; + var offset = portCenterOffset.clone() + .move(origin, opt.offset) + .difference(portCenterOffset) + .round(); + + var y = '.3em'; + var textAnchor; + + if ((angle + 90) % 180 === 0) { + textAnchor = autoOrient ? 'end' : 'middle'; + if (!autoOrient && angle === -270) { + y = '0em'; + } + } else if (angle > -270 && angle < -90) { + textAnchor = 'start'; + orientAngle = angle - 180; + } else { + textAnchor = 'end'; + } + + var round = Math.round; + return labelAttributes(opt, { + x: round(offset.x), + y: round(offset.y), + angle: ((autoOrient) ? orientAngle : 0), + attrs: { labelText: { y, textAnchor }} + }); +} + +const manual = function(_portPosition, _elBBox, opt) { + return labelAttributes(opt); +}; + +const portLabel_left = function(portPosition, elBBox, opt) { + return labelAttributes(opt, { + x: -15, + attrs: { labelText: { y: '.3em', textAnchor: 'end' }}, + }); +}; + +const portLabel_right = function(portPosition, elBBox, opt) { + return labelAttributes(opt, { + x: 15, + attrs: { labelText: { y: '.3em', textAnchor: 'start' }}, + }); +}; + +const portLabel_top = function(portPosition, elBBox, opt) { + return labelAttributes(opt, { + y: -15, + attrs: { labelText: { y: '0', textAnchor: 'middle' }}, + }); +}; + +const portLabel_bottom = function(portPosition, elBBox, opt) { + return labelAttributes(opt, { + y: 15, + attrs: { labelText: { y: '.6em', textAnchor: 'middle' }}, + }); +}; + +const outsideOriented = function(portPosition, elBBox, opt) { + return outsideLayout(portPosition, elBBox, true, opt); +}; + +const outside = function(portPosition, elBBox, opt) { + return outsideLayout(portPosition, elBBox, false, opt); +}; + +const insideOriented = function(portPosition, elBBox, opt) { + return insideLayout(portPosition, elBBox, true, opt); +}; + +const inside = function(portPosition, elBBox, opt) { + return insideLayout(portPosition, elBBox, false, opt); +}; + +const radial = function(portPosition, elBBox, opt) { + return radialLayout(portPosition.difference(elBBox.center()), false, opt); +}; + +const radialOriented = function(portPosition, elBBox, opt) { + return radialLayout(portPosition.difference(elBBox.center()), true, opt); +}; + +// EXTERNAL MODULE: ./node_modules/backbone/backbone.js +var backbone = __webpack_require__(2316); +;// CONCATENATED MODULE: ./node_modules/jointjs/src/util/getRectPoint.mjs + + +const Positions = { + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right', + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + CENTER: 'center', +}; + +function getRectPoint(rect, position) { + const r = new Rect(rect); + switch (position) { + case undefined: + throw new Error('Position required'); + + // Middle Points + case Positions.LEFT: + case 'leftMiddle': + return r.leftMiddle(); + + case Positions.RIGHT: + case 'rightMiddle': + return r.rightMiddle(); + + case Positions.TOP: + case 'topMiddle': + return r.topMiddle(); + + case Positions.BOTTOM: + case 'bottomMiddle': + return r.bottomMiddle(); + + // Corners + case Positions.TOP_LEFT: + case 'topLeft': + case 'origin': + return r.topLeft(); + + case Positions.TOP_RIGHT: + case 'topRight': + return r.topRight(); + + case Positions.BOTTOM_LEFT: + case 'bottomLeft': + return r.bottomLeft(); + + case Positions.BOTTOM_RIGHT: + case 'bottomRight': + case 'corner': + return r.bottomRight(); + + // Center + case Positions.CENTER: + return r.center(); + + // TODO: calc(), percentage etc. + default: + throw new Error(`Unknown position: ${position}`); + } +} + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/util/cloneCells.mjs + + +// Clone `cells` returning an object that maps the original cell ID to the clone. The number +// of clones is exactly the same as the `cells.length`. +// This function simply clones all the `cells`. However, it also reconstructs +// all the `source/target` and `parent/embed` references within the `cells`. +// This is the main difference from the `cell.clone()` method. The +// `cell.clone()` method works on one single cell only. +// For example, for a graph: `A --- L ---> B`, `cloneCells([A, L, B])` +// returns `[A2, L2, B2]` resulting to a graph: `A2 --- L2 ---> B2`, i.e. +// the source and target of the link `L2` is changed to point to `A2` and `B2`. +function cloneCells(cells) { + + cells = uniq(cells); + + // A map of the form [original cell ID] -> [clone] helping + // us to reconstruct references for source/target and parent/embeds. + // This is also the returned value. + const cloneMap = toArray(cells).reduce(function(map, cell) { + map[cell.id] = cell.clone(); + return map; + }, {}); + + toArray(cells).forEach(function(cell) { + + const clone = cloneMap[cell.id]; + // assert(clone exists) + + if (clone.isLink()) { + const source = clone.source(); + const target = clone.target(); + if (source.id && cloneMap[source.id]) { + // Source points to an element and the element is among the clones. + // => Update the source of the cloned link. + clone.prop('source/id', cloneMap[source.id].id); + } + if (target.id && cloneMap[target.id]) { + // Target points to an element and the element is among the clones. + // => Update the target of the cloned link. + clone.prop('target/id', cloneMap[target.id].id); + } + } + + // Find the parent of the original cell + const parent = cell.get('parent'); + if (parent && cloneMap[parent]) { + clone.set('parent', cloneMap[parent].id); + } + + // Find the embeds of the original cell + const embeds = toArray(cell.get('embeds')).reduce(function(newEmbeds, embed) { + // Embedded cells that are not being cloned can not be carried + // over with other embedded cells. + if (cloneMap[embed]) { + newEmbeds.push(cloneMap[embed].id); + } + return newEmbeds; + }, []); + + if (!isEmpty(embeds)) { + clone.set('embeds', embeds); + } + }); + + return cloneMap; +} + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/attributes/props.mjs + + +const validPropertiesList = ['checked', 'selected', 'disabled', 'readOnly', 'contentEditable', 'value', 'indeterminate']; + +const validProperties = validPropertiesList.reduce((acc, key) => { + acc[key] = true; + return acc; +}, {}); + +const props_props = { + qualify: function(properties) { + return isPlainObject(properties); + }, + set: function(properties, _, node) { + Object.keys(properties).forEach(function(key) { + if (validProperties[key] && key in node) { + const value = properties[key]; + if (node.tagName === 'SELECT' && Array.isArray(value)) { + Array.from(node.options).forEach(function(option, index) { + option.selected = value.includes(option.value); + }); + } else { + node[key] = value; + } + } + }); + } +}; + +/* harmony default export */ var attributes_props = (props_props); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/attributes/index.mjs + + + + + + + +function setWrapper(attrName, dimension) { + return function(value, refBBox) { + var isValuePercentage = isPercentage(value); + value = parseFloat(value); + if (isValuePercentage) { + value /= 100; + } + + var attrs = {}; + if (isFinite(value)) { + var attrValue = (isValuePercentage || value >= 0 && value <= 1) + ? value * refBBox[dimension] + : Math.max(value + refBBox[dimension], 0); + attrs[attrName] = attrValue; + } + + return attrs; + }; +} + +function positionWrapper(axis, dimension, origin) { + return function(value, refBBox) { + var valuePercentage = isPercentage(value); + value = parseFloat(value); + if (valuePercentage) { + value /= 100; + } + + var delta; + if (isFinite(value)) { + var refOrigin = refBBox[origin](); + if (valuePercentage || value > 0 && value < 1) { + delta = refOrigin[axis] + refBBox[dimension] * value; + } else { + delta = refOrigin[axis] + value; + } + } + + var point = Point(); + point[axis] = delta || 0; + return point; + }; +} + +function offsetWrapper(axis, dimension, corner) { + return function(value, nodeBBox) { + var delta; + if (value === 'middle') { + delta = nodeBBox[dimension] / 2; + } else if (value === corner) { + delta = nodeBBox[dimension]; + } else if (isFinite(value)) { + // TODO: or not to do a breaking change? + delta = (value > -1 && value < 1) ? (-nodeBBox[dimension] * value) : -value; + } else if (isPercentage(value)) { + delta = nodeBBox[dimension] * parseFloat(value) / 100; + } else { + delta = 0; + } + + var point = Point(); + point[axis] = -(nodeBBox[axis] + delta); + return point; + }; +} + +function shapeWrapper(shapeConstructor, opt) { + var cacheName = 'joint-shape'; + var resetOffset = opt && opt.resetOffset; + return function(value, refBBox, node) { + var $node = jquery(node); + var cache = $node.data(cacheName); + if (!cache || cache.value !== value) { + // only recalculate if value has changed + var cachedShape = shapeConstructor(value); + cache = { + value: value, + shape: cachedShape, + shapeBBox: cachedShape.bbox() + }; + $node.data(cacheName, cache); + } + + var shape = cache.shape.clone(); + var shapeBBox = cache.shapeBBox.clone(); + var shapeOrigin = shapeBBox.origin(); + var refOrigin = refBBox.origin(); + + shapeBBox.x = refOrigin.x; + shapeBBox.y = refOrigin.y; + + var fitScale = refBBox.maxRectScaleToFit(shapeBBox, refOrigin); + // `maxRectScaleToFit` can give Infinity if width or height is 0 + var sx = (shapeBBox.width === 0 || refBBox.width === 0) ? 1 : fitScale.sx; + var sy = (shapeBBox.height === 0 || refBBox.height === 0) ? 1 : fitScale.sy; + + shape.scale(sx, sy, shapeOrigin); + if (resetOffset) { + shape.translate(-shapeOrigin.x, -shapeOrigin.y); + } + + return shape; + }; +} + +// `d` attribute for SVGPaths +function dWrapper(opt) { + function pathConstructor(value) { + return new Path(src_V.normalizePathData(value)); + } + + var shape = shapeWrapper(pathConstructor, opt); + return function(value, refBBox, node) { + var path = shape(value, refBBox, node); + return { + d: path.serialize() + }; + }; +} + +// `points` attribute for SVGPolylines and SVGPolygons +function pointsWrapper(opt) { + var shape = shapeWrapper(Polyline, opt); + return function(value, refBBox, node) { + var polyline = shape(value, refBBox, node); + return { + points: polyline.serialize() + }; + }; +} + +function atConnectionWrapper(method, opt) { + var zeroVector = new Point(1, 0); + return function(value) { + var p, angle; + var tangent = this[method](value); + if (tangent) { + angle = (opt.rotate) ? tangent.vector().vectorAngle(zeroVector) : 0; + p = tangent.start; + } else { + p = this.path.start; + angle = 0; + } + if (angle === 0) return { transform: 'translate(' + p.x + ',' + p.y + ')' }; + return { transform: 'translate(' + p.x + ',' + p.y + ') rotate(' + angle + ')' }; + }; +} + +function setIfChangedWrapper(attribute) { + return function setIfChanged(value, _, node) { + const vel = src_V(node); + if (vel.attr(attribute) === value) return; + vel.attr(attribute, value); + }; +} + +function isTextInUse(_value, _node, attrs) { + return (attrs.text !== undefined); +} + +function isLinkView() { + return this.model.isLink(); +} + +function contextMarker(context) { + var marker = {}; + // Stroke + // The context 'fill' is disregared here. The usual case is to use the marker with a connection + // (for which 'fill' attribute is set to 'none'). + var stroke = context.stroke; + if (typeof stroke === 'string') { + marker['stroke'] = stroke; + marker['fill'] = stroke; + } + // Opacity + // Again the context 'fill-opacity' is ignored. + var strokeOpacity = context.strokeOpacity; + if (strokeOpacity === undefined) strokeOpacity = context['stroke-opacity']; + if (strokeOpacity === undefined) strokeOpacity = context.opacity; + if (strokeOpacity !== undefined) { + marker['stroke-opacity'] = strokeOpacity; + marker['fill-opacity'] = strokeOpacity; + } + return marker; +} + +function setPaintURL(def) { + const { paper } = this; + const url = (def.type === 'pattern') + ? paper.definePattern(def) + : paper.defineGradient(def); + return `url(#${url})`; +} + +const attributesNS = { + + xlinkShow: { + set: 'xlink:show' + }, + + xlinkRole: { + set: 'xlink:role' + }, + + xlinkType: { + set: 'xlink:type' + }, + + xlinkArcrole: { + set: 'xlink:arcrole' + }, + + xlinkTitle: { + set: 'xlink:title' + }, + + xlinkActuate: { + set: 'xlink:actuate' + }, + + xmlSpace: { + set: 'xml:space' + }, + + xmlBase: { + set: 'xml:base' + }, + + xmlLang: { + set: 'xml:lang' + }, + + preserveAspectRatio: { + set: 'preserveAspectRatio' + }, + + requiredExtension: { + set: 'requiredExtension' + }, + + requiredFeatures: { + set: 'requiredFeatures' + }, + + systemLanguage: { + set: 'systemLanguage' + }, + + externalResourcesRequired: { + set: 'externalResourceRequired' + }, + + href: { + set: setIfChangedWrapper('href') + }, + + xlinkHref: { + set: setIfChangedWrapper('xlink:href') + }, + + filter: { + qualify: isPlainObject, + set: function(filter) { + return 'url(#' + this.paper.defineFilter(filter) + ')'; + } + }, + + fill: { + qualify: isPlainObject, + set: setPaintURL + }, + + stroke: { + qualify: isPlainObject, + set: setPaintURL + }, + + sourceMarker: { + qualify: isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = utilHelpers_assign(contextMarker(attrs), marker); + return { 'marker-start': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + targetMarker: { + qualify: isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = utilHelpers_assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker); + return { 'marker-end': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + vertexMarker: { + qualify: isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = utilHelpers_assign(contextMarker(attrs), marker); + return { 'marker-mid': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + text: { + qualify: function(_text, _node, attrs) { + return !attrs.textWrap || !isPlainObject(attrs.textWrap); + }, + set: function(text, refBBox, node, attrs) { + var $node = jquery(node); + var cacheName = 'joint-text'; + var cache = $node.data(cacheName); + var textAttrs = pick(attrs, 'lineHeight', 'annotations', 'textPath', 'x', 'textVerticalAnchor', 'eol', 'displayEmpty'); + // eval `x` if using calc() + const { x } = textAttrs; + if (isCalcAttribute(x)) { + textAttrs.x = evalCalcAttribute(x, refBBox); + } + + let fontSizeAttr = attrs['font-size'] || attrs['fontSize']; + if (isCalcAttribute(fontSizeAttr)) { + fontSizeAttr = evalCalcAttribute(fontSizeAttr, refBBox); + } + var fontSize = textAttrs.fontSize = fontSizeAttr; + var textHash = JSON.stringify([text, textAttrs]); + // Update the text only if there was a change in the string + // or any of its attributes. + if (cache === undefined || cache !== textHash) { + // Chrome bug: + // Tspans positions defined as `em` are not updated + // when container `font-size` change. + if (fontSize) node.setAttribute('font-size', fontSize); + // Text Along Path Selector + var textPath = textAttrs.textPath; + if (isObject(textPath)) { + var pathSelector = textPath.selector; + if (typeof pathSelector === 'string') { + var pathNode = this.findBySelector(pathSelector)[0]; + if (pathNode instanceof SVGPathElement) { + textAttrs.textPath = utilHelpers_assign({ 'xlink:href': '#' + pathNode.id }, textPath); + } + } + } + src_V(node).text('' + text, textAttrs); + $node.data(cacheName, textHash); + } + } + }, + + textWrap: { + qualify: isPlainObject, + set: function(value, refBBox, node, attrs) { + var size = {}; + // option `width` + var width = value.width || 0; + if (isPercentage(width)) { + size.width = refBBox.width * parseFloat(width) / 100; + } else if (isCalcAttribute(width)) { + size.width = Number(evalCalcAttribute(width, refBBox)); + } else { + if (value.width === null) { + // breakText() requires width to be specified. + size.width = Infinity; + } else if (width <= 0) { + size.width = refBBox.width + width; + } else { + size.width = width; + } + } + // option `height` + var height = value.height || 0; + if (isPercentage(height)) { + size.height = refBBox.height * parseFloat(height) / 100; + } else if (isCalcAttribute(height)) { + size.height = Number(evalCalcAttribute(height, refBBox)); + } else { + if (value.height === null) { + // if height is not specified breakText() does not + // restrict the height of the text. + } else if (height <= 0) { + size.height = refBBox.height + height; + } else { + size.height = height; + } + } + // option `text` + var wrappedText; + var text = value.text; + if (text === undefined) text = attrs.text; + if (text !== undefined) { + const breakTextFn = value.breakText || breakText; + const fontSizeAttr = attrs['font-size'] || attrs.fontSize; + wrappedText = breakTextFn('' + text, size, { + 'font-weight': attrs['font-weight'] || attrs.fontWeight, + 'font-size': isCalcAttribute(fontSizeAttr) ? evalCalcAttribute(fontSizeAttr, refBBox) : fontSizeAttr, + 'font-family': attrs['font-family'] || attrs.fontFamily, + 'lineHeight': attrs.lineHeight, + 'letter-spacing': 'letter-spacing' in attrs ? attrs['letter-spacing'] : attrs.letterSpacing + }, { + // Provide an existing SVG Document here + // instead of creating a temporary one over again. + svgDocument: this.paper.svg, + ellipsis: value.ellipsis, + hyphen: value.hyphen, + maxLineCount: value.maxLineCount, + preserveSpaces: value.preserveSpaces + }); + } else { + wrappedText = ''; + } + attributesNS.text.set.call(this, wrappedText, refBBox, node, attrs); + } + }, + + title: { + qualify: function(title, node) { + // HTMLElement title is specified via an attribute (i.e. not an element) + return node instanceof SVGElement; + }, + set: function(title, refBBox, node) { + var $node = jquery(node); + var cacheName = 'joint-title'; + var cache = $node.data(cacheName); + if (cache === undefined || cache !== title) { + $node.data(cacheName, title); + if (node.tagName === 'title') { + // The target node is a element. + node.textContent = title; + return; + } + // Generally <title> element should be the first child element of its parent. + var firstChild = node.firstElementChild; + if (firstChild && firstChild.tagName === 'title') { + // Update an existing title + firstChild.textContent = title; + } else { + // Create a new title + var titleNode = document.createElementNS(node.namespaceURI, 'title'); + titleNode.textContent = title; + node.insertBefore(titleNode, firstChild); + } + } + } + }, + + lineHeight: { + qualify: isTextInUse + }, + + textVerticalAnchor: { + qualify: isTextInUse + }, + + textPath: { + qualify: isTextInUse + }, + + annotations: { + qualify: isTextInUse + }, + + eol: { + qualify: isTextInUse + }, + + displayEmpty: { + qualify: isTextInUse + }, + + // `port` attribute contains the `id` of the port that the underlying magnet represents. + port: { + set: function(port) { + return (port === null || port.id === undefined) ? port : port.id; + } + }, + + // `style` attribute is special in the sense that it sets the CSS style of the subelement. + style: { + qualify: isPlainObject, + set: function(styles, refBBox, node) { + jquery(node).css(styles); + } + }, + + html: { + set: function(html, refBBox, node) { + jquery(node).html(html + ''); + } + }, + + // Properties setter (set various properties on the node) + props: attributes_props, + + ref: { + // We do not set `ref` attribute directly on an element. + // The attribute itself does not qualify for relative positioning. + }, + + // if `refX` is in [0, 1] then `refX` is a fraction of bounding box width + // if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box + // otherwise, `refX` is the left coordinate of the bounding box + + refX: { + position: positionWrapper('x', 'width', 'origin') + }, + + refY: { + position: positionWrapper('y', 'height', 'origin') + }, + + // `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom + // coordinate of the reference element. + + refDx: { + position: positionWrapper('x', 'width', 'corner') + }, + + refDy: { + position: positionWrapper('y', 'height', 'corner') + }, + + // 'ref-width'/'ref-height' defines the width/height of the subelement relatively to + // the reference element size + // val in 0..1 ref-width = 0.75 sets the width to 75% of the ref. el. width + // val < 0 || val > 1 ref-height = -20 sets the height to the ref. el. height shorter by 20 + + refWidth: { + set: setWrapper('width', 'width') + }, + + refHeight: { + set: setWrapper('height', 'height') + }, + + refRx: { + set: setWrapper('rx', 'width') + }, + + refRy: { + set: setWrapper('ry', 'height') + }, + + refRInscribed: { + set: (function(attrName) { + var widthFn = setWrapper(attrName, 'width'); + var heightFn = setWrapper(attrName, 'height'); + return function(value, refBBox) { + var fn = (refBBox.height > refBBox.width) ? widthFn : heightFn; + return fn(value, refBBox); + }; + })('r') + }, + + refRCircumscribed: { + set: function(value, refBBox) { + var isValuePercentage = isPercentage(value); + value = parseFloat(value); + if (isValuePercentage) { + value /= 100; + } + + var diagonalLength = Math.sqrt((refBBox.height * refBBox.height) + (refBBox.width * refBBox.width)); + + var rValue; + if (isFinite(value)) { + if (isValuePercentage || value >= 0 && value <= 1) rValue = value * diagonalLength; + else rValue = Math.max(value + diagonalLength, 0); + } + + return { r: rValue }; + } + }, + + refCx: { + set: setWrapper('cx', 'width') + }, + + refCy: { + set: setWrapper('cy', 'height') + }, + + // `x-alignment` when set to `middle` causes centering of the subelement around its new x coordinate. + // `x-alignment` when set to `right` uses the x coordinate as referenced to the right of the bbox. + + xAlignment: { + offset: offsetWrapper('x', 'width', 'right') + }, + + // `y-alignment` when set to `middle` causes centering of the subelement around its new y coordinate. + // `y-alignment` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox. + + yAlignment: { + offset: offsetWrapper('y', 'height', 'bottom') + }, + + resetOffset: { + offset: function(val, nodeBBox) { + return (val) + ? { x: -nodeBBox.x, y: -nodeBBox.y } + : { x: 0, y: 0 }; + } + + }, + + refDResetOffset: { + set: dWrapper({ resetOffset: true }) + }, + + refDKeepOffset: { + set: dWrapper({ resetOffset: false }) + }, + + refPointsResetOffset: { + set: pointsWrapper({ resetOffset: true }) + }, + + refPointsKeepOffset: { + set: pointsWrapper({ resetOffset: false }) + }, + + // LinkView Attributes + + connection: { + qualify: isLinkView, + set: function({ stubs = 0 }) { + let d; + if (isFinite(stubs) && stubs !== 0) { + let offset; + if (stubs < 0) { + offset = (this.getConnectionLength() + stubs) / 2; + } else { + offset = stubs; + } + const path = this.getConnection(); + const segmentSubdivisions = this.getConnectionSubdivisions(); + const sourceParts = path.divideAtLength(offset, { segmentSubdivisions }); + const targetParts = path.divideAtLength(-offset, { segmentSubdivisions }); + if (sourceParts && targetParts) { + d = `${sourceParts[0].serialize()} ${targetParts[1].serialize()}`; + } + } + + return { d: d || this.getSerializedConnection() }; + } + }, + + atConnectionLengthKeepGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtLength', { rotate: true }) + }, + + atConnectionLengthIgnoreGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtLength', { rotate: false }) + }, + + atConnectionRatioKeepGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtRatio', { rotate: true }) + }, + + atConnectionRatioIgnoreGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtRatio', { rotate: false }) + } +}; + +attributesNS['xlink:href'] = attributesNS.xlinkHref; + +// Support `calc()` with the following SVG attributes +[ + 'transform', // g + 'd', // path + 'points', // polyline / polygon + 'cx', 'cy', // circle / ellipse + 'x1', 'x2', 'y1', 'y2', // line + 'x', 'y', // rect / text / image + 'dx', 'dy' // text +].forEach(attribute => { + attributesNS[attribute] = { + qualify: isCalcAttribute, + set: function setCalcAttribute(value, refBBox) { + return { [attribute]: evalCalcAttribute(value, refBBox) }; + } + }; +}); + +// Prevent "A negative value is not valid" error. +[ + 'width', 'height', // rect / image + 'r', // circle + 'rx', 'ry', // rect / ellipse + 'font-size', // text + 'stroke-width' // elements +].forEach(attribute => { + attributesNS[attribute] = { + qualify: isCalcAttribute, + set: function setCalcAttribute(value, refBBox) { + return { [attribute]: Math.max(0, evalCalcAttribute(value, refBBox)) }; + } + }; +}); + +// Aliases +attributesNS.refR = attributesNS.refRInscribed; +attributesNS.refD = attributesNS.refDResetOffset; +attributesNS.refPoints = attributesNS.refPointsResetOffset; +attributesNS.atConnectionLength = attributesNS.atConnectionLengthKeepGradient; +attributesNS.atConnectionRatio = attributesNS.atConnectionRatioKeepGradient; +attributesNS.fontSize = attributesNS['font-size']; +attributesNS.strokeWidth = attributesNS['stroke-width']; + +// This allows to combine both absolute and relative positioning +// refX: 50%, refX2: 20 +attributesNS.refX2 = attributesNS.refX; +attributesNS.refY2 = attributesNS.refY; +attributesNS.refWidth2 = attributesNS.refWidth; +attributesNS.refHeight2 = attributesNS.refHeight; + +// Aliases for backwards compatibility +attributesNS['ref-x'] = attributesNS.refX; +attributesNS['ref-y'] = attributesNS.refY; +attributesNS['ref-dy'] = attributesNS.refDy; +attributesNS['ref-dx'] = attributesNS.refDx; +attributesNS['ref-width'] = attributesNS.refWidth; +attributesNS['ref-height'] = attributesNS.refHeight; +attributesNS['x-alignment'] = attributesNS.xAlignment; +attributesNS['y-alignment'] = attributesNS.yAlignment; + +const attributes = attributesNS; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/Cell.mjs + + + + + + + +// Cell base model. +// -------------------------- + +const Cell = backbone.Model.extend({ + + // This is the same as Backbone.Model with the only difference that is uses util.merge + // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes. + constructor: function(attributes, options) { + + var defaults; + var attrs = attributes || {}; + if (typeof this.preinitialize === 'function') { + // Check to support an older version of Backbone (prior v1.4) + this.preinitialize.apply(this, arguments); + } + this.cid = uniqueId('c'); + this.attributes = {}; + if (options && options.collection) this.collection = options.collection; + if (options && options.parse) attrs = this.parse(attrs, options) || {}; + if ((defaults = result(this, 'defaults'))) { + //<custom code> + // Replaced the call to _.defaults with util.merge. + attrs = merge({}, defaults, attrs); + //</custom code> + } + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }, + + translate: function(dx, dy, opt) { + + throw new Error('Must define a translate() method.'); + }, + + toJSON: function() { + + const defaults = result(this.constructor.prototype, 'defaults'); + const defaultAttrs = defaults.attrs || {}; + const attrs = this.attributes.attrs; + const finalAttrs = {}; + + // Loop through all the attributes and + // omit the default attributes as they are implicitly reconstructible by the cell 'type'. + forIn(attrs, function(attr, selector) { + + const defaultAttr = defaultAttrs[selector]; + + forIn(attr, function(value, name) { + + // attr is mainly flat though it might have one more level (consider the `style` attribute). + // Check if the `value` is object and if yes, go one level deep. + if (isObject(value) && !Array.isArray(value)) { + + forIn(value, function(value2, name2) { + + if (!defaultAttr || !defaultAttr[name] || !isEqual(defaultAttr[name][name2], value2)) { + + finalAttrs[selector] = finalAttrs[selector] || {}; + (finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2; + } + }); + + } else if (!defaultAttr || !isEqual(defaultAttr[name], value)) { + // `value` is not an object, default attribute for such a selector does not exist + // or it is different than the attribute value set on the model. + + finalAttrs[selector] = finalAttrs[selector] || {}; + finalAttrs[selector][name] = value; + } + }); + }); + + const attributes = cloneDeep(omit(this.attributes, 'attrs')); + attributes.attrs = finalAttrs; + + return attributes; + }, + + initialize: function(options) { + + const idAttribute = this.getIdAttribute(); + if (!options || options[idAttribute] === undefined) { + this.set(idAttribute, this.generateId(), { silent: true }); + } + + this._transitionIds = {}; + this._scheduledTransitionIds = {}; + + // Collect ports defined in `attrs` and keep collecting whenever `attrs` object changes. + this.processPorts(); + this.on('change:attrs', this.processPorts, this); + }, + + getIdAttribute: function() { + return this.idAttribute || 'id'; + }, + + generateId: function() { + return uuid(); + }, + + /** + * @deprecated + */ + processPorts: function() { + + // Whenever `attrs` changes, we extract ports from the `attrs` object and store it + // in a more accessible way. Also, if any port got removed and there were links that had `target`/`source` + // set to that port, we remove those links as well (to follow the same behaviour as + // with a removed element). + + var previousPorts = this.ports; + + // Collect ports from the `attrs` object. + var ports = {}; + forIn(this.get('attrs'), function(attrs, selector) { + + if (attrs && attrs.port) { + + // `port` can either be directly an `id` or an object containing an `id` (and potentially other data). + if (attrs.port.id !== undefined) { + ports[attrs.port.id] = attrs.port; + } else { + ports[attrs.port] = { id: attrs.port }; + } + } + }); + + // Collect ports that have been removed (compared to the previous ports) - if any. + // Use hash table for quick lookup. + var removedPorts = {}; + forIn(previousPorts, function(port, id) { + + if (!ports[id]) removedPorts[id] = true; + }); + + // Remove all the incoming/outgoing links that have source/target port set to any of the removed ports. + if (this.graph && !isEmpty(removedPorts)) { + + var inboundLinks = this.graph.getConnectedLinks(this, { inbound: true }); + inboundLinks.forEach(function(link) { + + if (removedPorts[link.get('target').port]) link.remove(); + }); + + var outboundLinks = this.graph.getConnectedLinks(this, { outbound: true }); + outboundLinks.forEach(function(link) { + + if (removedPorts[link.get('source').port]) link.remove(); + }); + } + + // Update the `ports` object. + this.ports = ports; + }, + + remove: function(opt = {}) { + + // Store the graph in a variable because `this.graph` won't be accessible + // after `this.trigger('remove', ...)` down below. + const { graph, collection } = this; + if (!graph) { + // The collection is a common Backbone collection (not the graph collection). + if (collection) collection.remove(this, opt); + return this; + } + + graph.startBatch('remove'); + + // First, unembed this cell from its parent cell if there is one. + const parentCell = this.getParentCell(); + if (parentCell) { + parentCell.unembed(this, opt); + } + + // Remove also all the cells, which were embedded into this cell + const embeddedCells = this.getEmbeddedCells(); + for (let i = 0, n = embeddedCells.length; i < n; i++) { + const embed = embeddedCells[i]; + if (embed) { + embed.remove(opt); + } + } + + this.trigger('remove', this, graph.attributes.cells, opt); + + graph.stopBatch('remove'); + + return this; + }, + + toFront: function(opt) { + var graph = this.graph; + if (graph) { + opt = defaults(opt || {}, { foregroundEmbeds: true }); + + let cells; + if (opt.deep) { + cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds }); + cells.unshift(this); + } else { + cells = [this]; + } + + const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z()); + + const maxZ = graph.maxZIndex(); + let z = maxZ - cells.length + 1; + + const collection = graph.get('cells'); + + let shouldUpdate = (collection.indexOf(sortedCells[0]) !== (collection.length - cells.length)); + if (!shouldUpdate) { + shouldUpdate = sortedCells.some(function(cell, index) { + return cell.z() !== z + index; + }); + } + + if (shouldUpdate) { + this.startBatch('to-front'); + + z = z + cells.length; + + sortedCells.forEach(function(cell, index) { + cell.set('z', z + index, opt); + }); + + this.stopBatch('to-front'); + } + } + + return this; + }, + + toBack: function(opt) { + var graph = this.graph; + if (graph) { + opt = defaults(opt || {}, { foregroundEmbeds: true }); + + let cells; + if (opt.deep) { + cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false, sortSiblings: opt.foregroundEmbeds }); + cells.unshift(this); + } else { + cells = [this]; + } + + const sortedCells = opt.foregroundEmbeds ? cells : sortBy(cells, cell => cell.z()); + + let z = graph.minZIndex(); + + var collection = graph.get('cells'); + + let shouldUpdate = (collection.indexOf(sortedCells[0]) !== 0); + if (!shouldUpdate) { + shouldUpdate = sortedCells.some(function(cell, index) { + return cell.z() !== z + index; + }); + } + + if (shouldUpdate) { + this.startBatch('to-back'); + + z -= cells.length; + + sortedCells.forEach(function(cell, index) { + cell.set('z', z + index, opt); + }); + + this.stopBatch('to-back'); + } + } + + return this; + }, + + parent: function(parent, opt) { + + // getter + if (parent === undefined) return this.get('parent'); + // setter + return this.set('parent', parent, 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())) { + throw new Error('Embedding of already embedded cells is not allowed.'); + } + this._embedCells(cells, opt); + return this; + }, + + unembed: function(cell, opt) { + const cells = Array.isArray(cell) ? cell : [cell]; + this._unembedCells(cells, opt); + return this; + }, + + canEmbed: function(cell) { + const cells = Array.isArray(cell) ? cell : [cell]; + return cells.every(c => this !== c && !this.isEmbeddedIn(c)); + }, + + _embedCells: function(cells, opt) { + const batchName = 'embed'; + this.startBatch(batchName); + const embeds = utilHelpers_assign([], this.get('embeds')); + cells.forEach(cell => { + // We keep all element ids after link ids. + embeds[cell.isLink() ? 'unshift' : 'push'](cell.id); + cell.parent(this.id, opt); + }); + this.set('embeds', uniq(embeds), opt); + this.stopBatch(batchName); + }, + + _unembedCells: function(cells, opt) { + const batchName = 'unembed'; + this.startBatch(batchName); + cells.forEach(cell => cell.unset('parent', opt)); + this.set('embeds', without(this.get('embeds'), ...cells.map(cell => cell.id)), opt); + this.stopBatch(batchName); + }, + + getParentCell: function() { + + // unlike link.source/target, cell.parent stores id directly as a string + var parentId = this.parent(); + var graph = this.graph; + + return (parentId && graph && graph.getCell(parentId)) || null; + }, + + // Return an array of ancestor cells. + // The array is ordered from the parent of the cell + // to the most distant ancestor. + getAncestors: function() { + + var ancestors = []; + + if (!this.graph) { + return ancestors; + } + + var parentCell = this.getParentCell(); + while (parentCell) { + ancestors.push(parentCell); + parentCell = parentCell.getParentCell(); + } + + return ancestors; + }, + + getEmbeddedCells: function(opt) { + + opt = opt || {}; + + // Cell models can only be retrieved when this element is part of a collection. + // There is no way this element knows about other cells otherwise. + // This also means that calling e.g. `translate()` on an element with embeds before + // adding it to a graph does not translate its embeds. + if (!this.graph) { + return []; + } + + if (opt.deep) { + if (opt.breadthFirst) { + return this._getEmbeddedCellsBfs(opt.sortSiblings); + } else { + return this._getEmbeddedCellsDfs(opt.sortSiblings); + } + } + + const embeddedIds = this.get('embeds'); + if (isEmpty(embeddedIds)) { + return []; + } + + let cells = embeddedIds.map(this.graph.getCell, this.graph); + if (opt.sortSiblings) { + cells = sortBy(cells, cell => cell.z()); + } + + return cells; + }, + + _getEmbeddedCellsBfs: function(sortSiblings) { + const cells = []; + + const queue = []; + queue.push(this); + + while (queue.length > 0) { + const current = queue.shift(); + cells.push(current); + + const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings }); + + queue.push(...embeddedCells); + } + cells.shift(); + + return cells; + }, + + _getEmbeddedCellsDfs: function(sortSiblings) { + const cells = []; + + const stack = []; + stack.push(this); + + while (stack.length > 0) { + const current = stack.pop(); + cells.push(current); + + const embeddedCells = current.getEmbeddedCells({ sortSiblings: sortSiblings }); + + // When using the stack, cells that are embedded last are processed first. + // To maintain the original order, we need to push the cells in reverse order + for (let i = embeddedCells.length - 1; i >= 0; --i) { + stack.push(embeddedCells[i]); + } + } + cells.shift(); + + return cells; + }, + + isEmbeddedIn: function(cell, opt) { + + var cellId = isString(cell) ? cell : cell.id; + var parentId = this.parent(); + + opt = utilHelpers_assign({ deep: true }, opt); + + // See getEmbeddedCells(). + if (this.graph && opt.deep) { + + while (parentId) { + if (parentId === cellId) { + return true; + } + parentId = this.graph.getCell(parentId).parent(); + } + + return false; + + } else { + + // When this cell is not part of a collection check + // at least whether it's a direct child of given cell. + return parentId === cellId; + } + }, + + // Whether or not the cell is embedded in any other cell. + isEmbedded: function() { + + return !!this.parent(); + }, + + // Isolated cloning. Isolated cloning has two versions: shallow and deep (pass `{ deep: true }` in `opt`). + // Shallow cloning simply clones the cell and returns a new cell with different ID. + // Deep cloning clones the cell and all its embedded cells recursively. + clone: function(opt) { + + opt = opt || {}; + + if (!opt.deep) { + // Shallow cloning. + + var clone = backbone.Model.prototype.clone.apply(this, arguments); + // We don't want the clone to have the same ID as the original. + clone.set(this.getIdAttribute(), this.generateId()); + // A shallow cloned element does not carry over the original embeds. + clone.unset('embeds'); + // And can not be embedded in any cell + // as the clone is not part of the graph. + clone.unset('parent'); + + return clone; + + } else { + // Deep cloning. + + // For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells. + return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true })))); + } + }, + + // A convenient way to set nested properties. + // This method merges the properties you'd like to set with the ones + // stored in the cell and makes sure change events are properly triggered. + // You can either set a nested property with one object + // or use a property path. + // The most simple use case is: + // `cell.prop('name/first', 'John')` or + // `cell.prop({ name: { first: 'John' } })`. + // Nested arrays are supported too: + // `cell.prop('series/0/data/0/degree', 50)` or + // `cell.prop({ series: [ { data: [ { degree: 50 } ] } ] })`. + prop: function(props, value, opt) { + + var delim = '/'; + var _isString = isString(props); + + if (_isString || Array.isArray(props)) { + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + + if (arguments.length > 1) { + + var path; + var pathArray; + + if (_isString) { + path = props; + pathArray = path.split('/'); + } else { + path = props.join(delim); + pathArray = props.slice(); + } + + var property = pathArray[0]; + var pathArrayLength = pathArray.length; + + const options = opt || {}; + options.propertyPath = path; + options.propertyValue = value; + options.propertyPathArray = pathArray; + if (!('rewrite' in options)) { + options.rewrite = false; + } + + var update = {}; + // Initialize the nested object. Sub-objects are either arrays or objects. + // An empty array is created if the sub-key is an integer. Otherwise, an empty object is created. + // Note that this imposes a limitation on object keys one can use with Inspector. + // Pure integer keys will cause issues and are therefore not allowed. + var initializer = update; + var prevProperty = property; + + for (var i = 1; i < pathArrayLength; i++) { + var pathItem = pathArray[i]; + var isArrayIndex = Number.isFinite(_isString ? Number(pathItem) : pathItem); + initializer = initializer[prevProperty] = isArrayIndex ? [] : {}; + prevProperty = pathItem; + } + + // Fill update with the `value` on `path`. + update = setByPath(update, pathArray, value, '/'); + + var baseAttributes = merge({}, this.attributes); + // if rewrite mode enabled, we replace value referenced by path with + // the new one (we don't merge). + options.rewrite && unsetByPath(baseAttributes, path, '/'); + + // Merge update with the model attributes. + var attributes = merge(baseAttributes, update); + // Finally, set the property to the updated attributes. + return this.set(property, attributes[property], options); + + } else { + + return getByPath(this.attributes, props, delim); + } + } + + const options = value || {}; + // Note: '' is not the path to the root. It's a path with an empty string i.e. { '': {}}. + options.propertyPath = null; + options.propertyValue = props; + options.propertyPathArray = []; + if (!('rewrite' in options)) { + options.rewrite = false; + } + + return this.set(merge({}, this.attributes, props), options); + }, + + // A convenient way to unset nested properties + removeProp: function(path, opt) { + + opt = opt || {}; + + var pathArray = Array.isArray(path) ? path : path.split('/'); + + // Once a property is removed from the `attrs` attribute + // the cellView will recognize a `dirty` flag and re-render itself + // in order to remove the attribute from SVG element. + var property = pathArray[0]; + if (property === 'attrs') opt.dirty = true; + + if (pathArray.length === 1) { + // A top level property + return this.unset(path, opt); + } + + // A nested property + var nestedPath = pathArray.slice(1); + var propertyValue = this.get(property); + if (propertyValue === undefined || propertyValue === null) return this; + propertyValue = cloneDeep(propertyValue); + + unsetByPath(propertyValue, nestedPath, '/'); + + return this.set(property, propertyValue, opt); + }, + + // A convenient way to set nested attributes. + attr: function(attrs, value, opt) { + + var args = Array.from(arguments); + if (args.length === 0) { + return this.get('attrs'); + } + + if (Array.isArray(attrs)) { + args[0] = ['attrs'].concat(attrs); + } else if (isString(attrs)) { + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + args[0] = 'attrs/' + attrs; + + } else { + + args[0] = { 'attrs' : attrs }; + } + + return this.prop.apply(this, args); + }, + + // A convenient way to unset nested attributes + removeAttr: function(path, opt) { + + if (Array.isArray(path)) { + + return this.removeProp(['attrs'].concat(path)); + } + + return this.removeProp('attrs/' + path, opt); + }, + + transition: function(path, value, opt, delim) { + + delim = delim || '/'; + + var defaults = { + duration: 100, + delay: 10, + timingFunction: timing.linear, + valueFunction: interpolate.number + }; + + opt = utilHelpers_assign(defaults, opt); + + var firstFrameTime = 0; + var interpolatingFunction; + + var setter = function(runtime) { + + var id, progress, propertyValue; + + firstFrameTime = firstFrameTime || runtime; + runtime -= firstFrameTime; + progress = runtime / opt.duration; + + if (progress < 1) { + this._transitionIds[path] = id = nextFrame(setter); + } else { + progress = 1; + delete this._transitionIds[path]; + } + + propertyValue = interpolatingFunction(opt.timingFunction(progress)); + + opt.transitionId = id; + + this.prop(path, propertyValue, opt); + + if (!id) this.trigger('transition:end', this, path); + + }.bind(this); + + const { _scheduledTransitionIds } = this; + let initialId; + + var initiator = (callback) => { + + if (_scheduledTransitionIds[path]) { + _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId); + if (_scheduledTransitionIds[path].length === 0) { + delete _scheduledTransitionIds[path]; + } + } + + this.stopPendingTransitions(path, delim); + + interpolatingFunction = opt.valueFunction(getByPath(this.attributes, path, delim), value); + + this._transitionIds[path] = nextFrame(callback); + + this.trigger('transition:start', this, path); + + }; + + initialId = setTimeout(initiator, opt.delay, setter); + + _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []); + _scheduledTransitionIds[path].push(initialId); + + return initialId; + }, + + getTransitions: function() { + return union( + Object.keys(this._transitionIds), + Object.keys(this._scheduledTransitionIds) + ); + }, + + stopScheduledTransitions: function(path, delim = '/') { + const { _scheduledTransitionIds = {}} = this; + let transitions = Object.keys(_scheduledTransitionIds); + if (path) { + const pathArray = path.split(delim); + transitions = transitions.filter((key) => { + return isEqual(pathArray, key.split(delim).slice(0, pathArray.length)); + }); + } + transitions.forEach((key) => { + const transitionIds = _scheduledTransitionIds[key]; + // stop the initiator + transitionIds.forEach(transitionId => clearTimeout(transitionId)); + delete _scheduledTransitionIds[key]; + // Note: we could trigger transition:cancel` event here + }); + return this; + }, + + stopPendingTransitions(path, delim = '/') { + const { _transitionIds = {}} = this; + let transitions = Object.keys(_transitionIds); + if (path) { + const pathArray = path.split(delim); + transitions = transitions.filter((key) => { + return isEqual(pathArray, key.split(delim).slice(0, pathArray.length)); + }); + } + transitions.forEach((key) => { + const transitionId = _transitionIds[key]; + // stop the setter + cancelFrame(transitionId); + delete _transitionIds[key]; + this.trigger('transition:end', this, key); + }); + }, + + stopTransitions: function(path, delim = '/') { + this.stopScheduledTransitions(path, delim); + this.stopPendingTransitions(path, delim); + return this; + }, + + // A shorcut making it easy to create constructs like the following: + // `var el = (new joint.shapes.basic.Rect).addTo(graph)`. + addTo: function(graph, opt) { + + graph.addCell(this, opt); + return this; + }, + + // A shortcut for an equivalent call: `paper.findViewByModel(cell)` + // making it easy to create constructs like the following: + // `cell.findView(paper).highlight()` + findView: function(paper) { + + return paper.findViewByModel(this); + }, + + isElement: function() { + + return false; + }, + + isLink: function() { + + return false; + }, + + startBatch: function(name, opt) { + + if (this.graph) { this.graph.startBatch(name, utilHelpers_assign({}, opt, { cell: this })); } + return this; + }, + + stopBatch: function(name, opt) { + + if (this.graph) { this.graph.stopBatch(name, utilHelpers_assign({}, opt, { cell: this })); } + return this; + }, + + getChangeFlag: function(attributes) { + + var flag = 0; + if (!attributes) return flag; + for (var key in attributes) { + if (!attributes.hasOwnProperty(key) || !this.hasChanged(key)) continue; + flag |= attributes[key]; + } + return flag; + }, + + angle: function() { + + // To be overridden. + return 0; + }, + + position: function() { + + // To be overridden. + return new Point(0, 0); + }, + + z: function() { + return this.get('z') || 0; + }, + + getPointFromConnectedLink: function() { + + // To be overridden + return new Point(); + }, + + getBBox: function() { + + // To be overridden + return new Rect(0, 0, 0, 0); + }, + + getPointRotatedAroundCenter(angle, x, y) { + const point = new Point(x, y); + if (angle) point.rotate(this.getBBox().center(), angle); + return point; + }, + + getAbsolutePointFromRelative(x, y) { + // Rotate the position to take the model angle into account + return this.getPointRotatedAroundCenter( + -this.angle(), + // Transform the relative position to absolute + this.position().offset(x, y) + ); + }, + + getRelativePointFromAbsolute(x, y) { + return this + // Rotate the coordinates to mitigate the element's rotation. + .getPointRotatedAroundCenter(this.angle(), x, y) + // Transform the absolute position into relative + .difference(this.position()); + } + +}, { + + getAttributeDefinition: function(attrName) { + + var defNS = this.attributes; + var globalDefNS = attributes; + return (defNS && defNS[attrName]) || globalDefNS[attrName]; + }, + + define: function(type, defaults, protoProps, staticProps) { + + protoProps = utilHelpers_assign({ + defaults: defaultsDeep({ type: type }, defaults, this.prototype.defaults) + }, protoProps); + + var Cell = this.extend(protoProps, staticProps); + // es5 backward compatibility + /* eslint-disable no-undef */ + if (typeof joint !== 'undefined' && has(joint, 'shapes')) { + setByPath(joint.shapes, type, Cell, '.'); + } + /* eslint-enable no-undef */ + return Cell; + } +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/Link.mjs + + + + +// Link base model. +// -------------------------- + +const Link = Cell.extend({ + + // The default markup for links. + markup: [ + '<path class="connection" stroke="black" d="M 0 0 0 0"/>', + '<path class="marker-source" fill="black" stroke="black" d="M 0 0 0 0"/>', + '<path class="marker-target" fill="black" stroke="black" d="M 0 0 0 0"/>', + '<path class="connection-wrap" d="M 0 0 0 0"/>', + '<g class="labels"/>', + '<g class="marker-vertices"/>', + '<g class="marker-arrowheads"/>', + '<g class="link-tools"/>' + ].join(''), + + toolMarkup: [ + '<g class="link-tool">', + '<g class="tool-remove" event="remove">', + '<circle r="11" />', + '<path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z" />', + '<title>Remove link.', + '', + '', + '', + '', + 'Link options.', + '', + '' + ].join(''), + + doubleToolMarkup: undefined, + + // The default markup for showing/removing vertices. These elements are the children of the .marker-vertices element (see `this.markup`). + // Only .marker-vertex and .marker-vertex-remove element have special meaning. The former is used for + // dragging vertices (changing their position). The latter is used for removing vertices. + vertexMarkup: [ + '', + '', + '', + '', + 'Remove vertex.', + '', + '' + ].join(''), + + arrowheadMarkup: [ + '', + '', + '' + ].join(''), + + // may be overwritten by user to change default label (its markup, size, attrs, position) + defaultLabel: undefined, + + // deprecated + // may be overwritten by user to change default label markup + // lower priority than defaultLabel.markup + labelMarkup: undefined, + + // private + _builtins: { + defaultLabel: { + // builtin default markup: + // used if neither defaultLabel.markup + // nor label.markup is set + markup: [ + { + tagName: 'rect', + selector: 'rect' // faster than tagName CSS selector + }, { + tagName: 'text', + selector: 'text' // faster than tagName CSS selector + } + ], + // builtin default attributes: + // applied only if builtin default markup is used + attrs: { + text: { + fill: '#000000', + fontSize: 14, + textAnchor: 'middle', + yAlignment: 'middle', + pointerEvents: 'none' + }, + rect: { + ref: 'text', + fill: '#ffffff', + rx: 3, + ry: 3, + refWidth: 1, + refHeight: 1, + refX: 0, + refY: 0 + } + }, + // builtin default position: + // used if neither defaultLabel.position + // nor label.position is set + position: { + distance: 0.5 + } + } + }, + + defaults: { + type: 'link', + source: {}, + target: {} + }, + + isLink: function() { + + return true; + }, + + disconnect: function(opt) { + + return this.set({ + source: { x: 0, y: 0 }, + target: { x: 0, y: 0 } + }, opt); + }, + + source: function(source, args, opt) { + + // getter + if (source === undefined) { + return clone(this.get('source')); + } + + // setter + var setSource; + var setOpt; + + // `source` is a cell + // take only its `id` and combine with `args` + var isCellProvided = source instanceof Cell; + if (isCellProvided) { // three arguments + setSource = clone(args) || {}; + setSource.id = source.id; + setOpt = opt; + return this.set('source', setSource, setOpt); + } + + // `source` is a point-like object + // for example, a g.Point + // take only its `x` and `y` and combine with `args` + var isPointProvided = !isPlainObject(source); + if (isPointProvided) { // three arguments + setSource = clone(args) || {}; + setSource.x = source.x; + setSource.y = source.y; + setOpt = opt; + return this.set('source', setSource, setOpt); + } + + // `source` is an object + // no checking + // two arguments + setSource = source; + setOpt = args; + return this.set('source', setSource, setOpt); + }, + + target: function(target, args, opt) { + + // getter + if (target === undefined) { + return clone(this.get('target')); + } + + // setter + var setTarget; + var setOpt; + + // `target` is a cell + // take only its `id` argument and combine with `args` + var isCellProvided = target instanceof Cell; + if (isCellProvided) { // three arguments + setTarget = clone(args) || {}; + setTarget.id = target.id; + setOpt = opt; + return this.set('target', setTarget, setOpt); + } + + // `target` is a point-like object + // for example, a g.Point + // take only its `x` and `y` and combine with `args` + var isPointProvided = !isPlainObject(target); + if (isPointProvided) { // three arguments + setTarget = clone(args) || {}; + setTarget.x = target.x; + setTarget.y = target.y; + setOpt = opt; + return this.set('target', setTarget, setOpt); + } + + // `target` is an object + // no checking + // two arguments + setTarget = target; + setOpt = args; + return this.set('target', setTarget, setOpt); + }, + + router: function(name, args, opt) { + + // getter + if (name === undefined) { + var router = this.get('router'); + if (!router) { + if (this.get('manhattan')) return { name: 'orthogonal' }; // backwards compatibility + return null; + } + if (typeof router === 'object') return clone(router); + return router; // e.g. a function + } + + // setter + var isRouterProvided = ((typeof name === 'object') || (typeof name === 'function')); + var localRouter = isRouterProvided ? name : { name: name, args: args }; + var localOpt = isRouterProvided ? args : opt; + + return this.set('router', localRouter, localOpt); + }, + + connector: function(name, args, opt) { + + // getter + if (name === undefined) { + var connector = this.get('connector'); + if (!connector) { + if (this.get('smooth')) return { name: 'smooth' }; // backwards compatibility + return null; + } + if (typeof connector === 'object') return clone(connector); + return connector; // e.g. a function + } + + // setter + var isConnectorProvided = ((typeof name === 'object' || typeof name === 'function')); + var localConnector = isConnectorProvided ? name : { name: name, args: args }; + var localOpt = isConnectorProvided ? args : opt; + + return this.set('connector', localConnector, localOpt); + }, + + // Labels API + + // A convenient way to set labels. Currently set values will be mixined with `value` if used as a setter. + label: function(idx, label, opt) { + + var labels = this.labels(); + + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0; + if (idx < 0) idx = labels.length + idx; + + // getter + if (arguments.length <= 1) return this.prop(['labels', idx]); + // setter + return this.prop(['labels', idx], label, opt); + }, + + labels: function(labels, opt) { + + // getter + if (arguments.length === 0) { + labels = this.get('labels'); + if (!Array.isArray(labels)) return []; + return labels.slice(); + } + // setter + if (!Array.isArray(labels)) labels = []; + return this.set('labels', labels, opt); + }, + + hasLabels: function() { + const { labels } = this.attributes; + return Array.isArray(labels) && labels.length > 0; + }, + + insertLabel: function(idx, label, opt) { + + if (!label) throw new Error('dia.Link: no label provided'); + + var labels = this.labels(); + var n = labels.length; + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n; + if (idx < 0) idx = n + idx + 1; + + labels.splice(idx, 0, label); + return this.labels(labels, opt); + }, + + // convenience function + // add label to end of labels array + appendLabel: function(label, opt) { + + return this.insertLabel(-1, label, opt); + }, + + removeLabel: function(idx, opt) { + + var labels = this.labels(); + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1; + + labels.splice(idx, 1); + return this.labels(labels, opt); + }, + + // Vertices API + + vertex: function(idx, vertex, opt) { + + var vertices = this.vertices(); + + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0; + if (idx < 0) idx = vertices.length + idx; + + // getter + if (arguments.length <= 1) return this.prop(['vertices', idx]); + + // setter + var setVertex = this._normalizeVertex(vertex); + return this.prop(['vertices', idx], setVertex, opt); + }, + + vertices: function(vertices, opt) { + + // getter + if (arguments.length === 0) { + vertices = this.get('vertices'); + if (!Array.isArray(vertices)) return []; + return vertices.slice(); + } + + // setter + if (!Array.isArray(vertices)) vertices = []; + var setVertices = []; + for (var i = 0; i < vertices.length; i++) { + var vertex = vertices[i]; + var setVertex = this._normalizeVertex(vertex); + setVertices.push(setVertex); + } + return this.set('vertices', setVertices, opt); + }, + + insertVertex: function(idx, vertex, opt) { + + if (!vertex) throw new Error('dia.Link: no vertex provided'); + + var vertices = this.vertices(); + var n = vertices.length; + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n; + if (idx < 0) idx = n + idx + 1; + + var setVertex = this._normalizeVertex(vertex); + vertices.splice(idx, 0, setVertex); + return this.vertices(vertices, opt); + }, + + removeVertex: function(idx, opt) { + + var vertices = this.vertices(); + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1; + + vertices.splice(idx, 1); + return this.vertices(vertices, opt); + }, + + _normalizeVertex: function(vertex) { + + // is vertex a point-like object? + // for example, a g.Point + var isPointProvided = !isPlainObject(vertex); + if (isPointProvided) return { x: vertex.x, y: vertex.y }; + + // else: return vertex unchanged + return vertex; + }, + + // Transformations + + translate: function(tx, ty, opt) { + + // enrich the option object + opt = opt || {}; + opt.translateBy = opt.translateBy || this.id; + opt.tx = tx; + opt.ty = ty; + + return this.applyToPoints(function(p) { + return { x: (p.x || 0) + tx, y: (p.y || 0) + ty }; + }, opt); + }, + + scale: function(sx, sy, origin, opt) { + + return this.applyToPoints(function(p) { + return Point(p).scale(sx, sy, origin).toJSON(); + }, opt); + }, + + applyToPoints: function(fn, opt) { + + if (!isFunction(fn)) { + throw new TypeError('dia.Link: applyToPoints expects its first parameter to be a function.'); + } + + var attrs = {}; + + var { source, target } = this.attributes; + if (!source.id) { + attrs.source = fn(source); + } + if (!target.id) { + attrs.target = fn(target); + } + + var vertices = this.vertices(); + if (vertices.length > 0) { + attrs.vertices = vertices.map(fn); + } + + return this.set(attrs, opt); + }, + + getSourcePoint: function() { + var sourceCell = this.getSourceCell(); + if (!sourceCell) return new Point(this.source()); + return sourceCell.getPointFromConnectedLink(this, 'source'); + }, + + getTargetPoint: function() { + var targetCell = this.getTargetCell(); + if (!targetCell) return new Point(this.target()); + return targetCell.getPointFromConnectedLink(this, 'target'); + }, + + getPointFromConnectedLink: function(/* link, endType */) { + return this.getPolyline().pointAt(0.5); + }, + + getPolyline: function() { + const points = [ + this.getSourcePoint(), + ...this.vertices().map(Point), + this.getTargetPoint() + ]; + return new Polyline(points); + }, + + getBBox: function() { + return this.getPolyline().bbox(); + }, + + reparent: function(opt) { + + var newParent; + + if (this.graph) { + + var source = this.getSourceElement(); + var target = this.getTargetElement(); + var prevParent = this.getParentCell(); + + if (source && target) { + if (source === target || source.isEmbeddedIn(target)) { + newParent = target; + } else if (target.isEmbeddedIn(source)) { + newParent = source; + } else { + newParent = this.graph.getCommonAncestor(source, target); + } + } + + if (prevParent && (!newParent || newParent.id !== prevParent.id)) { + // Unembed the link if source and target has no common ancestor + // or common ancestor changed + prevParent.unembed(this, opt); + } + + if (newParent) { + newParent.embed(this, opt); + } + } + + return newParent; + }, + + hasLoop: function(opt) { + + opt = opt || {}; + + var { source, target } = this.attributes; + var sourceId = source.id; + var targetId = target.id; + + if (!sourceId || !targetId) { + // Link "pinned" to the paper does not have a loop. + return false; + } + + var loop = sourceId === targetId; + + // Note that there in the deep mode a link can have a loop, + // even if it connects only a parent and its embed. + // A loop "target equals source" is valid in both shallow and deep mode. + if (!loop && opt.deep && this.graph) { + + var sourceElement = this.getSourceCell(); + var targetElement = this.getTargetCell(); + + loop = sourceElement.isEmbeddedIn(targetElement) || targetElement.isEmbeddedIn(sourceElement); + } + + return loop; + }, + + // unlike source(), this method returns null if source is a point + getSourceCell: function() { + + const { graph, attributes } = this; + var source = attributes.source; + return (source && source.id && graph && graph.getCell(source.id)) || null; + }, + + getSourceElement: function() { + var cell = this; + var visited = {}; + do { + if (visited[cell.id]) return null; + visited[cell.id] = true; + cell = cell.getSourceCell(); + } while (cell && cell.isLink()); + return cell; + }, + + // unlike target(), this method returns null if target is a point + getTargetCell: function() { + + const { graph, attributes } = this; + var target = attributes.target; + return (target && target.id && graph && graph.getCell(target.id)) || null; + }, + + getTargetElement: function() { + var cell = this; + var visited = {}; + do { + if (visited[cell.id]) return null; + visited[cell.id] = true; + cell = cell.getTargetCell(); + } while (cell && cell.isLink()); + return cell; + }, + + // Returns the common ancestor for the source element, + // target element and the link itself. + getRelationshipAncestor: function() { + + var connectionAncestor; + + if (this.graph) { + + var cells = [ + this, + this.getSourceElement(), // null if source is a point + this.getTargetElement() // null if target is a point + ].filter(function(item) { + return !!item; + }); + + connectionAncestor = this.graph.getCommonAncestor.apply(this.graph, cells); + } + + return connectionAncestor || null; + }, + + // Is source, target and the link itself embedded in a given cell? + isRelationshipEmbeddedIn: function(cell) { + + var cellId = (isString(cell) || isNumber(cell)) ? cell : cell.id; + var ancestor = this.getRelationshipAncestor(); + + return !!ancestor && (ancestor.id === cellId || ancestor.isEmbeddedIn(cellId)); + }, + + // Get resolved default label. + _getDefaultLabel: function() { + + var defaultLabel = this.get('defaultLabel') || this.defaultLabel || {}; + + var label = {}; + label.markup = defaultLabel.markup || this.get('labelMarkup') || this.labelMarkup; + label.position = defaultLabel.position; + label.attrs = defaultLabel.attrs; + label.size = defaultLabel.size; + + return label; + } +}, { + + endsEqual: function(a, b) { + + var portsEqual = a.port === b.port || !a.port && !b.port; + return a.id === b.id && portsEqual; + } +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/ports.mjs + + + + + + +var PortData = function(data) { + + var clonedData = cloneDeep(data) || {}; + this.ports = []; + this.groups = {}; + this.portLayoutNamespace = port_namespaceObject; + this.portLabelLayoutNamespace = portLabel_namespaceObject; + + this._init(clonedData); +}; + +PortData.prototype = { + + getPorts: function() { + return this.ports; + }, + + getGroup: function(name) { + return this.groups[name] || {}; + }, + + getPortsByGroup: function(groupName) { + + return this.ports.filter(function(port) { + return port.group === groupName; + }); + }, + + getGroupPortsMetrics: function(groupName, elBBox) { + + var group = this.getGroup(groupName); + var ports = this.getPortsByGroup(groupName); + + var groupPosition = group.position || {}; + var groupPositionName = groupPosition.name; + var namespace = this.portLayoutNamespace; + if (!namespace[groupPositionName]) { + groupPositionName = 'left'; + } + + var groupArgs = groupPosition.args || {}; + var portsArgs = ports.map(function(port) { + return port && port.position && port.position.args; + }); + var groupPortTransformations = namespace[groupPositionName](portsArgs, elBBox, groupArgs); + + var accumulator = { + ports: ports, + result: [] + }; + + toArray(groupPortTransformations).reduce(function(res, portTransformation, index) { + var port = res.ports[index]; + res.result.push({ + portId: port.id, + portTransformation: portTransformation, + labelTransformation: this._getPortLabelLayout(port, Point(portTransformation), elBBox), + portAttrs: port.attrs, + portSize: port.size, + labelSize: port.label.size + }); + return res; + }.bind(this), accumulator); + + return accumulator.result; + }, + + _getPortLabelLayout: function(port, portPosition, elBBox) { + + var namespace = this.portLabelLayoutNamespace; + var labelPosition = port.label.position.name || 'left'; + + if (namespace[labelPosition]) { + return namespace[labelPosition](portPosition, elBBox, port.label.position.args); + } + + return null; + }, + + _init: function(data) { + + // prepare groups + if (isObject(data.groups)) { + var groups = Object.keys(data.groups); + for (var i = 0, n = groups.length; i < n; i++) { + var key = groups[i]; + this.groups[key] = this._evaluateGroup(data.groups[key]); + } + } + + // prepare ports + var ports = toArray(data.items); + for (var j = 0, m = ports.length; j < m; j++) { + this.ports.push(this._evaluatePort(ports[j])); + } + }, + + _evaluateGroup: function(group) { + + return merge(group, { + position: this._getPosition(group.position, true), + label: this._getLabel(group, true) + }); + }, + + _evaluatePort: function(port) { + + var evaluated = utilHelpers_assign({}, port); + + var group = this.getGroup(port.group); + + evaluated.markup = evaluated.markup || group.markup; + evaluated.attrs = merge({}, group.attrs, evaluated.attrs); + evaluated.position = this._createPositionNode(group, evaluated); + evaluated.label = merge({}, group.label, this._getLabel(evaluated)); + evaluated.z = this._getZIndex(group, evaluated); + evaluated.size = utilHelpers_assign({}, group.size, evaluated.size); + + return evaluated; + }, + + _getZIndex: function(group, port) { + + if (isNumber(port.z)) { + return port.z; + } + if (isNumber(group.z) || group.z === 'auto') { + return group.z; + } + return 'auto'; + }, + + _createPositionNode: function(group, port) { + + return merge({ + name: 'left', + args: {} + }, group.position, { args: port.args }); + }, + + _getPosition: function(position, setDefault) { + + var args = {}; + var positionName; + + if (isFunction(position)) { + positionName = 'fn'; + args.fn = position; + } else if (isString(position)) { + positionName = position; + } else if (position === undefined) { + positionName = setDefault ? 'left' : null; + } else if (Array.isArray(position)) { + positionName = 'absolute'; + args.x = position[0]; + args.y = position[1]; + } else if (isObject(position)) { + positionName = position.name; + utilHelpers_assign(args, position.args); + } + + var result = { args: args }; + + if (positionName) { + result.name = positionName; + } + return result; + }, + + _getLabel: function(item, setDefaults) { + + var label = item.label || {}; + + var ret = label; + ret.position = this._getPosition(label.position, setDefaults); + + return ret; + } +}; + +const elementPortPrototype = { + + _initializePorts: function() { + + this._createPortData(); + this.on('change:ports', function() { + + this._processRemovedPort(); + this._createPortData(); + }, this); + }, + + /** + * remove links tied wiht just removed element + * @private + */ + _processRemovedPort: function() { + + var current = this.get('ports') || {}; + var currentItemsMap = {}; + + toArray(current.items).forEach(function(item) { + currentItemsMap[item.id] = true; + }); + + var previous = this.previous('ports') || {}; + var removed = {}; + + toArray(previous.items).forEach(function(item) { + if (!currentItemsMap[item.id]) { + removed[item.id] = true; + } + }); + + var graph = this.graph; + if (graph && !isEmpty(removed)) { + + var inboundLinks = graph.getConnectedLinks(this, { inbound: true }); + inboundLinks.forEach(function(link) { + + if (removed[link.get('target').port]) link.remove(); + }); + + var outboundLinks = graph.getConnectedLinks(this, { outbound: true }); + outboundLinks.forEach(function(link) { + + if (removed[link.get('source').port]) link.remove(); + }); + } + }, + + /** + * @returns {boolean} + */ + hasPorts: function() { + + var ports = this.prop('ports/items'); + return Array.isArray(ports) && ports.length > 0; + }, + + /** + * @param {string} id + * @returns {boolean} + */ + hasPort: function(id) { + + return this.getPortIndex(id) !== -1; + }, + + /** + * @returns {Array} + */ + getPorts: function() { + + return cloneDeep(this.prop('ports/items')) || []; + }, + + /** + * @returns {Array} + */ + getGroupPorts: function(groupName) { + const groupPorts = toArray(this.prop(['ports','items'])).filter(port => port.group === groupName); + return cloneDeep(groupPorts); + }, + + /** + * @param {string} id + * @returns {object} + */ + getPort: function(id) { + + return cloneDeep(toArray(this.prop('ports/items')).find(function(port) { + return port.id && port.id === id; + })); + }, + + /** + * @param {string} groupName + * @returns {Object} + */ + getPortsPositions: function(groupName) { + + var portsMetrics = this._portSettingsData.getGroupPortsMetrics(groupName, Rect(this.size())); + + return portsMetrics.reduce(function(positions, metrics) { + var transformation = metrics.portTransformation; + positions[metrics.portId] = { + x: transformation.x, + y: transformation.y, + angle: transformation.angle + }; + return positions; + }, {}); + }, + + /** + * @param {string|Port} port port id or port + * @returns {number} port index + */ + getPortIndex: function(port) { + + var id = isObject(port) ? port.id : port; + + if (!this._isValidPortId(id)) { + return -1; + } + + return toArray(this.prop('ports/items')).findIndex(function(item) { + return item.id === id; + }); + }, + + /** + * @param {object} port + * @param {object} [opt] + * @returns {joint.dia.Element} + */ + addPort: function(port, opt) { + + if (!isObject(port) || Array.isArray(port)) { + throw new Error('Element: addPort requires an object.'); + } + + var ports = utilHelpers_assign([], this.prop('ports/items')); + ports.push(port); + this.prop('ports/items', ports, opt); + + return this; + }, + + /** + * @param {string|Port|number} before + * @param {object} port + * @param {object} [opt] + * @returns {joint.dia.Element} + */ + insertPort: function(before, port, opt) { + const index = (typeof before === 'number') ? before : this.getPortIndex(before); + + if (!isObject(port) || Array.isArray(port)) { + throw new Error('dia.Element: insertPort requires an object.'); + } + + const ports = utilHelpers_assign([], this.prop('ports/items')); + ports.splice(index, 0, port); + this.prop('ports/items', ports, opt); + + return this; + }, + + /** + * @param {string} portId + * @param {string|object=} path + * @param {*=} value + * @param {object=} opt + * @returns {joint.dia.Element} + */ + portProp: function(portId, path, value, opt) { + + var index = this.getPortIndex(portId); + + if (index === -1) { + throw new Error('Element: unable to find port with id ' + portId); + } + + var args = Array.prototype.slice.call(arguments, 1); + if (Array.isArray(path)) { + args[0] = ['ports', 'items', index].concat(path); + } else if (isString(path)) { + + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + args[0] = ['ports/items/', index, '/', path].join(''); + + } else { + + args = ['ports/items/' + index]; + if (isPlainObject(path)) { + args.push(path); + args.push(value); + } + } + + return this.prop.apply(this, args); + }, + + _validatePorts: function() { + + var portsAttr = this.get('ports') || {}; + + var errorMessages = []; + portsAttr = portsAttr || {}; + var ports = toArray(portsAttr.items); + + ports.forEach(function(p) { + + if (typeof p !== 'object') { + errorMessages.push('Element: invalid port ', p); + } + + if (!this._isValidPortId(p.id)) { + p.id = this.generatePortId(); + } + }, this); + + if (uniq(ports, 'id').length !== ports.length) { + errorMessages.push('Element: found id duplicities in ports.'); + } + + return errorMessages; + }, + + generatePortId: function() { + return this.generateId(); + }, + + /** + * @param {string} id port id + * @returns {boolean} + * @private + */ + _isValidPortId: function(id) { + + return id !== null && id !== undefined && !isObject(id); + }, + + addPorts: function(ports, opt) { + + if (ports.length) { + this.prop('ports/items', utilHelpers_assign([], this.prop('ports/items')).concat(ports), opt); + } + + return this; + }, + + removePort: function(port, opt) { + const options = opt || {}; + const index = this.getPortIndex(port); + if (index !== -1) { + const ports = utilHelpers_assign([], this.prop(['ports', 'items'])); + ports.splice(index, 1); + options.rewrite = true; + this.startBatch('port-remove'); + this.prop(['ports', 'items'], ports, options); + this.stopBatch('port-remove'); + } + return this; + }, + + removePorts: function(portsForRemoval, opt) { + let options, newPorts; + if (Array.isArray(portsForRemoval)) { + options = opt || {}; + if (portsForRemoval.length === 0) return this.this; + const currentPorts = utilHelpers_assign([], this.prop(['ports', 'items'])); + newPorts = currentPorts.filter(function(cp) { + return !portsForRemoval.some(function(rp) { + const rpId = isObject(rp) ? rp.id : rp; + return cp.id === rpId; + }); + }); + } else { + options = portsForRemoval || {}; + newPorts = []; + } + this.startBatch('port-remove'); + options.rewrite = true; + this.prop(['ports', 'items'], newPorts, options); + this.stopBatch('port-remove'); + return this; + }, + + /** + * @private + */ + _createPortData: function() { + + var err = this._validatePorts(); + + if (err.length > 0) { + this.set('ports', this.previous('ports')); + throw new Error(err.join(' ')); + } + + var prevPortData; + + if (this._portSettingsData) { + + prevPortData = this._portSettingsData.getPorts(); + } + + this._portSettingsData = new PortData(this.get('ports')); + + var curPortData = this._portSettingsData.getPorts(); + + if (prevPortData) { + + var added = curPortData.filter(function(item) { + if (!prevPortData.find(function(prevPort) { + return prevPort.id === item.id; + })) { + return item; + } + }); + + var removed = prevPortData.filter(function(item) { + if (!curPortData.find(function(curPort) { + return curPort.id === item.id; + })) { + return item; + } + }); + + if (removed.length > 0) { + this.trigger('ports:remove', this, removed); + } + + if (added.length > 0) { + this.trigger('ports:add', this, added); + } + } + } +}; + +const elementViewPortPrototype = { + + portContainerMarkup: 'g', + portMarkup: [{ + tagName: 'circle', + selector: 'circle', + attributes: { + 'r': 10, + 'fill': '#FFFFFF', + 'stroke': '#000000' + } + }], + portLabelMarkup: [{ + tagName: 'text', + selector: 'text', + attributes: { + 'fill': '#000000' + } + }], + /** @type {Object} */ + _portElementsCache: null, + + /** + * @private + */ + _initializePorts: function() { + this._cleanPortsCache(); + }, + + /** + * @typedef {Object} Port + * + * @property {string} id + * @property {Object} position + * @property {Object} label + * @property {Object} attrs + * @property {string} markup + * @property {string} group + */ + + /** + * @private + */ + _refreshPorts: function() { + + this._removePorts(); + this._cleanPortsCache(); + this._renderPorts(); + }, + + _cleanPortsCache: function() { + this._portElementsCache = {}; + }, + + /** + * @private + */ + _renderPorts: function() { + + // references to rendered elements without z-index + var elementReferences = []; + var elem = this._getContainerElement(); + + for (var i = 0, count = elem.node.childNodes.length; i < count; i++) { + elementReferences.push(elem.node.childNodes[i]); + } + + var portsGropsByZ = groupBy(this.model._portSettingsData.getPorts(), 'z'); + var withoutZKey = 'auto'; + + // render non-z first + toArray(portsGropsByZ[withoutZKey]).forEach(function(port) { + var portElement = this._getPortElement(port); + elem.append(portElement); + elementReferences.push(portElement); + }, this); + + var groupNames = Object.keys(portsGropsByZ); + for (var k = 0; k < groupNames.length; k++) { + var groupName = groupNames[k]; + if (groupName !== withoutZKey) { + var z = parseInt(groupName, 10); + this._appendPorts(portsGropsByZ[groupName], z, elementReferences); + } + } + + this._updatePorts(); + }, + + /** + * @returns {V} + * @private + */ + _getContainerElement: function() { + + return this.rotatableNode || this.vel; + }, + + /** + * @param {Array}ports + * @param {number} z + * @param refs + * @private + */ + _appendPorts: function(ports, z, refs) { + + var containerElement = this._getContainerElement(); + var portElements = toArray(ports).map(this._getPortElement, this); + + if (refs[z] || z < 0) { + src_V(refs[Math.max(z, 0)]).before(portElements); + } else { + containerElement.append(portElements); + } + }, + + /** + * Try to get element from cache, + * @param port + * @returns {*} + * @private + */ + _getPortElement: function(port) { + + if (this._portElementsCache[port.id]) { + return this._portElementsCache[port.id].portElement; + } + return this._createPortElement(port); + }, + + findPortNode: function(portId, selector) { + const portCache = this._portElementsCache[portId]; + if (!portCache) return null; + if (!selector) return portCache.portContentElement.node; + const portRoot = portCache.portElement.node; + const portSelectors = portCache.portSelectors; + const [node = null] = this.findBySelector(selector, portRoot, portSelectors); + return node; + }, + + /** + * @private + */ + _updatePorts: function() { + + // layout ports without group + this._updatePortGroup(undefined); + // layout ports with explicit group + var groupsNames = Object.keys(this.model._portSettingsData.groups); + groupsNames.forEach(this._updatePortGroup, this); + }, + + /** + * @private + */ + _removePorts: function() { + invoke(this._portElementsCache, 'portElement.remove'); + }, + + /** + * @param {Port} port + * @returns {V} + * @private + */ + _createPortElement: function(port) { + + let portElement; + let labelElement; + let labelSelectors; + let portSelectors; + + var portContainerElement = src_V(this.portContainerMarkup).addClass('joint-port'); + + var portMarkup = this._getPortMarkup(port); + if (Array.isArray(portMarkup)) { + var portDoc = this.parseDOMJSON(portMarkup, portContainerElement.node); + var portFragment = portDoc.fragment; + if (portFragment.childNodes.length > 1) { + portElement = src_V('g').append(portFragment); + } else { + portElement = src_V(portFragment.firstChild); + } + portSelectors = portDoc.selectors; + } else { + portElement = src_V(portMarkup); + if (Array.isArray(portElement)) { + portElement = src_V('g').append(portElement); + } + } + + if (!portElement) { + throw new Error('ElementView: Invalid port markup.'); + } + + portElement.attr({ + 'port': port.id, + 'port-group': port.group + }); + + const labelMarkupDef = this._getPortLabelMarkup(port.label); + if (Array.isArray(labelMarkupDef)) { + // JSON Markup + const { fragment, selectors } = this.parseDOMJSON(labelMarkupDef, portContainerElement.node); + const childCount = fragment.childNodes.length; + if (childCount > 0) { + labelSelectors = selectors; + labelElement = (childCount === 1) ? src_V(fragment.firstChild) : src_V('g').append(fragment); + } + } else { + // String Markup + labelElement = src_V(labelMarkupDef); + if (Array.isArray(labelElement)) { + labelElement = src_V('g').append(labelElement); + } + } + + var portContainerSelectors; + if (portSelectors && labelSelectors) { + for (var key in labelSelectors) { + if (portSelectors[key] && key !== this.selector) throw new Error('ElementView: selectors within port must be unique.'); + } + portContainerSelectors = utilHelpers_assign({}, portSelectors, labelSelectors); + } else { + portContainerSelectors = portSelectors || labelSelectors || {}; + } + + // The `portRootSelector` points to the root SVGNode of the port. + // Either the implicit wrapping group in case the port consist of multiple SVGNodes. + // Or the single SVGNode of the port. + const portRootSelector = 'portRoot'; + // The `labelRootSelector` points to the root SVGNode of the label. + const labelRootSelector = 'labelRoot'; + // The `labelTextSelector` points to all text SVGNodes of the label. + const labelTextSelector = 'labelText'; + + if (!(portRootSelector in portContainerSelectors)) { + portContainerSelectors[portRootSelector] = portElement.node; + } + + if (labelElement) { + const labelNode = labelElement.node; + if (!(labelRootSelector in portContainerSelectors)) { + portContainerSelectors[labelRootSelector] = labelNode; + } + if (!(labelTextSelector in portContainerSelectors)) { + // If the label is a element, we can use it directly. + // Otherwise, we need to find the element within the label. + const labelTextNode = (labelElement.tagName() === 'TEXT') + ? labelNode + : Array.from(labelNode.querySelectorAll('text')); + portContainerSelectors[labelTextSelector] = labelTextNode; + if (!labelSelectors) labelSelectors = {}; + labelSelectors[labelTextSelector] = labelTextNode; + } + } + + portContainerElement.append(portElement.addClass('joint-port-body')); + if (labelElement) { + portContainerElement.append(labelElement.addClass('joint-port-label')); + } + + this._portElementsCache[port.id] = { + portElement: portContainerElement, + portLabelElement: labelElement, + portSelectors: portContainerSelectors, + portLabelSelectors: labelSelectors, + portContentElement: portElement, + portContentSelectors: portSelectors + }; + + return portContainerElement; + }, + + /** + * @param {string=} groupName + * @private + */ + _updatePortGroup: function(groupName) { + + var elementBBox = Rect(this.model.size()); + var portsMetrics = this.model._portSettingsData.getGroupPortsMetrics(groupName, elementBBox); + + for (var i = 0, n = portsMetrics.length; i < n; i++) { + var metrics = portsMetrics[i]; + var portId = metrics.portId; + var cached = this._portElementsCache[portId] || {}; + var portTransformation = metrics.portTransformation; + var labelTransformation = metrics.labelTransformation; + if (labelTransformation && cached.portLabelElement) { + this.updateDOMSubtreeAttributes(cached.portLabelElement.node, labelTransformation.attrs, { + rootBBox: new Rect(metrics.labelSize), + selectors: cached.portLabelSelectors + }); + this.applyPortTransform(cached.portLabelElement, labelTransformation, (-portTransformation.angle || 0)); + } + this.updateDOMSubtreeAttributes(cached.portElement.node, metrics.portAttrs, { + rootBBox: new Rect(metrics.portSize), + selectors: cached.portSelectors + }); + this.applyPortTransform(cached.portElement, portTransformation); + } + }, + + /** + * @param {Vectorizer} element + * @param {{dx:number, dy:number, angle: number, attrs: Object, x:number: y:number}} transformData + * @param {number=} initialAngle + * @constructor + */ + applyPortTransform: function(element, transformData, initialAngle) { + + var matrix = src_V.createSVGMatrix() + .rotate(initialAngle || 0) + .translate(transformData.x || 0, transformData.y || 0) + .rotate(transformData.angle || 0); + + element.transform(matrix, { absolute: true }); + }, + + /** + * @param {Port} port + * @returns {string} + * @private + */ + _getPortMarkup: function(port) { + + return port.markup || this.model.get('portMarkup') || this.model.portMarkup || this.portMarkup; + }, + + /** + * @param {Object} label + * @returns {string} + * @private + */ + _getPortLabelMarkup: function(label) { + + return label.markup || this.model.get('portLabelMarkup') || this.model.portLabelMarkup || this.portLabelMarkup; + } +}; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/Element.mjs + + + + + +// Element base model. +// ----------------------------- + +const Element_Element = Cell.extend({ + + defaults: { + position: { x: 0, y: 0 }, + size: { width: 1, height: 1 }, + angle: 0 + }, + + initialize: function() { + + this._initializePorts(); + Cell.prototype.initialize.apply(this, arguments); + }, + + /** + * @abstract + */ + _initializePorts: function() { + // implemented in ports.js + }, + + _refreshPorts: function() { + // implemented in ports.js + }, + + isElement: function() { + + return true; + }, + + position: function(x, y, opt) { + + const isSetter = isNumber(y); + opt = (isSetter ? opt : x) || {}; + const { parentRelative, deep, restrictedArea } = opt; + + + // option `parentRelative` for setting the position relative to the element's parent. + let parentPosition; + if (parentRelative) { + + // Getting the parent's position requires the collection. + // Cell.parent() holds cell id only. + if (!this.graph) throw new Error('Element must be part of a graph.'); + + const parent = this.getParentCell(); + if (parent && !parent.isLink()) { + parentPosition = parent.get('position'); + } + } + + if (isSetter) { + + if (parentPosition) { + x += parentPosition.x; + y += parentPosition.y; + } + + if (deep || restrictedArea) { + const { x: x0, y: y0 } = this.get('position'); + this.translate(x - x0, y - y0, opt); + } else { + this.set('position', { x, y }, opt); + } + + return this; + + } else { // Getter returns a geometry point. + + const elementPosition = Point(this.get('position')); + return parentRelative + ? elementPosition.difference(parentPosition) + : elementPosition; + } + }, + + translate: function(tx, ty, opt) { + + tx = tx || 0; + ty = ty || 0; + + if (tx === 0 && ty === 0) { + // Like nothing has happened. + return this; + } + + opt = opt || {}; + // Pass the initiator of the translation. + opt.translateBy = opt.translateBy || this.id; + + var position = this.get('position') || { x: 0, y: 0 }; + var ra = opt.restrictedArea; + if (ra && opt.translateBy === this.id) { + + if (typeof ra === 'function') { + + var newPosition = ra.call(this, position.x + tx, position.y + ty, opt); + + tx = newPosition.x - position.x; + ty = newPosition.y - position.y; + + } else { + // We are restricting the translation for the element itself only. We get + // the bounding box of the element including all its embeds. + // All embeds have to be translated the exact same way as the element. + var bbox = this.getBBox({ deep: true }); + //- - - - - - - - - - - - -> ra.x + ra.width + // - - - -> position.x | + // -> bbox.x + // ▓▓▓▓▓▓▓ | + // ░░░░░░░▓▓▓▓▓▓▓ + // ░░░░░░░░░ | + // ▓▓▓▓▓▓▓▓░░░░░░░ + // ▓▓▓▓▓▓▓▓ | + // <-dx-> | restricted area right border + // <-width-> | ░ translated element + // <- - bbox.width - -> ▓ embedded element + var dx = position.x - bbox.x; + var dy = position.y - bbox.y; + // Find the maximal/minimal coordinates that the element can be translated + // while complies the restrictions. + var x = Math.max(ra.x + dx, Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx)); + var y = Math.max(ra.y + dy, Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty)); + // recalculate the translation taking the restrictions into account. + tx = x - position.x; + ty = y - position.y; + } + } + + var translatedPosition = { + x: position.x + tx, + y: position.y + ty + }; + + // To find out by how much an element was translated in event 'change:position' handlers. + opt.tx = tx; + opt.ty = ty; + + if (opt.transition) { + + if (!isObject(opt.transition)) opt.transition = {}; + + this.transition('position', translatedPosition, utilHelpers_assign({}, opt.transition, { + valueFunction: interpolate.object + })); + + // Recursively call `translate()` on all the embeds cells. + invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt); + + } else { + + this.startBatch('translate', opt); + this.set('position', translatedPosition, opt); + invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt); + this.stopBatch('translate', opt); + } + + return this; + }, + + size: function(width, height, opt) { + + var currentSize = this.get('size'); + // Getter + // () signature + if (width === undefined) { + return { + width: currentSize.width, + height: currentSize.height + }; + } + // Setter + // (size, opt) signature + if (isObject(width)) { + opt = height; + height = isNumber(width.height) ? width.height : currentSize.height; + width = isNumber(width.width) ? width.width : currentSize.width; + } + + return this.resize(width, height, opt); + }, + + resize: function(width, height, opt) { + + opt = opt || {}; + + this.startBatch('resize', opt); + + if (opt.direction) { + + var currentSize = this.get('size'); + + switch (opt.direction) { + + case 'left': + case 'right': + // Don't change height when resizing horizontally. + height = currentSize.height; + break; + + case 'top': + case 'bottom': + // Don't change width when resizing vertically. + width = currentSize.width; + break; + } + + // Get the angle and clamp its value between 0 and 360 degrees. + var angle = normalizeAngle(this.get('angle') || 0); + + // This is a rectangle in size of the un-rotated element. + var bbox = this.getBBox(); + + var origin; + + if (angle) { + + var quadrant = { + 'top-right': 0, + 'right': 0, + 'top-left': 1, + 'top': 1, + 'bottom-left': 2, + 'left': 2, + 'bottom-right': 3, + 'bottom': 3 + }[opt.direction]; + + if (opt.absolute) { + + // We are taking the element's rotation into account + quadrant += Math.floor((angle + 45) / 90); + quadrant %= 4; + } + + // Pick the corner point on the element, which meant to stay on its place before and + // after the rotation. + var fixedPoint = bbox[['bottomLeft', 'corner', 'topRight', 'origin'][quadrant]](); + + // Find an image of the previous indent point. This is the position, where is the + // point actually located on the screen. + var imageFixedPoint = Point(fixedPoint).rotate(bbox.center(), -angle); + + // Every point on the element rotates around a circle with the centre of rotation + // in the middle of the element while the whole element is being rotated. That means + // that the distance from a point in the corner of the element (supposed its always rect) to + // the center of the element doesn't change during the rotation and therefore it equals + // to a distance on un-rotated element. + // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5. + var radius = Math.sqrt((width * width) + (height * height)) / 2; + + // Now we are looking for an angle between x-axis and the line starting at image of fixed point + // and ending at the center of the element. We call this angle `alpha`. + + // The image of a fixed point is located in n-th quadrant. For each quadrant passed + // going anti-clockwise we have to add 90 degrees. Note that the first quadrant has index 0. + // + // 3 | 2 + // --c-- Quadrant positions around the element's center `c` + // 0 | 1 + // + var alpha = quadrant * Math.PI / 2; + + // Add an angle between the beginning of the current quadrant (line parallel with x-axis or y-axis + // going through the center of the element) and line crossing the indent of the fixed point and the center + // of the element. This is the angle we need but on the un-rotated element. + alpha += Math.atan(quadrant % 2 == 0 ? height / width : width / height); + + // Lastly we have to deduct the original angle the element was rotated by and that's it. + alpha -= toRad(angle); + + // With this angle and distance we can easily calculate the centre of the un-rotated element. + // Note that fromPolar constructor accepts an angle in radians. + var center = Point.fromPolar(radius, alpha, imageFixedPoint); + + // The top left corner on the un-rotated element has to be half a width on the left + // and half a height to the top from the center. This will be the origin of rectangle + // we were looking for. + origin = Point(center).offset(width / -2, height / -2); + + } else { + // calculation for the origin Point when there is no rotation of the element + origin = bbox.topLeft(); + + switch (opt.direction) { + case 'top': + case 'top-right': + origin.offset(0, bbox.height - height); + break; + case 'left': + case 'bottom-left': + origin.offset(bbox.width -width, 0); + break; + case 'top-left': + origin.offset(bbox.width - width, bbox.height - height); + break; + } + } + + // Resize the element (before re-positioning it). + this.set('size', { width: width, height: height }, opt); + + // Finally, re-position the element. + this.position(origin.x, origin.y, opt); + + } else { + + // Resize the element. + this.set('size', { width: width, height: height }, opt); + } + + this.stopBatch('resize', opt); + + return this; + }, + + scale: function(sx, sy, origin, opt) { + + var scaledBBox = this.getBBox().scale(sx, sy, origin); + this.startBatch('scale', opt); + this.position(scaledBBox.x, scaledBBox.y, opt); + this.resize(scaledBBox.width, scaledBBox.height, opt); + this.stopBatch('scale'); + return this; + }, + + fitEmbeds: function(opt) { + + return this.fitToChildren(opt); + }, + + fitToChildren: function(opt = {}) { + + // Getting the children's size and position requires the collection. + // Cell.get('embeds') holds an array of cell ids only. + const { graph } = this; + if (!graph) throw new Error('Element must be part of a graph.'); + + const childElements = this.getEmbeddedCells().filter(cell => cell.isElement()); + if (childElements.length === 0) return this; + + this.startBatch('fit-embeds', opt); + + if (opt.deep) { + // `opt.deep = true` means "fit to all descendants". + // As the first action of the fitting algorithm, recursively apply `fitToChildren()` on all descendants. + // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant, then go up (= this element). + invoke(childElements, 'fitToChildren', opt); + } + + // Set new size and position of this element, based on: + // - union of bboxes of all children + // - inflated by given `opt.padding` + this._fitToElements(Object.assign({ elements: childElements }, opt)); + + this.stopBatch('fit-embeds'); + + return this; + }, + + fitParent: function(opt = {}) { + + const { graph } = this; + if (!graph) throw new Error('Element must be part of a graph.'); + + // When `opt.deep = true`, we want `opt.terminator` to be the last ancestor processed. + // If the current element is `opt.terminator`, it means that this element has already been processed as parent so we can exit now. + if (opt.deep && opt.terminator && ((opt.terminator === this) || (opt.terminator === this.id))) return this; + + const parentElement = this.getParentCell(); + if (!parentElement || !parentElement.isElement()) return this; + + // Get all children of parent element (i.e. this element + any sibling elements). + const siblingElements = parentElement.getEmbeddedCells().filter(cell => cell.isElement()); + if (siblingElements.length === 0) return this; + + this.startBatch('fit-parent', opt); + + // Set new size and position of parent element, based on: + // - union of bboxes of all children of parent element (i.e. this element + any sibling elements) + // - inflated by given `opt.padding` + parentElement._fitToElements(Object.assign({ elements: siblingElements }, opt)); + + if (opt.deep) { + // `opt.deep = true` means "fit all ancestors to their respective children". + // As the last action of the fitting algorithm, recursively apply `fitParent()` on all ancestors. + // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant (= this element), then go up. + parentElement.fitParent(opt); + } + + this.stopBatch('fit-parent'); + + return this; + }, + + // Assumption: This element is part of a graph. + _fitToElements: function(opt = {}) { + + const elementsBBox = this.graph.getCellsBBox(opt.elements); + // If no `opt.elements` were provided, do nothing. + if (!elementsBBox) return; + + const { expandOnly, shrinkOnly } = opt; + // This combination is meaningless, do nothing. + if (expandOnly && shrinkOnly) return; + + // Calculate new size and position of this element based on: + // - union of bboxes of `opt.elements` + // - inflated by `opt.padding` (if not provided, all four properties = 0) + let { x, y, width, height } = elementsBBox; + const { left, right, top, bottom } = normalizeSides(opt.padding); + x -= left; + y -= top; + width += left + right; + height += bottom + top; + let resultBBox = new Rect(x, y, width, height); + + if (expandOnly) { + // Non-shrinking is enforced by taking union of this element's current bbox with bbox calculated from `opt.elements`. + resultBBox = this.getBBox().union(resultBBox); + + } else if (shrinkOnly) { + // Non-expansion is enforced by taking intersection of this element's current bbox with bbox calculated from `opt.elements`. + const intersectionBBox = this.getBBox().intersect(resultBBox); + // If all children are outside this element's current bbox, then `intersectionBBox` is `null` - does not make sense, do nothing. + if (!intersectionBBox) return; + + resultBBox = intersectionBBox; + } + + // Set the new size and position of this element. + this.set({ + position: { x: resultBBox.x, y: resultBBox.y }, + size: { width: resultBBox.width, height: resultBBox.height } + }, opt); + }, + + // Rotate element by `angle` degrees, optionally around `origin` point. + // If `origin` is not provided, it is considered to be the center of the element. + // If `absolute` is `true`, the `angle` is considered is absolute, i.e. it is not + // the difference from the previous angle. + rotate: function(angle, absolute, origin, opt) { + + if (origin) { + + var center = this.getBBox().center(); + var size = this.get('size'); + var position = this.get('position'); + center.rotate(origin, this.get('angle') - angle); + var dx = center.x - size.width / 2 - position.x; + var dy = center.y - size.height / 2 - position.y; + this.startBatch('rotate', { angle: angle, absolute: absolute, origin: origin }); + this.position(position.x + dx, position.y + dy, opt); + this.rotate(angle, absolute, null, opt); + this.stopBatch('rotate'); + + } else { + + this.set('angle', absolute ? angle : (this.get('angle') + angle) % 360, opt); + } + + return this; + }, + + angle: function() { + return normalizeAngle(this.get('angle') || 0); + }, + + getBBox: function(opt = {}) { + + const { graph, attributes } = this; + const { deep, rotate } = opt; + + if (deep && graph) { + // Get all the embedded elements using breadth first algorithm. + const elements = this.getEmbeddedCells({ deep: true, breadthFirst: true }); + // Add the model itself. + elements.push(this); + // Note: the default of getCellsBBox() is rotate=true and can't be + // changed without a breaking change + return graph.getCellsBBox(elements, opt); + } + + const { angle = 0, position: { x, y }, size: { width, height }} = attributes; + const bbox = new Rect(x, y, width, height); + if (rotate) { + bbox.rotateAroundCenter(angle); + } + return bbox; + }, + + getPointFromConnectedLink: function(link, endType) { + // Center of the model + var bbox = this.getBBox(); + var center = bbox.center(); + // Center of a port + var endDef = link.get(endType); + if (!endDef) return center; + var portId = endDef.port; + if (!portId || !this.hasPort(portId)) return center; + var portGroup = this.portProp(portId, ['group']); + var portsPositions = this.getPortsPositions(portGroup); + var portCenter = new Point(portsPositions[portId]).offset(bbox.origin()); + var angle = this.angle(); + if (angle) portCenter.rotate(center, -angle); + return portCenter; + } +}); + +utilHelpers_assign(Element_Element.prototype, elementPortPrototype); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/util/wrappers.mjs + + + +const wrapWith = function(object, methods, wrapper) { + + if (isString(wrapper)) { + + if (!wrappers[wrapper]) { + throw new Error('Unknown wrapper: "' + wrapper + '"'); + } + + wrapper = wrappers[wrapper]; + } + + if (!isFunction(wrapper)) { + throw new Error('Wrapper must be a function.'); + } + + toArray(methods).forEach(function(method) { + object[method] = wrapper(object[method]); + }); +}; + +const wrappers = { + + cells: function(fn) { + + return function() { + + var args = Array.from(arguments); + var n = args.length; + var cells = n > 0 && args[0] || []; + var opt = n > 1 && args[n - 1] || {}; + + if (!Array.isArray(cells)) { + + if (opt instanceof Cell) { + cells = args; + } else if (cells instanceof Cell) { + if (args.length > 1) { + args.pop(); + } + cells = args; + } + } + + if (opt instanceof Cell) { + opt = {}; + } + + return fn.call(this, cells, opt); + }; + } + +}; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/Graph.mjs + + + + + + + + + +const GraphCells = backbone.Collection.extend({ + + initialize: function(models, opt) { + + // Set the optional namespace where all model classes are defined. + if (opt.cellNamespace) { + this.cellNamespace = opt.cellNamespace; + } else { + /* eslint-disable no-undef */ + this.cellNamespace = typeof joint !== 'undefined' && has(joint, 'shapes') ? joint.shapes : null; + /* eslint-enable no-undef */ + } + + + this.graph = opt.graph; + }, + + model: function(attrs, opt) { + + var collection = opt.collection; + var namespace = collection.cellNamespace; + + // Find the model class in the namespace or use the default one. + var ModelClass = (attrs.type === 'link') + ? Link + : getByPath(namespace, attrs.type, '.') || Element_Element; + + var cell = new ModelClass(attrs, opt); + // Add a reference to the graph. It is necessary to do this here because this is the earliest place + // where a new model is created from a plain JS object. For other objects, see `joint.dia.Graph>>_prepareCell()`. + if (!opt.dry) { + cell.graph = collection.graph; + } + + return cell; + }, + + // `comparator` makes it easy to sort cells based on their `z` index. + comparator: function(model) { + + return model.get('z') || 0; + } +}); + + +const Graph = backbone.Model.extend({ + + initialize: function(attrs, opt) { + + opt = opt || {}; + + // Passing `cellModel` function in the options object to graph allows for + // setting models based on attribute objects. This is especially handy + // when processing JSON graphs that are in a different than JointJS format. + var cells = new GraphCells([], { + model: opt.cellModel, + cellNamespace: opt.cellNamespace, + graph: this + }); + backbone.Model.prototype.set.call(this, 'cells', cells); + + // Make all the events fired in the `cells` collection available. + // to the outside world. + cells.on('all', this.trigger, this); + + // Backbone automatically doesn't trigger re-sort if models attributes are changed later when + // they're already in the collection. Therefore, we're triggering sort manually here. + this.on('change:z', this._sortOnChangeZ, this); + + // `joint.dia.Graph` keeps an internal data structure (an adjacency list) + // for fast graph queries. All changes that affect the structure of the graph + // must be reflected in the `al` object. This object provides fast answers to + // questions such as "what are the neighbours of this node" or "what + // are the sibling links of this link". + + // Outgoing edges per node. Note that we use a hash-table for the list + // of outgoing edges for a faster lookup. + // [nodeId] -> Object [edgeId] -> true + this._out = {}; + // Ingoing edges per node. + // [nodeId] -> Object [edgeId] -> true + this._in = {}; + // `_nodes` is useful for quick lookup of all the elements in the graph, without + // having to go through the whole cells array. + // [node ID] -> true + this._nodes = {}; + // `_edges` is useful for quick lookup of all the links in the graph, without + // having to go through the whole cells array. + // [edgeId] -> true + this._edges = {}; + + this._batches = {}; + + cells.on('add', this._restructureOnAdd, this); + cells.on('remove', this._restructureOnRemove, this); + cells.on('reset', this._restructureOnReset, this); + cells.on('change:source', this._restructureOnChangeSource, this); + cells.on('change:target', this._restructureOnChangeTarget, this); + cells.on('remove', this._removeCell, this); + }, + + _sortOnChangeZ: function() { + + this.get('cells').sort(); + }, + + _restructureOnAdd: function(cell) { + + if (cell.isLink()) { + this._edges[cell.id] = true; + var { source, target } = cell.attributes; + if (source.id) { + (this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true; + } + if (target.id) { + (this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true; + } + } else { + this._nodes[cell.id] = true; + } + }, + + _restructureOnRemove: function(cell) { + + if (cell.isLink()) { + delete this._edges[cell.id]; + var { source, target } = cell.attributes; + if (source.id && this._out[source.id] && this._out[source.id][cell.id]) { + delete this._out[source.id][cell.id]; + } + if (target.id && this._in[target.id] && this._in[target.id][cell.id]) { + delete this._in[target.id][cell.id]; + } + } else { + delete this._nodes[cell.id]; + } + }, + + _restructureOnReset: function(cells) { + + // Normalize into an array of cells. The original `cells` is GraphCells Backbone collection. + cells = cells.models; + + this._out = {}; + this._in = {}; + this._nodes = {}; + this._edges = {}; + + cells.forEach(this._restructureOnAdd, this); + }, + + _restructureOnChangeSource: function(link) { + + var prevSource = link.previous('source'); + if (prevSource.id && this._out[prevSource.id]) { + delete this._out[prevSource.id][link.id]; + } + var source = link.attributes.source; + if (source.id) { + (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true; + } + }, + + _restructureOnChangeTarget: function(link) { + + var prevTarget = link.previous('target'); + if (prevTarget.id && this._in[prevTarget.id]) { + delete this._in[prevTarget.id][link.id]; + } + var target = link.get('target'); + if (target.id) { + (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true; + } + }, + + // Return all outbound edges for the node. Return value is an object + // of the form: [edgeId] -> true + getOutboundEdges: function(node) { + + return (this._out && this._out[node]) || {}; + }, + + // Return all inbound edges for the node. Return value is an object + // of the form: [edgeId] -> true + getInboundEdges: function(node) { + + return (this._in && this._in[node]) || {}; + }, + + toJSON: function() { + + // Backbone does not recursively call `toJSON()` on attributes that are themselves models/collections. + // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly. + var json = backbone.Model.prototype.toJSON.apply(this, arguments); + json.cells = this.get('cells').toJSON(); + return json; + }, + + fromJSON: function(json, opt) { + + if (!json.cells) { + + throw new Error('Graph JSON must contain cells array.'); + } + + return this.set(json, opt); + }, + + set: function(key, val, opt) { + + var attrs; + + // Handle both `key`, value and {key: value} style arguments. + if (typeof key === 'object') { + attrs = key; + opt = val; + } else { + (attrs = {})[key] = val; + } + + // Make sure that `cells` attribute is handled separately via resetCells(). + if (attrs.hasOwnProperty('cells')) { + this.resetCells(attrs.cells, opt); + attrs = omit(attrs, 'cells'); + } + + // The rest of the attributes are applied via original set method. + return backbone.Model.prototype.set.call(this, attrs, opt); + }, + + clear: function(opt) { + + opt = utilHelpers_assign({}, opt, { clear: true }); + + var collection = this.get('cells'); + + if (collection.length === 0) return this; + + this.startBatch('clear', opt); + + // The elements come after the links. + var cells = collection.sortBy(function(cell) { + return cell.isLink() ? 1 : 2; + }); + + do { + + // Remove all the cells one by one. + // Note that all the links are removed first, so it's + // safe to remove the elements without removing the connected + // links first. + cells.shift().remove(opt); + + } while (cells.length > 0); + + this.stopBatch('clear'); + + return this; + }, + + _prepareCell: function(cell, opt) { + + var attrs; + if (cell instanceof backbone.Model) { + attrs = cell.attributes; + if (!cell.graph && (!opt || !opt.dry)) { + // An element can not be member of more than one graph. + // A cell stops being the member of the graph after it's explicitly removed. + cell.graph = this; + } + } else { + // In case we're dealing with a plain JS object, we have to set the reference + // to the `graph` right after the actual model is created. This happens in the `model()` function + // of `joint.dia.GraphCells`. + attrs = cell; + } + + if (!isString(attrs.type)) { + throw new TypeError('dia.Graph: cell type must be a string.'); + } + + return cell; + }, + + minZIndex: function() { + + var firstCell = this.get('cells').first(); + return firstCell ? (firstCell.get('z') || 0) : 0; + }, + + maxZIndex: function() { + + var lastCell = this.get('cells').last(); + return lastCell ? (lastCell.get('z') || 0) : 0; + }, + + addCell: function(cell, opt) { + + if (Array.isArray(cell)) { + + return this.addCells(cell, opt); + } + + if (cell instanceof backbone.Model) { + + if (!cell.has('z')) { + cell.set('z', this.maxZIndex() + 1); + } + + } else if (cell.z === undefined) { + + cell.z = this.maxZIndex() + 1; + } + + this.get('cells').add(this._prepareCell(cell, opt), opt || {}); + + return this; + }, + + addCells: function(cells, opt) { + + if (cells.length === 0) return this; + + cells = flattenDeep(cells); + opt.maxPosition = opt.position = cells.length - 1; + + this.startBatch('add', opt); + cells.forEach(function(cell) { + this.addCell(cell, opt); + opt.position--; + }, this); + this.stopBatch('add', opt); + + return this; + }, + + // When adding a lot of cells, it is much more efficient to + // reset the entire cells collection in one go. + // Useful for bulk operations and optimizations. + resetCells: function(cells, opt) { + + var preparedCells = toArray(cells).map(function(cell) { + return this._prepareCell(cell, opt); + }, this); + this.get('cells').reset(preparedCells, opt); + + return this; + }, + + removeCells: function(cells, opt) { + + if (cells.length) { + + this.startBatch('remove'); + invoke(cells, 'remove', opt); + this.stopBatch('remove'); + } + + return this; + }, + + _removeCell: function(cell, collection, options) { + + options = options || {}; + + if (!options.clear) { + // Applications might provide a `disconnectLinks` option set to `true` in order to + // disconnect links when a cell is removed rather then removing them. The default + // is to remove all the associated links. + if (options.disconnectLinks) { + + this.disconnectLinks(cell, options); + + } else { + + this.removeLinks(cell, options); + } + } + // Silently remove the cell from the cells collection. Silently, because + // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is + // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events + // would be triggered on the graph model. + this.get('cells').remove(cell, { silent: true }); + + if (cell.graph === this) { + // Remove the element graph reference only if the cell is the member of this graph. + cell.graph = null; + } + }, + + // Get a cell by `id`. + getCell: function(id) { + + return this.get('cells').get(id); + }, + + getCells: function() { + + return this.get('cells').toArray(); + }, + + getElements: function() { + + return this.get('cells').filter(cell => cell.isElement()); + }, + + getLinks: function() { + + return this.get('cells').filter(cell => cell.isLink()); + }, + + getFirstCell: function() { + + return this.get('cells').first(); + }, + + getLastCell: function() { + + return this.get('cells').last(); + }, + + // Get all inbound and outbound links connected to the cell `model`. + getConnectedLinks: function(model, opt) { + + opt = opt || {}; + + var indirect = opt.indirect; + var inbound = opt.inbound; + var outbound = opt.outbound; + if ((inbound === undefined) && (outbound === undefined)) { + inbound = outbound = true; + } + + // the final array of connected link models + var links = []; + // a hash table of connected edges of the form: [edgeId] -> true + // used for quick lookups to check if we already added a link + var edges = {}; + + if (outbound) { + addOutbounds(this, model); + } + if (inbound) { + addInbounds(this, model); + } + + function addOutbounds(graph, model) { + forIn(graph.getOutboundEdges(model.id), function(_, edge) { + // skip links that were already added + // (those must be self-loop links) + // (because they are inbound and outbound edges of the same two elements) + if (edges[edge]) return; + var link = graph.getCell(edge); + links.push(link); + edges[edge] = true; + if (indirect) { + if (inbound) addInbounds(graph, link); + if (outbound) addOutbounds(graph, link); + } + }.bind(graph)); + if (indirect && model.isLink()) { + var outCell = model.getTargetCell(); + if (outCell && outCell.isLink()) { + if (!edges[outCell.id]) { + links.push(outCell); + addOutbounds(graph, outCell); + } + } + } + } + + function addInbounds(graph, model) { + forIn(graph.getInboundEdges(model.id), function(_, edge) { + // skip links that were already added + // (those must be self-loop links) + // (because they are inbound and outbound edges of the same two elements) + if (edges[edge]) return; + var link = graph.getCell(edge); + links.push(link); + edges[edge] = true; + if (indirect) { + if (inbound) addInbounds(graph, link); + if (outbound) addOutbounds(graph, link); + } + }.bind(graph)); + if (indirect && model.isLink()) { + var inCell = model.getSourceCell(); + if (inCell && inCell.isLink()) { + if (!edges[inCell.id]) { + links.push(inCell); + addInbounds(graph, inCell); + } + } + } + } + + // if `deep` option is `true`, check also all the links that are connected to any of the descendant cells + if (opt.deep) { + + var embeddedCells = model.getEmbeddedCells({ deep: true }); + + // in the first round, we collect all the embedded elements + var embeddedElements = {}; + embeddedCells.forEach(function(cell) { + if (cell.isElement()) { + embeddedElements[cell.id] = true; + } + }); + + embeddedCells.forEach(function(cell) { + if (cell.isLink()) return; + if (outbound) { + forIn(this.getOutboundEdges(cell.id), function(exists, edge) { + if (!edges[edge]) { + var edgeCell = this.getCell(edge); + var { source, target } = edgeCell.attributes; + var sourceId = source.id; + var targetId = target.id; + + // if `includeEnclosed` option is falsy, skip enclosed links + if (!opt.includeEnclosed + && (sourceId && embeddedElements[sourceId]) + && (targetId && embeddedElements[targetId])) { + return; + } + + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + if (inbound) { + forIn(this.getInboundEdges(cell.id), function(exists, edge) { + if (!edges[edge]) { + var edgeCell = this.getCell(edge); + var { source, target } = edgeCell.attributes; + var sourceId = source.id; + var targetId = target.id; + + // if `includeEnclosed` option is falsy, skip enclosed links + if (!opt.includeEnclosed + && (sourceId && embeddedElements[sourceId]) + && (targetId && embeddedElements[targetId])) { + return; + } + + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + }, this); + } + + return links; + }, + + getNeighbors: function(model, opt) { + + opt || (opt = {}); + + var inbound = opt.inbound; + var outbound = opt.outbound; + if (inbound === undefined && outbound === undefined) { + inbound = outbound = true; + } + + var neighbors = this.getConnectedLinks(model, opt).reduce(function(res, link) { + + var { source, target } = link.attributes; + var loop = link.hasLoop(opt); + + // Discard if it is a point, or if the neighbor was already added. + if (inbound && has(source, 'id') && !res[source.id]) { + + var sourceElement = this.getCell(source.id); + if (sourceElement.isElement()) { + if (loop || (sourceElement && sourceElement !== model && (!opt.deep || !sourceElement.isEmbeddedIn(model)))) { + res[source.id] = sourceElement; + } + } + } + + // Discard if it is a point, or if the neighbor was already added. + if (outbound && has(target, 'id') && !res[target.id]) { + + var targetElement = this.getCell(target.id); + if (targetElement.isElement()) { + if (loop || (targetElement && targetElement !== model && (!opt.deep || !targetElement.isEmbeddedIn(model)))) { + res[target.id] = targetElement; + } + } + } + + return res; + }.bind(this), {}); + + if (model.isLink()) { + if (inbound) { + var sourceCell = model.getSourceCell(); + if (sourceCell && sourceCell.isElement() && !neighbors[sourceCell.id]) { + neighbors[sourceCell.id] = sourceCell; + } + } + if (outbound) { + var targetCell = model.getTargetCell(); + if (targetCell && targetCell.isElement() && !neighbors[targetCell.id]) { + neighbors[targetCell.id] = targetCell; + } + } + } + + return toArray(neighbors); + }, + + getCommonAncestor: function(/* cells */) { + + var cellsAncestors = Array.from(arguments).map(function(cell) { + + var ancestors = []; + var parentId = cell.get('parent'); + + while (parentId) { + + ancestors.push(parentId); + parentId = this.getCell(parentId).get('parent'); + } + + return ancestors; + + }, this); + + cellsAncestors = cellsAncestors.sort(function(a, b) { + return a.length - b.length; + }); + + var commonAncestor = toArray(cellsAncestors.shift()).find(function(ancestor) { + return cellsAncestors.every(function(cellAncestors) { + return cellAncestors.includes(ancestor); + }); + }); + + return this.getCell(commonAncestor); + }, + + // Find the whole branch starting at `element`. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search. + getSuccessors: function(element, opt) { + + opt = opt || {}; + var res = []; + // Modify the options so that it includes the `outbound` neighbors only. In other words, search forwards. + this.search(element, function(el) { + if (el !== element) { + res.push(el); + } + }, utilHelpers_assign({}, opt, { outbound: true })); + return res; + }, + + cloneCells: cloneCells, + // Clone the whole subgraph (including all the connected links whose source/target is in the subgraph). + // If `opt.deep` is `true`, also take into account all the embedded cells of all the subgraph cells. + // Return a map of the form: [original cell ID] -> [clone]. + cloneSubgraph: function(cells, opt) { + + var subgraph = this.getSubgraph(cells, opt); + return this.cloneCells(subgraph); + }, + + // Return `cells` and all the connected links that connect cells in the `cells` array. + // If `opt.deep` is `true`, return all the cells including all their embedded cells + // and all the links that connect any of the returned cells. + // For example, for a single shallow element, the result is that very same element. + // For two elements connected with a link: `A --- L ---> B`, the result for + // `getSubgraph([A, B])` is `[A, L, B]`. The same goes for `getSubgraph([L])`, the result is again `[A, L, B]`. + getSubgraph: function(cells, opt) { + + opt = opt || {}; + + var subgraph = []; + // `cellMap` is used for a quick lookup of existence of a cell in the `cells` array. + var cellMap = {}; + var elements = []; + var links = []; + + toArray(cells).forEach(function(cell) { + if (!cellMap[cell.id]) { + subgraph.push(cell); + cellMap[cell.id] = cell; + if (cell.isLink()) { + links.push(cell); + } else { + elements.push(cell); + } + } + + if (opt.deep) { + var embeds = cell.getEmbeddedCells({ deep: true }); + embeds.forEach(function(embed) { + if (!cellMap[embed.id]) { + subgraph.push(embed); + cellMap[embed.id] = embed; + if (embed.isLink()) { + links.push(embed); + } else { + elements.push(embed); + } + } + }); + } + }); + + links.forEach(function(link) { + // For links, return their source & target (if they are elements - not points). + var { source, target } = link.attributes; + if (source.id && !cellMap[source.id]) { + var sourceElement = this.getCell(source.id); + subgraph.push(sourceElement); + cellMap[sourceElement.id] = sourceElement; + elements.push(sourceElement); + } + if (target.id && !cellMap[target.id]) { + var targetElement = this.getCell(target.id); + subgraph.push(this.getCell(target.id)); + cellMap[targetElement.id] = targetElement; + elements.push(targetElement); + } + }, this); + + elements.forEach(function(element) { + // For elements, include their connected links if their source/target is in the subgraph; + var links = this.getConnectedLinks(element, opt); + links.forEach(function(link) { + var { source, target } = link.attributes; + if (!cellMap[link.id] && source.id && cellMap[source.id] && target.id && cellMap[target.id]) { + subgraph.push(link); + cellMap[link.id] = link; + } + }); + }, this); + + return subgraph; + }, + + // Find all the predecessors of `element`. This is a reverse operation of `getSuccessors()`. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search. + getPredecessors: function(element, opt) { + + opt = opt || {}; + var res = []; + // Modify the options so that it includes the `inbound` neighbors only. In other words, search backwards. + this.search(element, function(el) { + if (el !== element) { + res.push(el); + } + }, utilHelpers_assign({}, opt, { inbound: true })); + return res; + }, + + // Perform search on the graph. + // If `opt.breadthFirst` is `true`, use the Breadth-first Search algorithm, otherwise use Depth-first search. + // By setting `opt.inbound` to `true`, you can reverse the direction of the search. + // If `opt.deep` is `true`, take into account embedded elements too. + // `iteratee` is a function of the form `function(element) {}`. + // If `iteratee` explicitly returns `false`, the searching stops. + search: function(element, iteratee, opt) { + + opt = opt || {}; + if (opt.breadthFirst) { + this.bfs(element, iteratee, opt); + } else { + this.dfs(element, iteratee, opt); + } + }, + + // Breadth-first search. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions). + // `iteratee` is a function of the form `function(element, distance) {}`. + // where `element` is the currently visited element and `distance` is the distance of that element + // from the root `element` passed the `bfs()`, i.e. the element we started the search from. + // Note that the `distance` is not the shortest or longest distance, it is simply the number of levels + // crossed till we visited the `element` for the first time. It is especially useful for tree graphs. + // If `iteratee` explicitly returns `false`, the searching stops. + bfs: function(element, iteratee, opt = {}) { + + const visited = {}; + const distance = {}; + const queue = []; + + queue.push(element); + distance[element.id] = 0; + + while (queue.length > 0) { + var next = queue.shift(); + if (visited[next.id]) continue; + visited[next.id] = true; + if (iteratee.call(this, next, distance[next.id]) === false) continue; + const neighbors = this.getNeighbors(next, opt); + for (let i = 0, n = neighbors.length; i < n; i++) { + const neighbor = neighbors[i]; + distance[neighbor.id] = distance[next.id] + 1; + queue.push(neighbor); + } + } + }, + + // Depth-first search. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions). + // `iteratee` is a function of the form `function(element, distance) {}`. + // If `iteratee` explicitly returns `false`, the search stops. + dfs: function(element, iteratee, opt = {}) { + + const visited = {}; + const distance = {}; + const queue = []; + + queue.push(element); + distance[element.id] = 0; + + while (queue.length > 0) { + const next = queue.pop(); + if (visited[next.id]) continue; + visited[next.id] = true; + if (iteratee.call(this, next, distance[next.id]) === false) continue; + const neighbors = this.getNeighbors(next, opt); + const lastIndex = queue.length; + for (let i = 0, n = neighbors.length; i < n; i++) { + const neighbor = neighbors[i]; + distance[neighbor.id] = distance[next.id] + 1; + queue.splice(lastIndex, 0, neighbor); + } + } + }, + + // Get all the roots of the graph. Time complexity: O(|V|). + getSources: function() { + + var sources = []; + forIn(this._nodes, function(exists, node) { + if (!this._in[node] || isEmpty(this._in[node])) { + sources.push(this.getCell(node)); + } + }.bind(this)); + return sources; + }, + + // Get all the leafs of the graph. Time complexity: O(|V|). + getSinks: function() { + + var sinks = []; + forIn(this._nodes, function(exists, node) { + if (!this._out[node] || isEmpty(this._out[node])) { + sinks.push(this.getCell(node)); + } + }.bind(this)); + return sinks; + }, + + // Return `true` if `element` is a root. Time complexity: O(1). + isSource: function(element) { + + return !this._in[element.id] || isEmpty(this._in[element.id]); + }, + + // Return `true` if `element` is a leaf. Time complexity: O(1). + isSink: function(element) { + + return !this._out[element.id] || isEmpty(this._out[element.id]); + }, + + // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise. + isSuccessor: function(elementA, elementB) { + + var isSuccessor = false; + this.search(elementA, function(element) { + if (element === elementB && element !== elementA) { + isSuccessor = true; + return false; + } + }, { outbound: true }); + return isSuccessor; + }, + + // Return `true` is `elementB` is a predecessor of `elementA`. Return `false` otherwise. + isPredecessor: function(elementA, elementB) { + + var isPredecessor = false; + this.search(elementA, function(element) { + if (element === elementB && element !== elementA) { + isPredecessor = true; + return false; + } + }, { inbound: true }); + return isPredecessor; + }, + + // Return `true` is `elementB` is a neighbor of `elementA`. Return `false` otherwise. + // `opt.deep` controls whether to take into account embedded elements as well. See `getNeighbors()` + // for more details. + // If `opt.outbound` is set to `true`, return `true` only if `elementB` is a successor neighbor. + // Similarly, if `opt.inbound` is set to `true`, return `true` only if `elementB` is a predecessor neighbor. + isNeighbor: function(elementA, elementB, opt) { + + opt = opt || {}; + + var inbound = opt.inbound; + var outbound = opt.outbound; + if ((inbound === undefined) && (outbound === undefined)) { + inbound = outbound = true; + } + + var isNeighbor = false; + + this.getConnectedLinks(elementA, opt).forEach(function(link) { + + var { source, target } = link.attributes; + + // Discard if it is a point. + if (inbound && has(source, 'id') && (source.id === elementB.id)) { + isNeighbor = true; + return false; + } + + // Discard if it is a point, or if the neighbor was already added. + if (outbound && has(target, 'id') && (target.id === elementB.id)) { + isNeighbor = true; + return false; + } + }); + + return isNeighbor; + }, + + // Disconnect links connected to the cell `model`. + disconnectLinks: function(model, opt) { + + this.getConnectedLinks(model).forEach(function(link) { + + link.set((link.attributes.source.id === model.id ? 'source' : 'target'), { x: 0, y: 0 }, opt); + }); + }, + + // Remove links connected to the cell `model` completely. + removeLinks: function(model, opt) { + + invoke(this.getConnectedLinks(model), 'remove', opt); + }, + + // Find all elements at given point + findModelsFromPoint: function(p) { + return this.getElements().filter(el => el.getBBox({ rotate: true }).containsPoint(p)); + }, + + // Find all elements in given area + findModelsInArea: function(rect, opt = {}) { + const r = new Rect(rect); + const { strict = false } = opt; + const method = strict ? 'containsRect' : 'intersect'; + return this.getElements().filter(el => r[method](el.getBBox({ rotate: true }))); + }, + + // Find all elements under the given element. + findModelsUnderElement: function(element, opt = {}) { + const { searchBy = 'bbox' } = opt; + const bbox = element.getBBox().rotateAroundCenter(element.angle()); + const elements = (searchBy === 'bbox') + ? this.findModelsInArea(bbox) + : this.findModelsFromPoint(getRectPoint(bbox, searchBy)); + // don't account element itself or any of its descendants + return elements.filter(el => element.id !== el.id && !el.isEmbeddedIn(element)); + }, + + // Return bounding box of all elements. + getBBox: function() { + + return this.getCellsBBox(this.getCells()); + }, + + // Return the bounding box of all cells in array provided. + getCellsBBox: function(cells, opt = {}) { + const { rotate = true } = opt; + return toArray(cells).reduce(function(memo, cell) { + const rect = cell.getBBox({ rotate }); + if (!rect) return memo; + if (memo) { + return memo.union(rect); + } + return rect; + }, null); + }, + + translate: function(dx, dy, opt) { + + // Don't translate cells that are embedded in any other cell. + var cells = this.getCells().filter(function(cell) { + return !cell.isEmbedded(); + }); + + invoke(cells, 'translate', dx, dy, opt); + + return this; + }, + + resize: function(width, height, opt) { + + return this.resizeCells(width, height, this.getCells(), opt); + }, + + resizeCells: function(width, height, cells, opt) { + + // `getBBox` method returns `null` if no elements provided. + // i.e. cells can be an array of links + var bbox = this.getCellsBBox(cells); + if (bbox) { + var sx = Math.max(width / bbox.width, 0); + var sy = Math.max(height / bbox.height, 0); + invoke(cells, 'scale', sx, sy, bbox.origin(), opt); + } + + return this; + }, + + startBatch: function(name, data) { + + data = data || {}; + this._batches[name] = (this._batches[name] || 0) + 1; + + return this.trigger('batch:start', utilHelpers_assign({}, data, { batchName: name })); + }, + + stopBatch: function(name, data) { + + data = data || {}; + this._batches[name] = (this._batches[name] || 0) - 1; + + return this.trigger('batch:stop', utilHelpers_assign({}, data, { batchName: name })); + }, + + hasActiveBatch: function(name) { + + const batches = this._batches; + let names; + + if (arguments.length === 0) { + names = Object.keys(batches); + } else if (Array.isArray(name)) { + names = name; + } else { + names = [name]; + } + + return names.some((batch) => batches[batch] > 0); + } + +}, { + + validations: { + + multiLinks: function(graph, link) { + + // Do not allow multiple links to have the same source and target. + var { source, target } = link.attributes; + + if (source.id && target.id) { + + var sourceModel = link.getSourceCell(); + if (sourceModel) { + + var connectedLinks = graph.getConnectedLinks(sourceModel, { outbound: true }); + var sameLinks = connectedLinks.filter(function(_link) { + + var { source: _source, target: _target } = _link.attributes; + return _source && _source.id === source.id && + (!_source.port || (_source.port === source.port)) && + _target && _target.id === target.id && + (!_target.port || (_target.port === target.port)); + + }); + + if (sameLinks.length > 1) { + return false; + } + } + } + + return true; + }, + + linkPinning: function(_graph, link) { + var { source, target } = link.attributes; + return source.id && target.id; + } + } + +}); + +wrapWith(Graph.prototype, ['resetCells', 'addCells', 'removeCells'], wrappers.cells); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/layout/DirectedGraph/DirectedGraph.mjs + + + + +const DirectedGraph = { + + exportElement: function(element) { + + // The width and height of the element. + return element.size(); + }, + + exportLink: function(link) { + + var labelSize = link.get('labelSize') || {}; + var edge = { + // The number of ranks to keep between the source and target of the edge. + minLen: link.get('minLen') || 1, + // The weight to assign edges. Higher weight edges are generally + // made shorter and straighter than lower weight edges. + weight: link.get('weight') || 1, + // Where to place the label relative to the edge. + // l = left, c = center r = right. + labelpos: link.get('labelPosition') || 'c', + // How many pixels to move the label away from the edge. + // Applies only when labelpos is l or r. + labeloffset: link.get('labelOffset') || 0, + // The width of the edge label in pixels. + width: labelSize.width || 0, + // The height of the edge label in pixels. + height: labelSize.height || 0 + }; + + return edge; + }, + + importElement: function(opt, v, gl) { + + var element = this.getCell(v); + var glNode = gl.node(v); + + if (opt.setPosition) { + opt.setPosition(element, glNode); + } else { + element.set('position', { + x: glNode.x - glNode.width / 2, + y: glNode.y - glNode.height / 2 + }); + } + }, + + importLink: function(opt, edgeObj, gl) { + + const SIMPLIFY_THRESHOLD = 0.001; + + const link = this.getCell(edgeObj.name); + const glEdge = gl.edge(edgeObj); + const points = glEdge.points || []; + const polyline = new Polyline(points); + + // check the `setLinkVertices` here for backwards compatibility + if (opt.setVertices || opt.setLinkVertices) { + if (isFunction(opt.setVertices)) { + opt.setVertices(link, points); + } else { + // simplify the `points` polyline + polyline.simplify({ threshold: SIMPLIFY_THRESHOLD }); + const polylinePoints = polyline.points.map((point) => (point.toJSON())); // JSON of points after simplification + const numPolylinePoints = polylinePoints.length; // number of points after simplification + // set simplified polyline points as link vertices + // remove first and last polyline points (= source/target sonnectionPoints) + link.set('vertices', polylinePoints.slice(1, numPolylinePoints - 1)); + } + } + + if (opt.setLabels && ('x' in glEdge) && ('y' in glEdge)) { + const labelPosition = { x: glEdge.x, y: glEdge.y }; + if (isFunction(opt.setLabels)) { + opt.setLabels(link, labelPosition, points); + } else { + // convert the absolute label position to a relative position + // towards the closest point on the edge + const length = polyline.closestPointLength(labelPosition); + const closestPoint = polyline.pointAtLength(length); + const distance = (length / polyline.length()); + const offset = new Point(labelPosition).difference(closestPoint).toJSON(); + link.label(0, { + position: { + distance: distance, + offset: offset + } + }); + } + } + }, + + layout: function(graphOrCells, opt) { + + var graph; + + if (graphOrCells instanceof Graph) { + graph = graphOrCells; + } else { + // Reset cells in dry mode so the graph reference is not stored on the cells. + // `sort: false` to prevent elements to change their order based on the z-index + graph = (new Graph()).resetCells(graphOrCells, { dry: true, sort: false }); + } + + // This is not needed anymore. + graphOrCells = null; + + opt = defaults(opt || {}, { + resizeClusters: true, + clusterPadding: 10, + exportElement: this.exportElement, + exportLink: this.exportLink + }); + + /* eslint-disable no-undef */ + const dagreUtil = opt.dagre || (typeof dagre !== 'undefined' ? dagre : undefined); + /* eslint-enable no-undef */ + + if (dagreUtil === undefined) throw new Error('The the "dagre" utility is a mandatory dependency.'); + + // create a graphlib.Graph that represents the joint.dia.Graph + // var glGraph = graph.toGraphLib({ + var glGraph = DirectedGraph.toGraphLib(graph, { + graphlib: opt.graphlib, + directed: true, + // We are about to use edge naming feature. + multigraph: true, + // We are able to layout graphs with embeds. + compound: true, + setNodeLabel: opt.exportElement, + setEdgeLabel: opt.exportLink, + setEdgeName: function(link) { + // Graphlib edges have no ids. We use edge name property + // to store and retrieve ids instead. + return link.id; + } + }); + + var glLabel = {}; + var marginX = opt.marginX || 0; + var marginY = opt.marginY || 0; + + // Dagre layout accepts options as lower case. + // Direction for rank nodes. Can be TB, BT, LR, or RL + if (opt.rankDir) glLabel.rankdir = opt.rankDir; + // Alignment for rank nodes. Can be UL, UR, DL, or DR + if (opt.align) glLabel.align = opt.align; + // Number of pixels that separate nodes horizontally in the layout. + if (opt.nodeSep) glLabel.nodesep = opt.nodeSep; + // Number of pixels that separate edges horizontally in the layout. + if (opt.edgeSep) glLabel.edgesep = opt.edgeSep; + // Number of pixels between each rank in the layout. + if (opt.rankSep) glLabel.ranksep = opt.rankSep; + // Type of algorithm to assign a rank to each node in the input graph. + // Possible values: network-simplex, tight-tree or longest-path + if (opt.ranker) glLabel.ranker = opt.ranker; + // Number of pixels to use as a margin around the left and right of the graph. + if (marginX) glLabel.marginx = marginX; + // Number of pixels to use as a margin around the top and bottom of the graph. + if (marginY) glLabel.marginy = marginY; + + // Set the option object for the graph label. + glGraph.setGraph(glLabel); + + // Executes the layout. + dagreUtil.layout(glGraph, { debugTiming: !!opt.debugTiming }); + + // Wrap all graph changes into a batch. + graph.startBatch('layout'); + + DirectedGraph.fromGraphLib(glGraph, { + importNode: this.importElement.bind(graph, opt), + importEdge: this.importLink.bind(graph, opt) + }); + + // // Update the graph. + // graph.fromGraphLib(glGraph, { + // importNode: this.importElement.bind(graph, opt), + // importEdge: this.importLink.bind(graph, opt) + // }); + + if (opt.resizeClusters) { + // Resize and reposition cluster elements (parents of other elements) + // to fit their children. + // 1. filter clusters only + // 2. map id on cells + // 3. sort cells by their depth (the deepest first) + // 4. resize cell to fit their direct children only. + var clusters = glGraph.nodes() + .filter(function(v) { return glGraph.children(v).length > 0; }) + .map(graph.getCell.bind(graph)) + .sort(function(aCluster, bCluster) { + return bCluster.getAncestors().length - aCluster.getAncestors().length; + }); + + invoke(clusters, 'fitToChildren', { padding: opt.clusterPadding }); + } + + graph.stopBatch('layout'); + + // Width and height of the graph extended by margins. + var glSize = glGraph.graph(); + // Return the bounding box of the graph after the layout. + return new Rect( + marginX, + marginY, + Math.abs(glSize.width - 2 * marginX), + Math.abs(glSize.height - 2 * marginY) + ); + }, + + fromGraphLib: function(glGraph, opt) { + + opt = opt || {}; + + var importNode = opt.importNode || noop; + var importEdge = opt.importEdge || noop; + var graph = (this instanceof Graph) ? this : new Graph; + + // Import all nodes. + glGraph.nodes().forEach(function(node) { + importNode.call(graph, node, glGraph, graph, opt); + }); + + // Import all edges. + glGraph.edges().forEach(function(edge) { + importEdge.call(graph, edge, glGraph, graph, opt); + }); + + return graph; + }, + + // Create new graphlib graph from existing JointJS graph. + toGraphLib: function(graph, opt) { + + opt = opt || {}; + + /* eslint-disable no-undef */ + const graphlibUtil = opt.graphlib || (typeof graphlib !== 'undefined' ? graphlib : undefined); + /* eslint-enable no-undef */ + + if (graphlibUtil === undefined) throw new Error('The the "graphlib" utility is a mandatory dependency.'); + + var glGraphType = pick(opt, 'directed', 'compound', 'multigraph'); + var glGraph = new graphlibUtil.Graph(glGraphType); + var setNodeLabel = opt.setNodeLabel || noop; + var setEdgeLabel = opt.setEdgeLabel || noop; + var setEdgeName = opt.setEdgeName || noop; + var collection = graph.get('cells'); + + for (var i = 0, n = collection.length; i < n; i++) { + + var cell = collection.at(i); + if (cell.isLink()) { + + var source = cell.get('source'); + var target = cell.get('target'); + + // Links that end at a point are ignored. + if (!source.id || !target.id) break; + + // Note that if we are creating a multigraph we can name the edges. If + // we try to name edges on a non-multigraph an exception is thrown. + glGraph.setEdge(source.id, target.id, setEdgeLabel(cell), setEdgeName(cell)); + + } else { + + glGraph.setNode(cell.id, setNodeLabel(cell)); + + // For the compound graphs we have to take embeds into account. + if (glGraph.isCompound() && cell.has('parent')) { + var parentId = cell.get('parent'); + if (collection.has(parentId)) { + // Make sure the parent cell is included in the graph (this can + // happen when the layout is run on part of the graph only). + glGraph.setParent(cell.id, parentId); + } + } + } + } + + return glGraph; + } +}; + +Graph.prototype.toGraphLib = function(opt) { + + return DirectedGraph.toGraphLib(this, opt); +}; + +Graph.prototype.fromGraphLib = function(glGraph, opt) { + + return DirectedGraph.fromGraphLib.call(this, glGraph, opt); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/layout/index.mjs + + + + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/mvc/View.mjs + + + + + + +const views = {}; + +const View = backbone.View.extend({ + + options: {}, + theme: null, + themeClassNamePrefix: addClassNamePrefix('theme-'), + requireSetThemeOverride: false, + defaultTheme: config.defaultTheme, + children: null, + childNodes: null, + + DETACHABLE: true, + UPDATE_PRIORITY: 2, + FLAG_INSERT: 1<<30, + FLAG_REMOVE: 1<<29, + FLAG_INIT: 1<<28, + + constructor: function(options) { + + this.requireSetThemeOverride = options && !!options.theme; + this.options = utilHelpers_assign({}, this.options, options); + + backbone.View.call(this, options); + }, + + initialize: function() { + + views[this.cid] = this; + + this.setTheme(this.options.theme || this.defaultTheme); + this.init(); + }, + + unmount: function() { + if (this.svgElement) { + this.vel.remove(); + } else { + this.$el.remove(); + } + }, + + isMounted: function() { + return this.el.parentNode !== null; + }, + + renderChildren: function(children) { + children || (children = result(this, 'children')); + if (children) { + var isSVG = this.svgElement; + var namespace = src_V.namespace[isSVG ? 'svg' : 'xhtml']; + var doc = parseDOMJSON(children, namespace); + (isSVG ? this.vel : this.$el).empty().append(doc.fragment); + this.childNodes = doc.selectors; + } + return this; + }, + + findAttribute: function(attributeName, node) { + + var currentNode = node; + + while (currentNode && currentNode.nodeType === 1) { + var attributeValue = currentNode.getAttribute(attributeName); + // attribute found + if (attributeValue) return attributeValue; + // do not climb up the DOM + if (currentNode === this.el) return null; + // try parent node + currentNode = currentNode.parentNode; + } + + return null; + }, + + // Override the Backbone `_ensureElement()` method in order to create an + // svg element (e.g., ``) node that wraps all the nodes of the Cell view. + // Expose class name setter as a separate method. + _ensureElement: function() { + if (!this.el) { + var tagName = result(this, 'tagName'); + var attrs = utilHelpers_assign({}, result(this, 'attributes')); + var style = utilHelpers_assign({}, result(this, 'style')); + if (this.id) attrs.id = result(this, 'id'); + this.setElement(this._createElement(tagName)); + this._setAttributes(attrs); + this._setStyle(style); + } else { + this.setElement(result(this, 'el')); + } + this._ensureElClassName(); + }, + + _setAttributes: function(attrs) { + if (this.svgElement) { + this.vel.attr(attrs); + } else { + this.$el.attr(attrs); + } + }, + + _setStyle: function(style) { + this.$el.css(style); + }, + + _createElement: function(tagName) { + if (this.svgElement) { + return document.createElementNS(src_V.namespace.svg, tagName); + } else { + return document.createElement(tagName); + } + }, + + // Utilize an alternative DOM manipulation API by + // adding an element reference wrapped in Vectorizer. + _setElement: function(el) { + this.$el = el instanceof backbone.$ ? el : backbone.$(el); + this.el = this.$el[0]; + if (this.svgElement) this.vel = src_V(this.el); + }, + + _ensureElClassName: function() { + var className = result(this, 'className'); + if (!className) return; + var prefixedClassName = addClassNamePrefix(className); + // Note: className removal here kept for backwards compatibility only + if (this.svgElement) { + this.vel.removeClass(className).addClass(prefixedClassName); + } else { + this.$el.removeClass(className).addClass(prefixedClassName); + } + }, + + init: function() { + // Intentionally empty. + // This method is meant to be overridden. + }, + + onRender: function() { + // Intentionally empty. + // This method is meant to be overridden. + }, + + confirmUpdate: function() { + // Intentionally empty. + // This method is meant to be overridden. + return 0; + }, + + setTheme: function(theme, opt) { + + opt = opt || {}; + + // Theme is already set, override is required, and override has not been set. + // Don't set the theme. + if (this.theme && this.requireSetThemeOverride && !opt.override) { + return this; + } + + this.removeThemeClassName(); + this.addThemeClassName(theme); + this.onSetTheme(this.theme/* oldTheme */, theme/* newTheme */); + this.theme = theme; + + return this; + }, + + addThemeClassName: function(theme) { + + theme = theme || this.theme; + if (!theme) return this; + + var className = this.themeClassNamePrefix + theme; + + if (this.svgElement) { + this.vel.addClass(className); + } else { + this.$el.addClass(className); + } + + return this; + }, + + removeThemeClassName: function(theme) { + + theme = theme || this.theme; + + var className = this.themeClassNamePrefix + theme; + + if (this.svgElement) { + this.vel.removeClass(className); + } else { + this.$el.removeClass(className); + } + + return this; + }, + + onSetTheme: function(oldTheme, newTheme) { + // Intentionally empty. + // This method is meant to be overridden. + }, + + remove: function() { + + this.onRemove(); + this.undelegateDocumentEvents(); + + views[this.cid] = null; + + backbone.View.prototype.remove.apply(this, arguments); + + return this; + }, + + onRemove: function() { + // Intentionally empty. + // This method is meant to be overridden. + }, + + getEventNamespace: function() { + // Returns a per-session unique namespace + return '.joint-event-ns-' + this.cid; + }, + + delegateElementEvents: function(element, events, data) { + if (!events) return this; + data || (data = {}); + var eventNS = this.getEventNamespace(); + for (var eventName in events) { + var method = events[eventName]; + if (typeof method !== 'function') method = this[method]; + if (!method) continue; + jquery(element).on(eventName + eventNS, data, method.bind(this)); + } + return this; + }, + + undelegateElementEvents: function(element) { + jquery(element).off(this.getEventNamespace()); + return this; + }, + + delegateDocumentEvents: function(events, data) { + events || (events = result(this, 'documentEvents')); + return this.delegateElementEvents(document, events, data); + }, + + undelegateDocumentEvents: function() { + return this.undelegateElementEvents(document); + }, + + eventData: function(evt, data) { + if (!evt) throw new Error('eventData(): event object required.'); + var currentData = evt.data; + var key = '__' + this.cid + '__'; + if (data === undefined) { + if (!currentData) return {}; + return currentData[key] || {}; + } + currentData || (currentData = evt.data = {}); + currentData[key] || (currentData[key] = {}); + utilHelpers_assign(currentData[key], data); + return this; + }, + + stopPropagation: function(evt) { + this.eventData(evt, { propagationStopped: true }); + return this; + }, + + isPropagationStopped: function(evt) { + return !!this.eventData(evt).propagationStopped; + } + +}, { + + extend: function() { + + var args = Array.from(arguments); + + // Deep clone the prototype and static properties objects. + // This prevents unexpected behavior where some properties are overwritten outside of this function. + var protoProps = args[0] && utilHelpers_assign({}, args[0]) || {}; + var staticProps = args[1] && utilHelpers_assign({}, args[1]) || {}; + + // Need the real render method so that we can wrap it and call it later. + var renderFn = protoProps.render || (this.prototype && this.prototype.render) || null; + + /* + Wrap the real render method so that: + .. `onRender` is always called. + .. `this` is always returned. + */ + protoProps.render = function() { + + if (typeof renderFn === 'function') { + // Call the original render method. + renderFn.apply(this, arguments); + } + + if (this.render.__render__ === renderFn) { + // Should always call onRender() method. + // Should call it only once when renderFn is actual prototype method i.e. not the wrapper + this.onRender(); + } + + // Should always return itself. + return this; + }; + + protoProps.render.__render__ = renderFn; + + return backbone.View.extend.call(this, protoProps, staticProps); + } +}); + +const DoubleTapEventName = 'dbltap'; +if (jquery.event && !(DoubleTapEventName in jquery.event.special)) { + const maxDelay = config.doubleTapInterval; + const minDelay = 30; + jquery.event.special[DoubleTapEventName] = { + bindType: 'touchend', + delegateType: 'touchend', + handle: function(event, ...args) { + const { handleObj, target } = event; + const targetData = jquery.data(target); + const now = new Date().getTime(); + const delta = 'lastTouch' in targetData ? now - targetData.lastTouch : 0; + if (delta < maxDelay && delta > minDelay) { + targetData.lastTouch = null; + event.type = handleObj.origType; + // let jQuery handle the triggering of "dbltap" event handlers + handleObj.handler.call(this, event, ...args); + } else { + targetData.lastTouch = now; + } + } + }; +} + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/HighlighterView.mjs + + + + +function HighlighterView_toArray(obj) { + if (!obj) return []; + if (Array.isArray(obj)) return obj; + return [obj]; +} + +const HighlighterView = View.extend({ + + tagName: 'g', + svgElement: true, + className: 'highlight', + + HIGHLIGHT_FLAG: 1, + UPDATE_PRIORITY: 3, + DETACHABLE: false, + UPDATABLE: true, + MOUNTABLE: true, + + cellView: null, + nodeSelector: null, + node: null, + updateRequested: false, + postponedUpdate: false, + transformGroup: null, + detachedTransformGroup: null, + + requestUpdate(cellView, nodeSelector) { + const { paper } = cellView; + this.cellView = cellView; + this.nodeSelector = nodeSelector; + if (paper) { + this.updateRequested = true; + paper.requestViewUpdate(this, this.HIGHLIGHT_FLAG, this.UPDATE_PRIORITY); + } + }, + + confirmUpdate() { + // The cellView is now rendered/updated since it has a higher update priority. + this.updateRequested = false; + const { cellView, nodeSelector } = this; + if (!cellView.isMounted()) { + this.postponedUpdate = true; + return 0; + } + this.update(cellView, nodeSelector); + this.mount(); + this.transform(); + return 0; + }, + + findNode(cellView, nodeSelector = null) { + let el; + if (typeof nodeSelector === 'string') { + [el] = cellView.findBySelector(nodeSelector); + } else if (isPlainObject(nodeSelector)) { + const isLink = cellView.model.isLink(); + const { label = null, port, selector } = nodeSelector; + if (isLink && label !== null) { + // Link Label Selector + el = cellView.findLabelNode(label, selector); + } else if (!isLink && port) { + // Element Port Selector + el = cellView.findPortNode(port, selector); + } else { + // Cell Selector + [el] = cellView.findBySelector(selector); + } + } else if (nodeSelector) { + el = src_V.toNode(nodeSelector); + if (!(el instanceof SVGElement)) el = null; + } + return el ? el : null; + }, + + getNodeMatrix(cellView, node) { + const { options } = this; + const { layer } = options; + const { rotatableNode } = cellView; + const nodeMatrix = cellView.getNodeMatrix(node); + if (rotatableNode) { + if (layer) { + if (rotatableNode.contains(node)) { + return nodeMatrix; + } + // The node is outside of the rotatable group. + // Compensate the rotation set by transformGroup. + return cellView.getRootRotateMatrix().inverse().multiply(nodeMatrix); + } else { + return cellView.getNodeRotateMatrix(node).multiply(nodeMatrix); + } + } + return nodeMatrix; + }, + + mount() { + const { MOUNTABLE, cellView, el, options, transformGroup, detachedTransformGroup, postponedUpdate, nodeSelector } = this; + if (!MOUNTABLE || transformGroup) return; + if (postponedUpdate) { + // The cellView was not mounted when the update was requested. + // The update was postponed until the cellView is mounted. + this.update(cellView, nodeSelector); + this.transform(); + return; + } + const { vel: cellViewRoot, paper } = cellView; + const { layer: layerName } = options; + if (layerName) { + let vGroup; + if (detachedTransformGroup) { + vGroup = detachedTransformGroup; + this.detachedTransformGroup = null; + } else { + vGroup = src_V('g').addClass('highlight-transform').append(el); + } + this.transformGroup = vGroup; + paper.getLayerView(layerName).insertSortedNode(vGroup.node, options.z); + } else { + // TODO: prepend vs append + if (!el.parentNode || el.nextSibling) { + // Not appended yet or not the last child + cellViewRoot.append(el); + } + } + }, + + unmount() { + const { MOUNTABLE, transformGroup, vel } = this; + if (!MOUNTABLE) return; + if (transformGroup) { + this.transformGroup = null; + this.detachedTransformGroup = transformGroup; + transformGroup.remove(); + } else { + vel.remove(); + } + }, + + transform() { + const { transformGroup, cellView, updateRequested } = this; + if (!transformGroup || cellView.model.isLink() || updateRequested) return; + const translateMatrix = cellView.getRootTranslateMatrix(); + const rotateMatrix = cellView.getRootRotateMatrix(); + const transformMatrix = translateMatrix.multiply(rotateMatrix); + transformGroup.attr('transform', src_V.matrixToTransformString(transformMatrix)); + }, + + update() { + const { node: prevNode, cellView, nodeSelector, updateRequested, id } = this; + if (updateRequested) return; + this.postponedUpdate = false; + const node = this.node = this.findNode(cellView, nodeSelector); + if (prevNode) { + this.unhighlight(cellView, prevNode); + } + if (node) { + this.highlight(cellView, node); + this.mount(); + } else { + this.unmount(); + cellView.notify('cell:highlight:invalid', id, this); + } + }, + + onRemove() { + const { node, cellView, id, constructor } = this; + if (node) { + this.unhighlight(cellView, node); + } + this.unmount(); + constructor._removeRef(cellView, id); + }, + + highlight(_cellView, _node) { + // to be overridden + }, + + unhighlight(_cellView, _node) { + // to be overridden + }, + + // Update Attributes + + listenToUpdateAttributes(cellView) { + const attributes = result(this, 'UPDATE_ATTRIBUTES'); + if (!Array.isArray(attributes) || attributes.length === 0) return; + this.listenTo(cellView.model, 'change', this.onCellAttributeChange); + }, + + onCellAttributeChange() { + const { cellView } = this; + if (!cellView) return; + const { model, paper } = cellView; + const attributes = result(this, 'UPDATE_ATTRIBUTES'); + if (!attributes.some(attribute => model.hasChanged(attribute))) return; + paper.requestViewUpdate(this, this.HIGHLIGHT_FLAG, this.UPDATE_PRIORITY); + } + +}, { + + _views: {}, + + // Used internally by CellView highlight() + highlight: function(cellView, node, opt) { + const id = this.uniqueId(node, opt); + this.add(cellView, node, id, opt); + }, + + // Used internally by CellView unhighlight() + unhighlight: function(cellView, node, opt) { + const id = this.uniqueId(node, opt); + this.remove(cellView, id); + }, + + get(cellView, id = null) { + const { cid } = cellView; + const { _views } = this; + const refs = _views[cid]; + if (id === null) { + // all highlighters + const views = []; + if (!refs) return views; + for (let hid in refs) { + const ref = refs[hid]; + if (ref instanceof this) { + views.push(ref); + } + } + return views; + } else { + // single highlighter + if (!refs) return null; + if (id in refs) { + const ref = refs[id]; + if (ref instanceof this) return ref; + } + return null; + } + }, + + add(cellView, nodeSelector, id, opt = {}) { + if (!id) throw new Error('dia.HighlighterView: An ID required.'); + // Search the existing view amongst all the highlighters + const previousView = HighlighterView.get(cellView, id); + if (previousView) previousView.remove(); + const view = new this(opt); + view.id = id; + this._addRef(cellView, id, view); + view.requestUpdate(cellView, nodeSelector); + view.listenToUpdateAttributes(cellView); + return view; + }, + + _addRef(cellView, id, view) { + const { cid } = cellView; + const { _views } = this; + let refs = _views[cid]; + if (!refs) refs = _views[cid] = {}; + refs[id] = view; + }, + + _removeRef(cellView, id) { + const { cid } = cellView; + const { _views } = this; + const refs = _views[cid]; + if (!refs) return; + if (id) delete refs[id]; + for (let _ in refs) return; + delete _views[cid]; + }, + + remove(cellView, id = null) { + HighlighterView_toArray(this.get(cellView, id)).forEach(view => { + view.remove(); + }); + }, + + removeAll(paper, id = null) { + const { _views } = this; + + for (let cid in _views) { + for (let hid in _views[cid]) { + const view = _views[cid][hid]; + + if (view.cellView.paper === paper && view instanceof this && (id === null || hid === id)) { + view.remove(); + } + } + } + }, + + update(cellView, id = null, dirty = false) { + HighlighterView_toArray(this.get(cellView, id)).forEach(view => { + if (dirty || view.UPDATABLE) view.update(); + }); + }, + + transform(cellView, id = null) { + HighlighterView_toArray(this.get(cellView, id)).forEach(view => { + if (view.UPDATABLE) view.transform(); + }); + }, + + unmount(cellView, id = null) { + HighlighterView_toArray(this.get(cellView, id)).forEach(view => view.unmount()); + }, + + mount(cellView, id = null) { + HighlighterView_toArray(this.get(cellView, id)).forEach(view => view.mount()); + }, + + uniqueId(node, opt = '') { + return src_V.ensureId(node) + JSON.stringify(opt); + } + +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/CellView.mjs + + + + + + + + +const HighlightingTypes = { + DEFAULT: 'default', + EMBEDDING: 'embedding', + CONNECTING: 'connecting', + MAGNET_AVAILABILITY: 'magnetAvailability', + ELEMENT_AVAILABILITY: 'elementAvailability' +}; + +const Flags = { + TOOLS: 'TOOLS', +}; + +// CellView base view and controller. +// -------------------------------------------- + +// This is the base view and controller for `ElementView` and `LinkView`. +const CellView = View.extend({ + + tagName: 'g', + + svgElement: true, + + selector: 'root', + + metrics: null, + + className: function() { + + var classNames = ['cell']; + var type = this.model.get('type'); + + if (type) { + + type.toLowerCase().split('.').forEach(function(value, index, list) { + classNames.push('type-' + list.slice(0, index + 1).join('-')); + }); + } + + return classNames.join(' '); + }, + + _presentationAttributes: null, + _flags: null, + + setFlags: function() { + var flags = {}; + var attributes = {}; + var shift = 0; + var i, n, label; + var presentationAttributes = result(this, 'presentationAttributes'); + for (var attribute in presentationAttributes) { + if (!presentationAttributes.hasOwnProperty(attribute)) continue; + var labels = presentationAttributes[attribute]; + if (!Array.isArray(labels)) labels = [labels]; + for (i = 0, n = labels.length; i < n; i++) { + label = labels[i]; + var flag = flags[label]; + if (!flag) { + flag = flags[label] = 1<<(shift++); + } + attributes[attribute] |= flag; + } + } + var initFlag = result(this, 'initFlag'); + if (!Array.isArray(initFlag)) initFlag = [initFlag]; + for (i = 0, n = initFlag.length; i < n; i++) { + label = initFlag[i]; + if (!flags[label]) flags[label] = 1<<(shift++); + } + + // 26 - 30 are reserved for paper flags + // 31+ overflows maximal number + if (shift > 25) throw new Error('dia.CellView: Maximum number of flags exceeded.'); + + this._flags = flags; + this._presentationAttributes = attributes; + }, + + hasFlag: function(flag, label) { + return flag & this.getFlag(label); + }, + + removeFlag: function(flag, label) { + return flag ^ (flag & this.getFlag(label)); + }, + + getFlag: function(label) { + var flags = this._flags; + if (!flags) return 0; + var flag = 0; + if (Array.isArray(label)) { + for (var i = 0, n = label.length; i < n; i++) flag |= flags[label[i]]; + } else { + flag |= flags[label]; + } + return flag; + }, + + attributes: function() { + var cell = this.model; + return { + 'model-id': cell.id, + 'data-type': cell.attributes.type + }; + }, + + constructor: function(options) { + + // Make sure a global unique id is assigned to this view. Store this id also to the properties object. + // The global unique id makes sure that the same view can be rendered on e.g. different machines and + // still be associated to the same object among all those clients. This is necessary for real-time + // collaboration mechanism. + options.id = options.id || guid(this); + + View.call(this, options); + }, + + initialize: function() { + + this.setFlags(); + + View.prototype.initialize.apply(this, arguments); + + this.cleanNodesCache(); + + // Store reference to this to the DOM element so that the view is accessible through the DOM tree. + this.$el.data('view', this); + + this.startListening(); + }, + + startListening: function() { + this.listenTo(this.model, 'change', this.onAttributesChange); + }, + + onAttributesChange: function(model, opt) { + var flag = model.getChangeFlag(this._presentationAttributes); + if (opt.updateHandled || !flag) return; + if (opt.dirty && this.hasFlag(flag, 'UPDATE')) flag |= this.getFlag('RENDER'); + // TODO: tool changes does not need to be sync + // Fix Segments tools + if (opt.tool) opt.async = false; + this.requestUpdate(flag, opt); + }, + + requestUpdate: function(flags, opt) { + const { paper } = this; + if (paper && flags > 0) { + paper.requestViewUpdate(this, flags, this.UPDATE_PRIORITY, opt); + } + }, + + parseDOMJSON: function(markup, root) { + + var doc = parseDOMJSON(markup); + var selectors = doc.selectors; + var groups = doc.groupSelectors; + for (var group in groups) { + if (selectors[group]) throw new Error('dia.CellView: ambiguous group selector'); + selectors[group] = groups[group]; + } + if (root) { + var rootSelector = this.selector; + if (selectors[rootSelector]) throw new Error('dia.CellView: ambiguous root selector.'); + selectors[rootSelector] = root; + } + return { fragment: doc.fragment, selectors: selectors }; + }, + + // Return `true` if cell link is allowed to perform a certain UI `feature`. + // Example: `can('vertexMove')`, `can('labelMove')`. + can: function(feature) { + + var interactive = isFunction(this.options.interactive) + ? this.options.interactive(this) + : this.options.interactive; + + return (isObject(interactive) && interactive[feature] !== false) || + (isBoolean(interactive) && interactive !== false); + }, + + findBySelector: function(selector, root, selectors) { + + root || (root = this.el); + selectors || (selectors = this.selectors); + + // These are either descendants of `this.$el` of `this.$el` itself. + // `.` is a special selector used to select the wrapping `` element. + if (!selector || selector === '.') return [root]; + if (selectors) { + var nodes = selectors[selector]; + if (nodes) { + if (Array.isArray(nodes)) return nodes; + return [nodes]; + } + } + + // Maintaining backwards compatibility + // e.g. `circle:first` would fail with querySelector() call + if (config.useCSSSelectors) return jquery(root).find(selector).toArray(); + + return []; + }, + + notify: function(eventName) { + + if (this.paper) { + + var args = Array.prototype.slice.call(arguments, 1); + + // Trigger the event on both the element itself and also on the paper. + this.trigger.apply(this, [eventName].concat(args)); + + // Paper event handlers receive the view object as the first argument. + this.paper.trigger.apply(this.paper, [eventName, this].concat(args)); + } + }, + + getBBox: function(opt) { + + var bbox; + if (opt && opt.useModelGeometry) { + var model = this.model; + bbox = model.getBBox().bbox(model.angle()); + } else { + bbox = this.getNodeBBox(this.el); + } + + return this.paper.localToPaperRect(bbox); + }, + + getNodeBBox: function(magnet) { + + const rect = this.getNodeBoundingRect(magnet); + const transformMatrix = this.getRootTranslateMatrix().multiply(this.getNodeRotateMatrix(magnet)); + const magnetMatrix = this.getNodeMatrix(magnet); + return src_V.transformRect(rect, transformMatrix.multiply(magnetMatrix)); + }, + + getNodeRotateMatrix(node) { + if (!this.rotatableNode || this.rotatableNode.contains(node)) { + // Rotate transformation is applied to all nodes when no rotatableGroup + // is present or to nodes inside the rotatableGroup only. + return this.getRootRotateMatrix(); + } + // Nodes outside the rotatable group + return src_V.createSVGMatrix(); + }, + + getNodeUnrotatedBBox: function(magnet) { + + var rect = this.getNodeBoundingRect(magnet); + var magnetMatrix = this.getNodeMatrix(magnet); + var translateMatrix = this.getRootTranslateMatrix(); + return src_V.transformRect(rect, translateMatrix.multiply(magnetMatrix)); + }, + + getRootTranslateMatrix: function() { + + var model = this.model; + var position = model.position(); + var mt = src_V.createSVGMatrix().translate(position.x, position.y); + return mt; + }, + + getRootRotateMatrix: function() { + + var mr = src_V.createSVGMatrix(); + var model = this.model; + var angle = model.angle(); + if (angle) { + var bbox = model.getBBox(); + var cx = bbox.width / 2; + var cy = bbox.height / 2; + mr = mr.translate(cx, cy).rotate(angle).translate(-cx, -cy); + } + return mr; + }, + + _notifyHighlight: function(eventName, el, opt = {}) { + const { el: rootNode } = this; + let node; + if (typeof el === 'string') { + [node = rootNode] = this.findBySelector(el); + } else { + [node = rootNode] = this.$(el); + } + // set partial flag if the highlighted element is not the entire view. + opt.partial = (node !== rootNode); + // translate type flag into a type string + if (opt.type === undefined) { + let type; + switch (true) { + case opt.embedding: + type = HighlightingTypes.EMBEDDING; + break; + case opt.connecting: + type = HighlightingTypes.CONNECTING; + break; + case opt.magnetAvailability: + type = HighlightingTypes.MAGNET_AVAILABILITY; + break; + case opt.elementAvailability: + type = HighlightingTypes.ELEMENT_AVAILABILITY; + break; + default: + type = HighlightingTypes.DEFAULT; + break; + } + opt.type = type; + } + this.notify(eventName, node, opt); + return this; + }, + + highlight: function(el, opt) { + return this._notifyHighlight('cell:highlight', el, opt); + }, + + unhighlight: function(el, opt = {}) { + return this._notifyHighlight('cell:unhighlight', el, opt); + }, + + // Find the closest element that has the `magnet` attribute set to `true`. If there was not such + // an element found, return the root element of the cell view. + findMagnet: function(el) { + + const root = this.el; + let magnet = this.$(el)[0]; + if (!magnet) { + magnet = root; + } + + do { + const magnetAttribute = magnet.getAttribute('magnet'); + const isMagnetRoot = (magnet === root); + if ((magnetAttribute || isMagnetRoot) && magnetAttribute !== 'false') { + return magnet; + } + if (isMagnetRoot) { + // If the overall cell has set `magnet === false`, then return `undefined` to + // announce there is no magnet found for this cell. + // This is especially useful to set on cells that have 'ports'. In this case, + // only the ports have set `magnet === true` and the overall element has `magnet === false`. + return undefined; + } + magnet = magnet.parentNode; + } while (magnet); + + return undefined; + }, + + findProxyNode: function(el, type) { + el || (el = this.el); + const nodeSelector = el.getAttribute(`${type}-selector`); + if (nodeSelector) { + const [proxyNode] = this.findBySelector(nodeSelector); + if (proxyNode) return proxyNode; + } + return el; + }, + + // Construct a unique selector for the `el` element within this view. + // `prevSelector` is being collected through the recursive call. + // No value for `prevSelector` is expected when using this method. + getSelector: function(el, prevSelector) { + + var selector; + + if (el === this.el) { + if (typeof prevSelector === 'string') selector = '> ' + prevSelector; + return selector; + } + + if (el) { + + var nthChild = src_V(el).index() + 1; + selector = el.tagName + ':nth-child(' + nthChild + ')'; + + if (prevSelector) { + selector += ' > ' + prevSelector; + } + + selector = this.getSelector(el.parentNode, selector); + } + + return selector; + }, + + addLinkFromMagnet: function(magnet, x, y) { + + var paper = this.paper; + var graph = paper.model; + + var link = paper.getDefaultLink(this, magnet); + link.set({ + source: this.getLinkEnd(magnet, x, y, link, 'source'), + target: { x: x, y: y } + }).addTo(graph, { + async: false, + ui: true + }); + + return link.findView(paper); + }, + + getLinkEnd: function(magnet, ...args) { + + var model = this.model; + var id = model.id; + var port = this.findAttribute('port', magnet); + // Find a unique `selector` of the element under pointer that is a magnet. + var selector = magnet.getAttribute('joint-selector'); + + var end = { id: id }; + if (selector != null) end.magnet = selector; + if (port != null) { + end.port = port; + if (!model.hasPort(port) && !selector) { + // port created via the `port` attribute (not API) + end.selector = this.getSelector(magnet); + } + } else if (selector == null && this.el !== magnet) { + end.selector = this.getSelector(magnet); + } + + return this.customizeLinkEnd(end, magnet, ...args); + }, + + customizeLinkEnd: function(end, magnet, x, y, link, endType) { + const { paper } = this; + const { connectionStrategy } = paper.options; + if (typeof connectionStrategy === 'function') { + var strategy = connectionStrategy.call(paper, end, this, magnet, new Point(x, y), link, endType, paper); + if (strategy) return strategy; + } + return end; + }, + + getMagnetFromLinkEnd: function(end) { + + var root = this.el; + var port = end.port; + var selector = end.magnet; + var model = this.model; + var magnet; + if (port != null && model.isElement() && model.hasPort(port)) { + magnet = this.findPortNode(port, selector) || root; + } else { + if (!selector) selector = end.selector; + if (!selector && port != null) { + // link end has only `id` and `port` property referencing + // a port created via the `port` attribute (not API). + selector = '[port="' + port + '"]'; + } + magnet = this.findBySelector(selector, root, this.selectors)[0]; + } + + return this.findProxyNode(magnet, 'magnet'); + }, + + dragLinkStart: function(evt, magnet, x, y) { + this.model.startBatch('add-link'); + const linkView = this.addLinkFromMagnet(magnet, x, y); + // backwards compatibility events + linkView.notifyPointerdown(evt, x, y); + linkView.eventData(evt, linkView.startArrowheadMove('target', { whenNotAllowed: 'remove' })); + this.eventData(evt, { linkView }); + }, + + dragLink: function(evt, x, y) { + var data = this.eventData(evt); + var linkView = data.linkView; + if (linkView) { + linkView.pointermove(evt, x, y); + } else { + var paper = this.paper; + var magnetThreshold = paper.options.magnetThreshold; + var currentTarget = this.getEventTarget(evt); + var targetMagnet = data.targetMagnet; + if (magnetThreshold === 'onleave') { + // magnetThreshold when the pointer leaves the magnet + if (targetMagnet === currentTarget || src_V(targetMagnet).contains(currentTarget)) return; + } else { + // magnetThreshold defined as a number of movements + if (paper.eventData(evt).mousemoved <= magnetThreshold) return; + } + this.dragLinkStart(evt, targetMagnet, x, y); + } + }, + + dragLinkEnd: function(evt, x, y) { + var data = this.eventData(evt); + var linkView = data.linkView; + if (!linkView) return; + linkView.pointerup(evt, x, y); + this.model.stopBatch('add-link'); + }, + + getAttributeDefinition: function(attrName) { + + return this.model.constructor.getAttributeDefinition(attrName); + }, + + setNodeAttributes: function(node, attrs) { + + if (!isEmpty(attrs)) { + if (node instanceof SVGElement) { + src_V(node).attr(attrs); + } else { + jquery(node).attr(attrs); + } + } + }, + + processNodeAttributes: function(node, attrs) { + + var attrName, attrVal, def, i, n; + var normalAttrs, setAttrs, positionAttrs, offsetAttrs; + var relatives = []; + // divide the attributes between normal and special + for (attrName in attrs) { + if (!attrs.hasOwnProperty(attrName)) continue; + attrVal = attrs[attrName]; + def = this.getAttributeDefinition(attrName); + if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, attrs, this))) { + if (isString(def.set)) { + normalAttrs || (normalAttrs = {}); + normalAttrs[def.set] = attrVal; + } + if (attrVal !== null) { + relatives.push(attrName, def); + } + } else { + normalAttrs || (normalAttrs = {}); + normalAttrs[toKebabCase(attrName)] = attrVal; + } + } + + // handle the rest of attributes via related method + // from the special attributes namespace. + for (i = 0, n = relatives.length; i < n; i+=2) { + attrName = relatives[i]; + def = relatives[i+1]; + attrVal = attrs[attrName]; + if (isFunction(def.set)) { + setAttrs || (setAttrs = {}); + setAttrs[attrName] = attrVal; + } + if (isFunction(def.position)) { + positionAttrs || (positionAttrs = {}); + positionAttrs[attrName] = attrVal; + } + if (isFunction(def.offset)) { + offsetAttrs || (offsetAttrs = {}); + offsetAttrs[attrName] = attrVal; + } + } + + return { + raw: attrs, + normal: normalAttrs, + set: setAttrs, + position: positionAttrs, + offset: offsetAttrs + }; + }, + + updateRelativeAttributes: function(node, attrs, refBBox, opt) { + + opt || (opt = {}); + + var attrName, attrVal, def; + var rawAttrs = attrs.raw || {}; + var nodeAttrs = attrs.normal || {}; + var setAttrs = attrs.set; + var positionAttrs = attrs.position; + var offsetAttrs = attrs.offset; + + for (attrName in setAttrs) { + attrVal = setAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // SET - set function should return attributes to be set on the node, + // which will affect the node dimensions based on the reference bounding + // box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points + var setResult = def.set.call(this, attrVal, refBBox.clone(), node, rawAttrs, this); + if (isObject(setResult)) { + utilHelpers_assign(nodeAttrs, setResult); + } else if (setResult !== undefined) { + nodeAttrs[attrName] = setResult; + } + } + + if (node instanceof HTMLElement) { + // TODO: setting the `transform` attribute on HTMLElements + // via `node.style.transform = 'matrix(...)';` would introduce + // a breaking change (e.g. basic.TextBlock). + this.setNodeAttributes(node, nodeAttrs); + return; + } + + // The final translation of the subelement. + var nodeTransform = nodeAttrs.transform; + var nodeMatrix = src_V.transformStringToMatrix(nodeTransform); + var nodePosition = Point(nodeMatrix.e, nodeMatrix.f); + if (nodeTransform) { + nodeAttrs = omit(nodeAttrs, 'transform'); + nodeMatrix.e = nodeMatrix.f = 0; + } + + // Calculate node scale determined by the scalable group + // only if later needed. + var sx, sy, translation; + if (positionAttrs || offsetAttrs) { + var nodeScale = this.getNodeScale(node, opt.scalableNode); + sx = nodeScale.sx; + sy = nodeScale.sy; + } + + var positioned = false; + for (attrName in positionAttrs) { + attrVal = positionAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // POSITION - position function should return a point from the + // reference bounding box. The default position of the node is x:0, y:0 of + // the reference bounding box or could be further specify by some + // SVG attributes e.g. `x`, `y` + translation = def.position.call(this, attrVal, refBBox.clone(), node, rawAttrs, this); + if (translation) { + nodePosition.offset(Point(translation).scale(sx, sy)); + positioned || (positioned = true); + } + } + + // The node bounding box could depend on the `size` set from the previous loop. + // Here we know, that all the size attributes have been already set. + this.setNodeAttributes(node, nodeAttrs); + + var offseted = false; + if (offsetAttrs) { + // Check if the node is visible + var nodeBoundingRect = this.getNodeBoundingRect(node); + if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) { + var nodeBBox = src_V.transformRect(nodeBoundingRect, nodeMatrix).scale(1 / sx, 1 / sy); + for (attrName in offsetAttrs) { + attrVal = offsetAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // OFFSET - offset function should return a point from the element + // bounding box. The default offset point is x:0, y:0 (origin) or could be further + // specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy` + translation = def.offset.call(this, attrVal, nodeBBox, node, rawAttrs, this); + if (translation) { + nodePosition.offset(Point(translation).scale(sx, sy)); + offseted || (offseted = true); + } + } + } + } + + // Do not touch node's transform attribute if there is no transformation applied. + if (nodeTransform !== undefined || positioned || offseted) { + // Round the coordinates to 1 decimal point. + nodePosition.round(1); + nodeMatrix.e = nodePosition.x; + nodeMatrix.f = nodePosition.y; + node.setAttribute('transform', src_V.matrixToTransformString(nodeMatrix)); + // TODO: store nodeMatrix metrics? + } + }, + + getNodeScale: function(node, scalableNode) { + + // Check if the node is a descendant of the scalable group. + var sx, sy; + if (scalableNode && scalableNode.contains(node)) { + var scale = scalableNode.scale(); + sx = 1 / scale.sx; + sy = 1 / scale.sy; + } else { + sx = 1; + sy = 1; + } + + return { sx: sx, sy: sy }; + }, + + cleanNodesCache: function() { + this.metrics = {}; + }, + + nodeCache: function(magnet) { + + var metrics = this.metrics; + // Don't use cache? It most likely a custom view with overridden update. + if (!metrics) return {}; + var id = src_V.ensureId(magnet); + var value = metrics[id]; + if (!value) value = metrics[id] = {}; + return value; + }, + + getNodeData: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (!metrics.data) metrics.data = {}; + return metrics.data; + }, + + getNodeBoundingRect: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (metrics.boundingRect === undefined) metrics.boundingRect = src_V(magnet).getBBox(); + return new Rect(metrics.boundingRect); + }, + + getNodeMatrix: function(magnet) { + + const metrics = this.nodeCache(magnet); + if (metrics.magnetMatrix === undefined) { + const { rotatableNode, el } = this; + let target; + if (rotatableNode && rotatableNode.contains(magnet)) { + target = rotatableNode; + } else { + target = el; + } + metrics.magnetMatrix = src_V(magnet).getTransformToElement(target); + } + return src_V.createSVGMatrix(metrics.magnetMatrix); + }, + + getNodeShape: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (metrics.geometryShape === undefined) metrics.geometryShape = src_V(magnet).toGeometryShape(); + return metrics.geometryShape.clone(); + }, + + isNodeConnection: function(node) { + return this.model.isLink() && (!node || node === this.el); + }, + + findNodesAttributes: function(attrs, root, selectorCache, selectors) { + + var i, n, nodeAttrs, nodeId; + var nodesAttrs = {}; + var mergeIds = []; + for (var selector in attrs) { + if (!attrs.hasOwnProperty(selector)) continue; + nodeAttrs = attrs[selector]; + if (!isPlainObject(nodeAttrs)) continue; // Not a valid selector-attributes pair + var selected = selectorCache[selector] = this.findBySelector(selector, root, selectors); + for (i = 0, n = selected.length; i < n; i++) { + var node = selected[i]; + nodeId = src_V.ensureId(node); + // "unique" selectors are selectors that referencing a single node (defined by `selector`) + // groupSelector referencing a single node is not "unique" + var unique = (selectors && selectors[selector] === node); + var prevNodeAttrs = nodesAttrs[nodeId]; + if (prevNodeAttrs) { + // Note, that nodes referenced by deprecated `CSS selectors` are not taken into account. + // e.g. css:`.circle` and selector:`circle` can be applied in a random order + if (!prevNodeAttrs.array) { + mergeIds.push(nodeId); + prevNodeAttrs.array = true; + prevNodeAttrs.attributes = [prevNodeAttrs.attributes]; + prevNodeAttrs.selectedLength = [prevNodeAttrs.selectedLength]; + } + var attributes = prevNodeAttrs.attributes; + var selectedLength = prevNodeAttrs.selectedLength; + if (unique) { + // node referenced by `selector` + attributes.unshift(nodeAttrs); + selectedLength.unshift(-1); + } else { + // node referenced by `groupSelector` + var sortIndex = sortedIndex(selectedLength, n); + attributes.splice(sortIndex, 0, nodeAttrs); + selectedLength.splice(sortIndex, 0, n); + } + } else { + nodesAttrs[nodeId] = { + attributes: nodeAttrs, + selectedLength: unique ? -1 : n, + node: node, + array: false + }; + } + } + } + + for (i = 0, n = mergeIds.length; i < n; i++) { + nodeId = mergeIds[i]; + nodeAttrs = nodesAttrs[nodeId]; + nodeAttrs.attributes = merge({}, ...nodeAttrs.attributes.reverse()); + } + + return nodesAttrs; + }, + + getEventTarget: function(evt, opt = {}) { + const { target, type, clientX = 0, clientY = 0 } = evt; + if ( + // Explicitly defined `fromPoint` option + opt.fromPoint || + // Touchmove/Touchend event's target is not reflecting the element under the coordinates as mousemove does. + // It holds the element when a touchstart triggered. + type === 'touchmove' || type === 'touchend' || + // Pointermove/Pointerup event with the pointer captured + ('pointerId' in evt && target.hasPointerCapture(evt.pointerId)) + ) { + return document.elementFromPoint(clientX, clientY); + } + + return target; + }, + + // Default is to process the `model.attributes.attrs` object and set attributes on subelements based on the selectors, + // unless `attrs` parameter was passed. + updateDOMSubtreeAttributes: function(rootNode, attrs, opt) { + + opt || (opt = {}); + opt.rootBBox || (opt.rootBBox = Rect()); + opt.selectors || (opt.selectors = this.selectors); // selector collection to use + + // Cache table for query results and bounding box calculation. + // Note that `selectorCache` needs to be invalidated for all + // `updateAttributes` calls, as the selectors might pointing + // to nodes designated by an attribute or elements dynamically + // created. + var selectorCache = {}; + var bboxCache = {}; + var relativeItems = []; + var relativeRefItems = []; + var item, node, nodeAttrs, nodeData, processedAttrs; + + var roAttrs = opt.roAttributes; + var nodesAttrs = this.findNodesAttributes(roAttrs || attrs, rootNode, selectorCache, opt.selectors); + // `nodesAttrs` are different from all attributes, when + // rendering only attributes sent to this method. + var nodesAllAttrs = (roAttrs) + ? this.findNodesAttributes(attrs, rootNode, selectorCache, opt.selectors) + : nodesAttrs; + + for (var nodeId in nodesAttrs) { + nodeData = nodesAttrs[nodeId]; + nodeAttrs = nodeData.attributes; + node = nodeData.node; + processedAttrs = this.processNodeAttributes(node, nodeAttrs); + + if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset) { + // Set all the normal attributes right on the SVG/HTML element. + this.setNodeAttributes(node, processedAttrs.normal); + + } else { + + var nodeAllAttrs = nodesAllAttrs[nodeId] && nodesAllAttrs[nodeId].attributes; + var refSelector = (nodeAllAttrs && (nodeAttrs.ref === undefined)) + ? nodeAllAttrs.ref + : nodeAttrs.ref; + + var refNode; + if (refSelector) { + refNode = (selectorCache[refSelector] || this.findBySelector(refSelector, rootNode, opt.selectors))[0]; + if (!refNode) { + throw new Error('dia.CellView: "' + refSelector + '" reference does not exist.'); + } + } else { + refNode = null; + } + + item = { + node: node, + refNode: refNode, + processedAttributes: processedAttrs, + allAttributes: nodeAllAttrs + }; + + if (refNode) { + // If an element in the list is positioned relative to this one, then + // we want to insert this one before it in the list. + var itemIndex = relativeRefItems.findIndex(function(item) { + return item.refNode === node; + }); + + if (itemIndex > -1) { + relativeRefItems.splice(itemIndex, 0, item); + } else { + relativeRefItems.push(item); + } + } else { + // A node with no ref attribute. To be updated before the nodes referencing other nodes. + // The order of no-ref-items is not specified/important. + relativeItems.push(item); + } + } + } + + relativeItems.push(...relativeRefItems); + + for (let i = 0, n = relativeItems.length; i < n; i++) { + item = relativeItems[i]; + node = item.node; + refNode = item.refNode; + + // Find the reference element bounding box. If no reference was provided, we + // use the optional bounding box. + const refNodeId = refNode ? src_V.ensureId(refNode) : ''; + let refBBox = bboxCache[refNodeId]; + if (!refBBox) { + // Get the bounding box of the reference element using to the common ancestor + // transformation space. + // + // @example 1 + // + // + // + // + // + // In this case, the reference bounding box can not be affected + // by the `transform` attribute of the `` element, + // because the exact transformation will be applied to the `a` element + // as well as to the `b` element. + // + // @example 2 + // + // + // + // + // + // In this case, the reference bounding box have to be affected by the + // `transform` attribute of the `` element, because the `a` element + // is not descendant of the `` element and will not be affected + // by the transformation. + refBBox = bboxCache[refNodeId] = (refNode) + ? src_V(refNode).getBBox({ target: getCommonAncestorNode(node, refNode) }) + : opt.rootBBox; + } + + if (roAttrs) { + // if there was a special attribute affecting the position amongst passed-in attributes + // we have to merge it with the rest of the element's attributes as they are necessary + // to update the position relatively (i.e `ref-x` && 'ref-dx') + processedAttrs = this.processNodeAttributes(node, item.allAttributes); + this.mergeProcessedAttributes(processedAttrs, item.processedAttributes); + + } else { + processedAttrs = item.processedAttributes; + } + + this.updateRelativeAttributes(node, processedAttrs, refBBox, opt); + } + }, + + mergeProcessedAttributes: function(processedAttrs, roProcessedAttrs) { + + processedAttrs.set || (processedAttrs.set = {}); + processedAttrs.position || (processedAttrs.position = {}); + processedAttrs.offset || (processedAttrs.offset = {}); + + utilHelpers_assign(processedAttrs.set, roProcessedAttrs.set); + utilHelpers_assign(processedAttrs.position, roProcessedAttrs.position); + utilHelpers_assign(processedAttrs.offset, roProcessedAttrs.offset); + + // Handle also the special transform property. + var transform = processedAttrs.normal && processedAttrs.normal.transform; + if (transform !== undefined && roProcessedAttrs.normal) { + roProcessedAttrs.normal.transform = transform; + } + processedAttrs.normal = roProcessedAttrs.normal; + }, + + // Lifecycle methods + + // Called when the view is attached to the DOM, + // as result of `cell.addTo(graph)` being called (isInitialMount === true) + // or `paper.options.viewport` returning `true` (isInitialMount === false). + onMount(isInitialMount) { + if (isInitialMount) return; + this.mountTools(); + HighlighterView.mount(this); + }, + + // Called when the view is detached from the DOM, + // as result of `paper.options.viewport` returning `false`. + onDetach() { + this.unmountTools(); + HighlighterView.unmount(this); + }, + + // Called when the view is removed from the DOM + // as result of `cell.remove()`. + onRemove: function() { + this.removeTools(); + this.removeHighlighters(); + }, + + _toolsView: null, + + hasTools: function(name) { + var toolsView = this._toolsView; + if (!toolsView) return false; + if (!name) return true; + return (toolsView.getName() === name); + }, + + addTools: function(toolsView) { + + this.removeTools(); + + if (toolsView) { + this._toolsView = toolsView; + toolsView.configure({ relatedView: this }); + toolsView.listenTo(this.paper, 'tools:event', this.onToolEvent.bind(this)); + } + return this; + }, + + unmountTools() { + const toolsView = this._toolsView; + if (toolsView) toolsView.unmount(); + return this; + }, + + mountTools() { + const toolsView = this._toolsView; + // Prevent unnecessary re-appending of the tools. + if (toolsView && !toolsView.isMounted()) toolsView.mount(); + return this; + }, + + updateTools: function(opt) { + + var toolsView = this._toolsView; + if (toolsView) toolsView.update(opt); + return this; + }, + + removeTools: function() { + + var toolsView = this._toolsView; + if (toolsView) { + toolsView.remove(); + this._toolsView = null; + } + return this; + }, + + hideTools: function() { + + var toolsView = this._toolsView; + if (toolsView) toolsView.hide(); + return this; + }, + + showTools: function() { + + var toolsView = this._toolsView; + if (toolsView) toolsView.show(); + return this; + }, + + onToolEvent: function(event) { + switch (event) { + case 'remove': + this.removeTools(); + break; + case 'hide': + this.hideTools(); + break; + case 'show': + this.showTools(); + break; + } + }, + + removeHighlighters: function() { + HighlighterView.remove(this); + }, + + updateHighlighters: function(dirty = false) { + HighlighterView.update(this, null, dirty); + }, + + transformHighlighters: function() { + HighlighterView.transform(this); + }, + + // Interaction. The controller part. + // --------------------------------- + + preventDefaultInteraction(evt) { + this.eventData(evt, { defaultInteractionPrevented: true }); + }, + + isDefaultInteractionPrevented(evt) { + const { defaultInteractionPrevented = false } = this.eventData(evt); + return defaultInteractionPrevented; + }, + + // Interaction is handled by the paper and delegated to the view in interest. + // `x` & `y` parameters passed to these functions represent the coordinates already snapped to the paper grid. + // If necessary, real coordinates can be obtained from the `evt` event object. + + // These functions are supposed to be overridden by the views that inherit from `joint.dia.Cell`, + // i.e. `joint.dia.Element` and `joint.dia.Link`. + + pointerdblclick: function(evt, x, y) { + + this.notify('cell:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + this.notify('cell:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + this.notify('cell:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + const { model } = this; + const { graph } = model; + if (graph) { + model.startBatch('pointer'); + this.eventData(evt, { graph }); + } + + this.notify('cell:pointerdown', evt, x, y); + }, + + pointermove: function(evt, x, y) { + + this.notify('cell:pointermove', evt, x, y); + }, + + pointerup: function(evt, x, y) { + + const { graph } = this.eventData(evt); + + this.notify('cell:pointerup', evt, x, y); + + if (graph) { + // we don't want to trigger event on model as model doesn't + // need to be member of collection anymore (remove) + graph.stopBatch('pointer', { cell: this.model }); + } + }, + + mouseover: function(evt) { + + this.notify('cell:mouseover', evt); + }, + + mouseout: function(evt) { + + this.notify('cell:mouseout', evt); + }, + + mouseenter: function(evt) { + + this.notify('cell:mouseenter', evt); + }, + + mouseleave: function(evt) { + + this.notify('cell:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + this.notify('cell:mousewheel', evt, x, y, delta); + }, + + onevent: function(evt, eventName, x, y) { + + this.notify(eventName, evt, x, y); + }, + + onmagnet: function() { + + // noop + }, + + magnetpointerdblclick: function() { + + // noop + }, + + magnetcontextmenu: function() { + + // noop + }, + + checkMouseleave(evt) { + const { paper, model } = this; + if (paper.isAsync()) { + // Make sure the source/target views are updated before this view. + // It's not 100% bulletproof (see below) but it's a good enough solution for now. + // The connected cells could be links as well. In that case, we would + // need to recursively go through all the connected links and update + // their source/target views as well. + if (model.isLink()) { + // The `this.sourceView` and `this.targetView` might not be updated yet. + // We need to find the view by the model. + const sourceElement = model.getSourceElement(); + if (sourceElement) { + const sourceView = paper.findViewByModel(sourceElement); + if (sourceView) { + paper.dumpView(sourceView); + paper.checkViewVisibility(sourceView); + } + } + const targetElement = model.getTargetElement(); + if (targetElement) { + const targetView = paper.findViewByModel(targetElement); + if (targetView) { + paper.dumpView(targetView); + paper.checkViewVisibility(targetView); + } + } + } + // Do the updates of the current view synchronously now + paper.dumpView(this); + paper.checkViewVisibility(this); + } + const target = this.getEventTarget(evt, { fromPoint: true }); + const view = paper.findView(target); + if (view === this) return; + // Leaving the current view + this.mouseleave(evt); + if (!view) return; + // Entering another view + view.mouseenter(evt); + }, + + setInteractivity: function(value) { + + this.options.interactive = value; + } +}, { + + Flags, + + Highlighting: HighlightingTypes, + + addPresentationAttributes: function(presentationAttributes) { + return merge({}, result(this.prototype, 'presentationAttributes'), presentationAttributes, function(a, b) { + if (!a || !b) return; + if (typeof a === 'string') a = [a]; + if (typeof b === 'string') b = [b]; + if (Array.isArray(a) && Array.isArray(b)) return uniq(a.concat(b)); + }); + } +}); + +// TODO: Move to Vectorizer library. +function getCommonAncestorNode(node1, node2) { + let parent = node1; + do { + if (parent.contains(node2)) return parent; + parent = parent.parentNode; + } while (parent); + return null; +} + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/ElementView.mjs + + + + + + + + +const ElementView_Flags = { + TOOLS: CellView.Flags.TOOLS, + UPDATE: 'UPDATE', + TRANSLATE: 'TRANSLATE', + RESIZE: 'RESIZE', + PORTS: 'PORTS', + ROTATE: 'ROTATE', + RENDER: 'RENDER' +}; + +const DragActions = { + MOVE: 'move', + MAGNET: 'magnet', +}; +// Element base view and controller. +// ------------------------------------------- + +const ElementView = CellView.extend({ + + /** + * @abstract + */ + _removePorts: function() { + // implemented in ports.js + }, + + /** + * + * @abstract + */ + _renderPorts: function() { + // implemented in ports.js + }, + + className: function() { + + var classNames = CellView.prototype.className.apply(this).split(' '); + + classNames.push('element'); + + return classNames.join(' '); + }, + + initialize: function() { + + CellView.prototype.initialize.apply(this, arguments); + + this._initializePorts(); + }, + + presentationAttributes: { + 'attrs': [ElementView_Flags.UPDATE], + 'position': [ElementView_Flags.TRANSLATE, ElementView_Flags.TOOLS], + 'size': [ElementView_Flags.RESIZE, ElementView_Flags.PORTS, ElementView_Flags.TOOLS], + 'angle': [ElementView_Flags.ROTATE, ElementView_Flags.TOOLS], + 'markup': [ElementView_Flags.RENDER], + 'ports': [ElementView_Flags.PORTS], + }, + + initFlag: [ElementView_Flags.RENDER], + + UPDATE_PRIORITY: 0, + + confirmUpdate: function(flag, opt) { + + const { useCSSSelectors } = config; + if (this.hasFlag(flag, ElementView_Flags.PORTS)) { + this._removePorts(); + this._cleanPortsCache(); + } + let transformHighlighters = false; + if (this.hasFlag(flag, ElementView_Flags.RENDER)) { + this.render(); + this.updateTools(opt); + this.updateHighlighters(true); + transformHighlighters = true; + flag = this.removeFlag(flag, [ElementView_Flags.RENDER, ElementView_Flags.UPDATE, ElementView_Flags.RESIZE, ElementView_Flags.TRANSLATE, ElementView_Flags.ROTATE, ElementView_Flags.PORTS, ElementView_Flags.TOOLS]); + } else { + let updateHighlighters = false; + + // Skip this branch if render is required + if (this.hasFlag(flag, ElementView_Flags.RESIZE)) { + this.resize(opt); + updateHighlighters = true; + // Resize method is calling `update()` internally + flag = this.removeFlag(flag, [ElementView_Flags.RESIZE, ElementView_Flags.UPDATE]); + if (useCSSSelectors) { + // `resize()` rendered the ports when useCSSSelectors are enabled + flag = this.removeFlag(flag, ElementView_Flags.PORTS); + } + } + if (this.hasFlag(flag, ElementView_Flags.UPDATE)) { + this.update(this.model, null, opt); + flag = this.removeFlag(flag, ElementView_Flags.UPDATE); + updateHighlighters = true; + if (useCSSSelectors) { + // `update()` will render ports when useCSSSelectors are enabled + flag = this.removeFlag(flag, ElementView_Flags.PORTS); + } + } + if (this.hasFlag(flag, ElementView_Flags.TRANSLATE)) { + this.translate(); + flag = this.removeFlag(flag, ElementView_Flags.TRANSLATE); + transformHighlighters = true; + } + if (this.hasFlag(flag, ElementView_Flags.ROTATE)) { + this.rotate(); + flag = this.removeFlag(flag, ElementView_Flags.ROTATE); + transformHighlighters = true; + } + if (this.hasFlag(flag, ElementView_Flags.PORTS)) { + this._renderPorts(); + updateHighlighters = true; + flag = this.removeFlag(flag, ElementView_Flags.PORTS); + } + + if (updateHighlighters) { + this.updateHighlighters(false); + } + } + + if (transformHighlighters) { + this.transformHighlighters(); + } + + if (this.hasFlag(flag, ElementView_Flags.TOOLS)) { + this.updateTools(opt); + flag = this.removeFlag(flag, ElementView_Flags.TOOLS); + } + + return flag; + }, + + /** + * @abstract + */ + _initializePorts: function() { + + }, + + update: function(_, renderingOnlyAttrs) { + + this.cleanNodesCache(); + + // When CSS selector strings are used, make sure no rule matches port nodes. + const { useCSSSelectors } = config; + if (useCSSSelectors) this._removePorts(); + + var model = this.model; + var modelAttrs = model.attr(); + this.updateDOMSubtreeAttributes(this.el, modelAttrs, { + rootBBox: new Rect(model.size()), + selectors: this.selectors, + scalableNode: this.scalableNode, + rotatableNode: this.rotatableNode, + // Use rendering only attributes if they differs from the model attributes + roAttributes: (renderingOnlyAttrs === modelAttrs) ? null : renderingOnlyAttrs + }); + + if (useCSSSelectors) { + this._renderPorts(); + } + }, + + rotatableSelector: 'rotatable', + scalableSelector: 'scalable', + scalableNode: null, + rotatableNode: null, + + // `prototype.markup` is rendered by default. Set the `markup` attribute on the model if the + // default markup is not desirable. + renderMarkup: function() { + + var element = this.model; + var markup = element.get('markup') || element.markup; + if (!markup) throw new Error('dia.ElementView: markup required'); + if (Array.isArray(markup)) return this.renderJSONMarkup(markup); + if (typeof markup === 'string') return this.renderStringMarkup(markup); + throw new Error('dia.ElementView: invalid markup'); + }, + + renderJSONMarkup: function(markup) { + + var doc = this.parseDOMJSON(markup, this.el); + var selectors = this.selectors = doc.selectors; + this.rotatableNode = src_V(selectors[this.rotatableSelector]) || null; + this.scalableNode = src_V(selectors[this.scalableSelector]) || null; + // Fragment + this.vel.append(doc.fragment); + }, + + renderStringMarkup: function(markup) { + + var vel = this.vel; + vel.append(src_V(markup)); + // Cache transformation groups + this.rotatableNode = vel.findOne('.rotatable'); + this.scalableNode = vel.findOne('.scalable'); + + var selectors = this.selectors = {}; + selectors[this.selector] = this.el; + }, + + render: function() { + + this.vel.empty(); + this.renderMarkup(); + if (this.scalableNode) { + // Double update is necessary for elements with the scalable group only + // Note the resize() triggers the other `update`. + this.update(); + } + this.resize(); + if (this.rotatableNode) { + // Translate transformation is applied on `this.el` while the rotation transformation + // on `this.rotatableNode` + this.rotate(); + this.translate(); + } else { + this.updateTransformation(); + } + if (!config.useCSSSelectors) this._renderPorts(); + return this; + }, + + resize: function(opt) { + + if (this.scalableNode) return this.sgResize(opt); + if (this.model.attributes.angle) this.rotate(); + this.update(); + }, + + translate: function() { + + if (this.rotatableNode) return this.rgTranslate(); + this.updateTransformation(); + }, + + rotate: function() { + + if (this.rotatableNode) { + this.rgRotate(); + // It's necessary to call the update for the nodes outside + // the rotatable group referencing nodes inside the group + this.update(); + return; + } + this.updateTransformation(); + }, + + updateTransformation: function() { + + var transformation = this.getTranslateString(); + var rotateString = this.getRotateString(); + if (rotateString) transformation += ' ' + rotateString; + this.vel.attr('transform', transformation); + }, + + getTranslateString: function() { + + var position = this.model.attributes.position; + return 'translate(' + position.x + ',' + position.y + ')'; + }, + + getRotateString: function() { + var attributes = this.model.attributes; + var angle = attributes.angle; + if (!angle) return null; + var size = attributes.size; + return 'rotate(' + angle + ',' + (size.width / 2) + ',' + (size.height / 2) + ')'; + }, + + // Rotatable & Scalable Group + // always slower, kept mainly for backwards compatibility + + rgRotate: function() { + + this.rotatableNode.attr('transform', this.getRotateString()); + }, + + rgTranslate: function() { + + this.vel.attr('transform', this.getTranslateString()); + }, + + sgResize: function(opt) { + + var model = this.model; + var angle = model.angle(); + var size = model.size(); + var scalable = this.scalableNode; + + // Getting scalable group's bbox. + // Due to a bug in webkit's native SVG .getBBox implementation, the bbox of groups with path children includes the paths' control points. + // To work around the issue, we need to check whether there are any path elements inside the scalable group. + var recursive = false; + if (scalable.node.getElementsByTagName('path').length > 0) { + // If scalable has at least one descendant that is a path, we need to switch to recursive bbox calculation. + // If there are no path descendants, group bbox calculation works and so we can use the (faster) native function directly. + recursive = true; + } + var scalableBBox = scalable.getBBox({ recursive: recursive }); + + // Make sure `scalableBbox.width` and `scalableBbox.height` are not zero which can happen if the element does not have any content. By making + // the width/height 1, we prevent HTML errors of the type `scale(Infinity, Infinity)`. + var sx = (size.width / (scalableBBox.width || 1)); + var sy = (size.height / (scalableBBox.height || 1)); + scalable.attr('transform', 'scale(' + sx + ',' + sy + ')'); + + // Now the interesting part. The goal is to be able to store the object geometry via just `x`, `y`, `angle`, `width` and `height` + // Order of transformations is significant but we want to reconstruct the object always in the order: + // resize(), rotate(), translate() no matter of how the object was transformed. For that to work, + // we must adjust the `x` and `y` coordinates of the object whenever we resize it (because the origin of the + // rotation changes). The new `x` and `y` coordinates are computed by canceling the previous rotation + // around the center of the resized object (which is a different origin then the origin of the previous rotation) + // and getting the top-left corner of the resulting object. Then we clean up the rotation back to what it originally was. + + // Cancel the rotation but now around a different origin, which is the center of the scaled object. + var rotatable = this.rotatableNode; + var rotation = rotatable && rotatable.attr('transform'); + if (rotation) { + + rotatable.attr('transform', rotation + ' rotate(' + (-angle) + ',' + (size.width / 2) + ',' + (size.height / 2) + ')'); + var rotatableBBox = scalable.getBBox({ target: this.paper.cells }); + + // Store new x, y and perform rotate() again against the new rotation origin. + model.set('position', { x: rotatableBBox.x, y: rotatableBBox.y }, utilHelpers_assign({ updateHandled: true }, opt)); + this.translate(); + this.rotate(); + } + + // Update must always be called on non-rotated element. Otherwise, relative positioning + // would work with wrong (rotated) bounding boxes. + this.update(); + }, + + // Embedding mode methods. + // ----------------------- + + prepareEmbedding: function(data = {}) { + + const element = data.model || this.model; + const paper = data.paper || this.paper; + const graph = paper.model; + + const initialZIndices = data.initialZIndices = {}; + const embeddedCells = element.getEmbeddedCells({ deep: true }); + const connectedLinks = graph.getConnectedLinks(element, { deep: true, includeEnclosed: true }); + + // Note: an embedded cell can be a connect link, but it's fine + // to iterate over the cell twice. + [ + element, + ...embeddedCells, + ...connectedLinks + ].forEach(cell => initialZIndices[cell.id] = cell.attributes.z); + + element.startBatch('to-front'); + + // Bring the model to the front with all his embeds. + element.toFront({ deep: true, ui: true }); + + // Note that at this point cells in the collection are not sorted by z index (it's running in the batch, see + // the dia.Graph._sortOnChangeZ), so we can't assume that the last cell in the collection has the highest z. + const maxZ = graph.getElements().reduce((max, cell) => Math.max(max, cell.attributes.z || 0), 0); + + // Move to front also all the inbound and outbound links that are connected + // to any of the element descendant. If we bring to front only embedded elements, + // links connected to them would stay in the background. + connectedLinks.forEach((link) => { + if (link.attributes.z <= maxZ) { + link.set('z', maxZ + 1, { ui: true }); + } + }); + + element.stopBatch('to-front'); + + // Before we start looking for suitable parent we remove the current one. + const parentId = element.parent(); + if (parentId) { + const parent = graph.getCell(parentId); + parent.unembed(element, { ui: true }); + data.initialParentId = parentId; + } else { + data.initialParentId = null; + } + }, + + processEmbedding: function(data = {}, evt, x, y) { + + const model = data.model || this.model; + const paper = data.paper || this.paper; + const graph = paper.model; + const { findParentBy, frontParentOnly, validateEmbedding } = paper.options; + + let candidates; + if (isFunction(findParentBy)) { + candidates = toArray(findParentBy.call(graph, this, evt, x, y)); + } else if (findParentBy === 'pointer') { + candidates = toArray(graph.findModelsFromPoint({ x, y })); + } else { + candidates = graph.findModelsUnderElement(model, { searchBy: findParentBy }); + } + + candidates = candidates.filter((el) => { + return (el instanceof Cell) && (model.id !== el.id) && !el.isEmbeddedIn(model); + }); + + if (frontParentOnly) { + // pick the element with the highest `z` index + candidates = candidates.slice(-1); + } + + let newCandidateView = null; + const prevCandidateView = data.candidateEmbedView; + + // iterate over all candidates starting from the last one (has the highest z-index). + for (let i = candidates.length - 1; i >= 0; i--) { + const candidate = candidates[i]; + if (prevCandidateView && prevCandidateView.model.id == candidate.id) { + // candidate remains the same + newCandidateView = prevCandidateView; + break; + } else { + const view = candidate.findView(paper); + if (!isFunction(validateEmbedding) || validateEmbedding.call(paper, this, view)) { + // flip to the new candidate + newCandidateView = view; + break; + } + } + } + + if (newCandidateView && newCandidateView != prevCandidateView) { + // A new candidate view found. Highlight the new one. + this.clearEmbedding(data); + data.candidateEmbedView = newCandidateView.highlight( + newCandidateView.findProxyNode(null, 'container'), + { embedding: true } + ); + } + + if (!newCandidateView && prevCandidateView) { + // No candidate view found. Unhighlight the previous candidate. + this.clearEmbedding(data); + } + }, + + clearEmbedding: function(data) { + + data || (data = {}); + + var candidateView = data.candidateEmbedView; + if (candidateView) { + // No candidate view found. Unhighlight the previous candidate. + candidateView.unhighlight( + candidateView.findProxyNode(null, 'container'), + { embedding: true } + ); + data.candidateEmbedView = null; + } + }, + + finalizeEmbedding: function(data = {}) { + + const candidateView = data.candidateEmbedView; + const element = data.model || this.model; + const paper = data.paper || this.paper; + + if (candidateView) { + + // We finished embedding. Candidate view is chosen to become the parent of the model. + candidateView.model.embed(element, { ui: true }); + candidateView.unhighlight(candidateView.findProxyNode(null, 'container'), { embedding: true }); + + data.candidateEmbedView = null; + + } else { + + const { validateUnembedding } = paper.options; + const { initialParentId } = data; + // The element was originally embedded into another element. + // The interaction would unembed the element. Let's validate + // if the element can be unembedded. + if ( + initialParentId && + typeof validateUnembedding === 'function' && + !validateUnembedding.call(paper, this) + ) { + this._disallowUnembed(data); + return; + } + } + + paper.model.getConnectedLinks(element, { deep: true }).forEach(link => { + link.reparent({ ui: true }); + }); + }, + + _disallowUnembed: function(data) { + const { model, whenNotAllowed = 'revert' } = data; + const element = model || this.model; + const paper = data.paper || this.paper; + const graph = paper.model; + switch (whenNotAllowed) { + case 'remove': { + element.remove({ ui: true }); + break; + } + case 'revert': { + const { initialParentId, initialPosition, initialZIndices } = data; + // Revert the element's position (and the position of its embedded cells if any) + if (initialPosition) { + const { x, y } = initialPosition; + element.position(x, y, { deep: true, ui: true }); + } + // Revert all the z-indices changed during the embedding + if (initialZIndices) { + Object.keys(initialZIndices).forEach(id => { + const cell = graph.getCell(id); + if (cell) { + cell.set('z', initialZIndices[id], { ui: true }); + } + }); + } + // Revert the original parent + const parent = graph.getCell(initialParentId); + if (parent) { + parent.embed(element, { ui: true }); + } + break; + } + } + }, + + getDelegatedView: function() { + + var view = this; + var model = view.model; + var paper = view.paper; + + while (view) { + if (model.isLink()) break; + if (!model.isEmbedded() || view.can('stopDelegation')) return view; + model = model.getParentCell(); + view = paper.findViewByModel(model); + } + + return null; + }, + + findProxyNode: function(el, type) { + el || (el = this.el); + const nodeSelector = el.getAttribute(`${type}-selector`); + if (nodeSelector) { + const port = this.findAttribute('port', el); + if (port) { + const proxyPortNode = this.findPortNode(port, nodeSelector); + if (proxyPortNode) return proxyPortNode; + } else { + const [proxyNode] = this.findBySelector(nodeSelector); + if (proxyNode) return proxyNode; + } + } + return el; + }, + + // Interaction. The controller part. + // --------------------------------- + + notifyPointerdown(evt, x, y) { + CellView.prototype.pointerdown.call(this, evt, x, y); + this.notify('element:pointerdown', evt, x, y); + }, + + notifyPointermove(evt, x, y) { + CellView.prototype.pointermove.call(this, evt, x, y); + this.notify('element:pointermove', evt, x, y); + }, + + notifyPointerup(evt, x, y) { + this.notify('element:pointerup', evt, x, y); + CellView.prototype.pointerup.call(this, evt, x, y); + }, + + pointerdblclick: function(evt, x, y) { + + CellView.prototype.pointerdblclick.apply(this, arguments); + this.notify('element:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + CellView.prototype.pointerclick.apply(this, arguments); + this.notify('element:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + CellView.prototype.contextmenu.apply(this, arguments); + this.notify('element:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + this.notifyPointerdown(evt, x, y); + this.dragStart(evt, x, y); + }, + + pointermove: function(evt, x, y) { + + const data = this.eventData(evt); + const { targetMagnet, action, delegatedView } = data; + + if (targetMagnet) { + this.magnetpointermove(evt, targetMagnet, x, y); + } + + switch (action) { + case DragActions.MAGNET: + this.dragMagnet(evt, x, y); + break; + case DragActions.MOVE: + (delegatedView || this).drag(evt, x, y); + // eslint: no-fallthrough=false + default: + if (data.preventPointerEvents) break; + this.notifyPointermove(evt, x, y); + break; + } + + // Make sure the element view data is passed along. + // It could have been wiped out in the handlers above. + this.eventData(evt, data); + }, + + pointerup: function(evt, x, y) { + + const data = this.eventData(evt); + const { targetMagnet, action, delegatedView } = data; + + if (targetMagnet) { + this.magnetpointerup(evt, targetMagnet, x, y); + } + + switch (action) { + case DragActions.MAGNET: + this.dragMagnetEnd(evt, x, y); + break; + case DragActions.MOVE: + (delegatedView || this).dragEnd(evt, x, y); + // eslint: no-fallthrough=false + default: + if (data.preventPointerEvents) break; + this.notifyPointerup(evt, x, y); + } + + if (targetMagnet) { + this.magnetpointerclick(evt, targetMagnet, x, y); + } + + this.checkMouseleave(evt); + }, + + mouseover: function(evt) { + + CellView.prototype.mouseover.apply(this, arguments); + this.notify('element:mouseover', evt); + }, + + mouseout: function(evt) { + + CellView.prototype.mouseout.apply(this, arguments); + this.notify('element:mouseout', evt); + }, + + mouseenter: function(evt) { + + CellView.prototype.mouseenter.apply(this, arguments); + this.notify('element:mouseenter', evt); + }, + + mouseleave: function(evt) { + + CellView.prototype.mouseleave.apply(this, arguments); + this.notify('element:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + CellView.prototype.mousewheel.apply(this, arguments); + this.notify('element:mousewheel', evt, x, y, delta); + }, + + onmagnet: function(evt, x, y) { + + const { currentTarget: targetMagnet } = evt; + this.magnetpointerdown(evt, targetMagnet, x, y); + this.eventData(evt, { targetMagnet }); + this.dragMagnetStart(evt, x, y); + }, + + magnetpointerdown: function(evt, magnet, x, y) { + + this.notify('element:magnet:pointerdown', evt, magnet, x, y); + }, + + magnetpointermove: function(evt, magnet, x, y) { + + this.notify('element:magnet:pointermove', evt, magnet, x, y); + }, + + magnetpointerup: function(evt, magnet, x, y) { + + this.notify('element:magnet:pointerup', evt, magnet, x, y); + }, + + magnetpointerdblclick: function(evt, magnet, x, y) { + + this.notify('element:magnet:pointerdblclick', evt, magnet, x, y); + }, + + magnetcontextmenu: function(evt, magnet, x, y) { + + this.notify('element:magnet:contextmenu', evt, magnet, x, y); + }, + + // Drag Start Handlers + + dragStart: function(evt, x, y) { + + if (this.isDefaultInteractionPrevented(evt)) return; + + var view = this.getDelegatedView(); + if (!view || !view.can('elementMove')) return; + + this.eventData(evt, { + action: DragActions.MOVE, + delegatedView: view + }); + + const position = view.model.position(); + view.eventData(evt, { + initialPosition: position, + pointerOffset: position.difference(x, y), + restrictedArea: this.paper.getRestrictedArea(view, x, y) + }); + }, + + dragMagnetStart: function(evt, x, y) { + + const { paper } = this; + const isPropagationAlreadyStopped = evt.isPropagationStopped(); + if (isPropagationAlreadyStopped) { + // Special case when the propagation was already stopped + // on the `element:magnet:pointerdown` event. + // Do not trigger any `element:pointer*` events + // but still start the magnet dragging. + this.eventData(evt, { preventPointerEvents: true }); + } + + if (this.isDefaultInteractionPrevented(evt) || !this.can('addLinkFromMagnet')) { + // Stop the default action, which is to start dragging a link. + return; + } + + const { targetMagnet = evt.currentTarget } = this.eventData(evt); + evt.stopPropagation(); + + // Invalid (Passive) magnet. Start dragging the element. + if (!paper.options.validateMagnet.call(paper, this, targetMagnet, evt)) { + if (isPropagationAlreadyStopped) { + // Do not trigger `element:pointerdown` and start element dragging + // if the propagation was stopped. + this.dragStart(evt, x, y); + // The `element:pointerdown` event is not triggered because + // of `preventPointerEvents` flag. + } else { + // We need to reset the action + // to `MOVE` so that the element is dragged. + this.pointerdown(evt, x, y); + } + return; + } + + // Valid magnet. Start dragging a link. + if (paper.options.magnetThreshold <= 0) { + this.dragLinkStart(evt, targetMagnet, x, y); + } + this.eventData(evt, { action: DragActions.MAGNET }); + }, + + // Drag Handlers + + drag: function(evt, x, y) { + + var paper = this.paper; + var grid = paper.options.gridSize; + var element = this.model; + var data = this.eventData(evt); + var { pointerOffset, restrictedArea, embedding } = data; + + // Make sure the new element's position always snaps to the current grid + var elX = snapToGrid(x + pointerOffset.x, grid); + var elY = snapToGrid(y + pointerOffset.y, grid); + + element.position(elX, elY, { restrictedArea, deep: true, ui: true }); + + if (paper.options.embeddingMode) { + if (!embedding) { + // Prepare the element for embedding only if the pointer moves. + // We don't want to do unnecessary action with the element + // if an user only clicks/dblclicks on it. + this.prepareEmbedding(data); + embedding = true; + } + this.processEmbedding(data, evt, x, y); + } + + this.eventData(evt, { + embedding + }); + }, + + dragMagnet: function(evt, x, y) { + this.dragLink(evt, x, y); + }, + + // Drag End Handlers + + dragEnd: function(evt, x, y) { + + var data = this.eventData(evt); + if (data.embedding) this.finalizeEmbedding(data); + }, + + dragMagnetEnd: function(evt, x, y) { + this.dragLinkEnd(evt, x, y); + }, + + magnetpointerclick: function(evt, magnet, x, y) { + var paper = this.paper; + if (paper.eventData(evt).mousemoved > paper.options.clickThreshold) return; + this.notify('element:magnet:pointerclick', evt, magnet, x, y); + } + +}, { + + Flags: ElementView_Flags, +}); + +utilHelpers_assign(ElementView.prototype, elementViewPortPrototype); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/env/index.mjs +const env = { + + _results: {}, + + _tests: { + + svgforeignobject: function() { + return !!document.createElementNS && + /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'))); + } + }, + + addTest: function(name, fn) { + + return this._tests[name] = fn; + }, + + test: function(name) { + + var fn = this._tests[name]; + + if (!fn) { + throw new Error('Test not defined ("' + name + '"). Use `joint.env.addTest(name, fn) to add a new test.`'); + } + + var result = this._results[name]; + + if (typeof result !== 'undefined') { + return result; + } + + try { + result = fn(); + } catch (error) { + result = false; + } + + // Cache the test result. + this._results[name] = result; + + return result; + } +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/basic.mjs + + + + + +const Generic = Element_Element.define('basic.Generic', { + attrs: { + '.': { fill: '#ffffff', stroke: 'none' } + } +}); + +const basic_Rect = Generic.define('basic.Rect', { + attrs: { + 'rect': { + fill: '#ffffff', + stroke: '#000000', + width: 100, + height: 60 + }, + 'text': { + fill: '#000000', + text: '', + 'font-size': 14, + 'ref-x': .5, + 'ref-y': .5, + 'text-anchor': 'middle', + 'y-alignment': 'middle', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '' +}); + +const TextView = ElementView.extend({ + + presentationAttributes: ElementView.addPresentationAttributes({ + // The element view is not automatically re-scaled to fit the model size + // when the attribute 'attrs' is changed. + attrs: ['SCALE'] + }), + + confirmUpdate: function() { + var flags = ElementView.prototype.confirmUpdate.apply(this, arguments); + if (this.hasFlag(flags, 'SCALE')) { + this.resize(); + flags = this.removeFlag(flags, 'SCALE'); + } + return flags; + } +}); + +const Text = Generic.define('basic.Text', { + attrs: { + 'text': { + 'font-size': 18, + fill: '#000000' + } + } +}, { + markup: '', +}); + +const Circle = Generic.define('basic.Circle', { + size: { width: 60, height: 60 }, + attrs: { + 'circle': { + fill: '#ffffff', + stroke: '#000000', + r: 30, + cx: 30, + cy: 30 + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +const basic_Ellipse = Generic.define('basic.Ellipse', { + size: { width: 60, height: 40 }, + attrs: { + 'ellipse': { + fill: '#ffffff', + stroke: '#000000', + rx: 30, + ry: 20, + cx: 30, + cy: 20 + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +const basic_Polygon = Generic.define('basic.Polygon', { + size: { width: 60, height: 40 }, + attrs: { + 'polygon': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +const basic_Polyline = Generic.define('basic.Polyline', { + size: { width: 60, height: 40 }, + attrs: { + 'polyline': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +const Image = Generic.define('basic.Image', { + attrs: { + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +const basic_Path = Generic.define('basic.Path', { + size: { width: 60, height: 60 }, + attrs: { + 'path': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref': 'path', + 'ref-x': .5, + 'ref-dy': 10, + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } + +}, { + markup: '', +}); + +const Rhombus = basic_Path.define('basic.Rhombus', { + attrs: { + 'path': { + d: 'M 30 0 L 60 30 30 60 0 30 z' + }, + 'text': { + 'ref-y': .5, + 'ref-dy': null, + 'y-alignment': 'middle' + } + } +}); + +const svgForeignObjectSupported = env.test('svgforeignobject'); + +const TextBlock = Generic.define('basic.TextBlock', { + // see joint.css for more element styles + attrs: { + rect: { + fill: '#ffffff', + stroke: '#000000', + width: 80, + height: 100 + }, + text: { + fill: '#000000', + 'font-size': 14, + 'font-family': 'Arial, helvetica, sans-serif' + }, + '.content': { + text: '', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + 'x-alignment': 'middle' + } + }, + + content: '' +}, { + markup: [ + '', + '', + svgForeignObjectSupported + ? '
' + : '', + '' + ].join(''), + + initialize: function() { + + this.listenTo(this, 'change:size', this.updateSize); + this.listenTo(this, 'change:content', this.updateContent); + this.updateSize(this, this.get('size')); + this.updateContent(this, this.get('content')); + Generic.prototype.initialize.apply(this, arguments); + }, + + updateSize: function(cell, size) { + + // Selector `foreignObject' doesn't work across all browsers, we're using class selector instead. + // We have to clone size as we don't want attributes.div.style to be same object as attributes.size. + this.attr({ + '.fobj': utilHelpers_assign({}, size), + div: { + style: utilHelpers_assign({}, size) + } + }); + }, + + updateContent: function(cell, content) { + + if (svgForeignObjectSupported) { + + // Content element is a
element. + this.attr({ + '.content': { + html: sanitizeHTML(content) + } + }); + + } else { + + // Content element is a element. + // SVG elements don't have innerHTML attribute. + this.attr({ + '.content': { + text: content + } + }); + } + }, + + // Here for backwards compatibility: + setForeignObjectSize: function() { + + this.updateSize.apply(this, arguments); + }, + + // Here for backwards compatibility: + setDivContent: function() { + + this.updateContent.apply(this, arguments); + } +}); + +// TextBlockView implements the fallback for IE when no foreignObject exists and +// the text needs to be manually broken. +const TextBlockView = ElementView.extend({ + + presentationAttributes: svgForeignObjectSupported + ? ElementView.prototype.presentationAttributes + : ElementView.addPresentationAttributes({ + content: ['CONTENT'], + size: ['CONTENT'] + }), + + initFlag: ['RENDER', 'CONTENT'], + + confirmUpdate: function() { + var flags = ElementView.prototype.confirmUpdate.apply(this, arguments); + if (this.hasFlag(flags, 'CONTENT')) { + this.updateContent(this.model); + flags = this.removeFlag(flags, 'CONTENT'); + } + return flags; + }, + + update: function(_, renderingOnlyAttrs) { + + var model = this.model; + + if (!svgForeignObjectSupported) { + + // Update everything but the content first. + var noTextAttrs = omit(renderingOnlyAttrs || model.get('attrs'), '.content'); + ElementView.prototype.update.call(this, model, noTextAttrs); + + if (!renderingOnlyAttrs || has(renderingOnlyAttrs, '.content')) { + // Update the content itself. + this.updateContent(model, renderingOnlyAttrs); + } + + } else { + + ElementView.prototype.update.call(this, model, renderingOnlyAttrs); + } + }, + + updateContent: function(cell, renderingOnlyAttrs) { + + // Create copy of the text attributes + var textAttrs = merge({}, (renderingOnlyAttrs || cell.get('attrs'))['.content']); + + textAttrs = omit(textAttrs, 'text'); + + // Break the content to fit the element size taking into account the attributes + // set on the model. + var text = breakText(cell.get('content'), cell.get('size'), textAttrs, { + // measuring sandbox svg document + svgDocument: this.paper.svg + }); + + // Create a new attrs with same structure as the model attrs { text: { *textAttributes* }} + var attrs = setByPath({}, '.content', textAttrs, '/'); + + // Replace text attribute with the one we just processed. + attrs['.content'].text = text; + + // Update the view using renderingOnlyAttributes parameter. + ElementView.prototype.update.call(this, cell, attrs); + } +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/standard.mjs + + + + + + + + +// ELEMENTS + +const Rectangle = Element_Element.define('standard.Rectangle', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'rect', + selector: 'body', + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Circle = Element_Element.define('standard.Circle', { + attrs: { + body: { + refCx: '50%', + refCy: '50%', + refR: '50%', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'circle', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Ellipse = Element_Element.define('standard.Ellipse', { + attrs: { + body: { + refCx: '50%', + refCy: '50%', + refRx: '50%', + refRy: '50%', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'ellipse', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Path = Element_Element.define('standard.Path', { + attrs: { + body: { + refD: 'M 0 0 L 10 0 10 10 0 10 Z', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'path', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Polygon = Element_Element.define('standard.Polygon', { + attrs: { + body: { + refPoints: '0 0 10 0 10 10 0 10', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'polygon', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Polyline = Element_Element.define('standard.Polyline', { + attrs: { + body: { + refPoints: '0 0 10 0 10 10 0 10 0 0', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'polyline', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const standard_Image = Element_Element.define('standard.Image', { + attrs: { + image: { + refWidth: '100%', + refHeight: '100%', + // xlinkHref: '[URL]' + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 10, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'image', + selector: 'image' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const BorderedImage = Element_Element.define('standard.BorderedImage', { + attrs: { + border: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + strokeWidth: 2 + }, + background: { + refWidth: -1, + refHeight: -1, + x: 0.5, + y: 0.5, + fill: '#FFFFFF' + }, + image: { + // xlinkHref: '[URL]' + refWidth: -1, + refHeight: -1, + x: 0.5, + y: 0.5 + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 10, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'rect', + selector: 'background', + attributes: { + 'stroke': 'none' + } + }, { + tagName: 'image', + selector: 'image' + }, { + tagName: 'rect', + selector: 'border', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const EmbeddedImage = Element_Element.define('standard.EmbeddedImage', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + fill: '#FFFFFF', + strokeWidth: 2 + }, + image: { + // xlinkHref: '[URL]' + refWidth: '30%', + refHeight: -20, + x: 10, + y: 10, + preserveAspectRatio: 'xMidYMin' + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'left', + refX: '30%', + refX2: 20, // 10 + 10 + refY: 10, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, { + tagName: 'image', + selector: 'image' + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const InscribedImage = Element_Element.define('standard.InscribedImage', { + attrs: { + border: { + refRx: '50%', + refRy: '50%', + refCx: '50%', + refCy: '50%', + stroke: '#333333', + strokeWidth: 2 + }, + background: { + refRx: '50%', + refRy: '50%', + refCx: '50%', + refCy: '50%', + fill: '#FFFFFF' + }, + image: { + // The image corners touch the border when its size is Math.sqrt(2) / 2 = 0.707.. ~= 70% + refWidth: '68%', + refHeight: '68%', + // The image offset is calculated as (100% - 68%) / 2 + refX: '16%', + refY: '16%', + preserveAspectRatio: 'xMidYMid' + // xlinkHref: '[URL]' + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 10, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'ellipse', + selector: 'background' + }, { + tagName: 'image', + selector: 'image' + }, { + tagName: 'ellipse', + selector: 'border', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'text', + selector: 'label' + }] +}); + +const HeaderedRectangle = Element_Element.define('standard.HeaderedRectangle', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + header: { + refWidth: '100%', + height: 30, + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + headerText: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: 15, + fontSize: 16, + fill: '#333333' + }, + bodyText: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + refY2: 15, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, { + tagName: 'rect', + selector: 'header' + }, { + tagName: 'text', + selector: 'headerText' + }, { + tagName: 'text', + selector: 'bodyText' + }] +}); + +var CYLINDER_TILT = 10; + +const Cylinder = Element_Element.define('standard.Cylinder', { + attrs: { + body: { + lateralArea: CYLINDER_TILT, + fill: '#FFFFFF', + stroke: '#333333', + strokeWidth: 2 + }, + top: { + refCx: '50%', + cy: CYLINDER_TILT, + refRx: '50%', + ry: CYLINDER_TILT, + fill: '#FFFFFF', + stroke: '#333333', + strokeWidth: 2 + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 15, + fontSize: 14, + fill: '#333333' + } + } +}, { + markup: [{ + tagName: 'path', + selector: 'body' + }, { + tagName: 'ellipse', + selector: 'top' + }, { + tagName: 'text', + selector: 'label' + }], + + topRy: function(t, opt) { + // getter + if (t === undefined) return this.attr('body/lateralArea'); + + // setter + var isPercentageSetter = isPercentage(t); + + var bodyAttrs = { lateralArea: t }; + var topAttrs = isPercentageSetter + ? { refCy: t, refRy: t, cy: null, ry: null } + : { refCy: null, refRy: null, cy: t, ry: t }; + + return this.attr({ body: bodyAttrs, top: topAttrs }, opt); + } + +}, { + attributes: { + lateralArea: { + set: function(t, refBBox) { + var isPercentageSetter = isPercentage(t); + if (isPercentageSetter) t = parseFloat(t) / 100; + + var x = refBBox.x; + var y = refBBox.y; + var w = refBBox.width; + var h = refBBox.height; + + // curve control point variables + var rx = w / 2; + var ry = isPercentageSetter ? (h * t) : t; + + var kappa = src_V.KAPPA; + var cx = kappa * rx; + var cy = kappa * (isPercentageSetter ? (h * t) : t); + + // shape variables + var xLeft = x; + var xCenter = x + (w / 2); + var xRight = x + w; + + var ySideTop = y + ry; + var yCurveTop = ySideTop - ry; + var ySideBottom = y + h - ry; + var yCurveBottom = y + h; + + // return calculated shape + var data = [ + 'M', xLeft, ySideTop, + 'L', xLeft, ySideBottom, + 'C', x, (ySideBottom + cy), (xCenter - cx), yCurveBottom, xCenter, yCurveBottom, + 'C', (xCenter + cx), yCurveBottom, xRight, (ySideBottom + cy), xRight, ySideBottom, + 'L', xRight, ySideTop, + 'C', xRight, (ySideTop - cy), (xCenter + cx), yCurveTop, xCenter, yCurveTop, + 'C', (xCenter - cx), yCurveTop, xLeft, (ySideTop - cy), xLeft, ySideTop, + 'Z' + ]; + return { d: data.join(' ') }; + } + } + } +}); + +var foLabelMarkup = { + tagName: 'foreignObject', + selector: 'foreignObject', + attributes: { + 'overflow': 'hidden' + }, + children: [{ + tagName: 'div', + namespaceURI: 'http://www.w3.org/1999/xhtml', + selector: 'label', + style: { + width: '100%', + height: '100%', + position: 'static', + backgroundColor: 'transparent', + textAlign: 'center', + margin: 0, + padding: '0px 5px', + boxSizing: 'border-box', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + } + }] +}; + +var svgLabelMarkup = { + tagName: 'text', + selector: 'label', + attributes: { + 'text-anchor': 'middle' + } +}; + +var labelMarkup = (env.test('svgforeignobject')) ? foLabelMarkup : svgLabelMarkup; + +const standard_TextBlock = Element_Element.define('standard.TextBlock', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + fill: '#ffffff', + strokeWidth: 2 + }, + foreignObject: { + refWidth: '100%', + refHeight: '100%' + }, + label: { + style: { + fontSize: 14 + } + } + } +}, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, labelMarkup] +}, { + attributes: { + text: { + set: function(text, refBBox, node, attrs) { + if (node instanceof HTMLElement) { + node.textContent = text; + } else { + // No foreign object + var style = attrs.style || {}; + var wrapValue = { text: text, width: -5, height: '100%' }; + var wrapAttrs = utilHelpers_assign({ textVerticalAnchor: 'middle' }, style); + attributes.textWrap.set.call(this, wrapValue, refBBox, node, wrapAttrs); + return { fill: style.color || null }; + } + }, + position: function(text, refBBox, node) { + // No foreign object + if (node instanceof SVGElement) return refBBox.center(); + } + } + } +}); + +// LINKS + +const standard_Link = Link.define('standard.Link', { + attrs: { + line: { + connection: true, + stroke: '#333333', + strokeWidth: 2, + strokeLinejoin: 'round', + targetMarker: { + 'type': 'path', + 'd': 'M 10 -5 0 0 10 5 z' + } + }, + wrapper: { + connection: true, + strokeWidth: 10, + strokeLinejoin: 'round' + } + } +}, { + markup: [{ + tagName: 'path', + selector: 'wrapper', + attributes: { + 'fill': 'none', + 'cursor': 'pointer', + 'stroke': 'transparent', + 'stroke-linecap': 'round' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none', + 'pointer-events': 'none' + } + }] +}); + +const DoubleLink = Link.define('standard.DoubleLink', { + attrs: { + line: { + connection: true, + stroke: '#DDDDDD', + strokeWidth: 4, + strokeLinejoin: 'round', + targetMarker: { + type: 'path', + stroke: '#000000', + d: 'M 10 -3 10 -10 -2 0 10 10 10 3' + } + }, + outline: { + connection: true, + stroke: '#000000', + strokeWidth: 6, + strokeLinejoin: 'round' + } + } +}, { + markup: [{ + tagName: 'path', + selector: 'outline', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none' + } + }] +}); + +const ShadowLink = Link.define('standard.ShadowLink', { + attrs: { + line: { + connection: true, + stroke: '#FF0000', + strokeWidth: 20, + strokeLinejoin: 'round', + targetMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M 0 -10 -10 0 0 10 z' + }, + sourceMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z' + } + }, + shadow: { + connection: true, + refX: 3, + refY: 6, + stroke: '#000000', + strokeOpacity: 0.2, + strokeWidth: 20, + strokeLinejoin: 'round', + targetMarker: { + 'type': 'path', + 'd': 'M 0 -10 -10 0 0 10 z', + 'stroke': 'none' + }, + sourceMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z' + } + } + } +}, { + markup: [{ + tagName: 'path', + selector: 'shadow', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none' + } + }] +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/devs.mjs + + + + +/** + * @deprecated use the port api instead + */ +const Model = Generic.define('devs.Model', { + inPorts: [], + outPorts: [], + size: { + width: 80, + height: 80 + }, + attrs: { + '.': { + magnet: false + }, + '.label': { + text: 'Model', + 'ref-x': .5, + 'ref-y': 10, + 'font-size': 18, + 'text-anchor': 'middle', + fill: '#000' + }, + '.body': { + 'ref-width': '100%', + 'ref-height': '100%', + stroke: '#000' + } + }, + ports: { + groups: { + 'in': { + position: { + name: 'left' + }, + attrs: { + '.port-label': { + fill: '#000' + }, + '.port-body': { + fill: '#fff', + stroke: '#000', + r: 10, + magnet: true + } + }, + label: { + position: { + name: 'left', + args: { + y: 10 + } + } + } + }, + 'out': { + position: { + name: 'right' + }, + attrs: { + '.port-label': { + fill: '#000' + }, + '.port-body': { + fill: '#fff', + stroke: '#000', + r: 10, + magnet: true + } + }, + label: { + position: { + name: 'right', + args: { + y: 10 + } + } + } + } + } + } +}, { + markup: '', + portMarkup: '', + portLabelMarkup: '', + + initialize: function() { + + Generic.prototype.initialize.apply(this, arguments); + + this.on('change:inPorts change:outPorts', this.updatePortItems, this); + this.updatePortItems(); + }, + + updatePortItems: function(model, changed, opt) { + + // Make sure all ports are unique. + var inPorts = uniq(this.get('inPorts')); + var outPorts = difference(uniq(this.get('outPorts')), inPorts); + + var inPortItems = this.createPortItems('in', inPorts); + var outPortItems = this.createPortItems('out', outPorts); + + this.prop('ports/items', inPortItems.concat(outPortItems), utilHelpers_assign({ rewrite: true }, opt)); + }, + + createPortItem: function(group, port) { + + return { + id: port, + group: group, + attrs: { + '.port-label': { + text: port + } + } + }; + }, + + createPortItems: function(group, ports) { + + return toArray(ports).map(this.createPortItem.bind(this, group)); + }, + + _addGroupPort: function(port, group, opt) { + + var ports = this.get(group); + return this.set(group, Array.isArray(ports) ? ports.concat(port) : [port], opt); + }, + + addOutPort: function(port, opt) { + + return this._addGroupPort(port, 'outPorts', opt); + }, + + addInPort: function(port, opt) { + + return this._addGroupPort(port, 'inPorts', opt); + }, + + _removeGroupPort: function(port, group, opt) { + + return this.set(group, without(this.get(group), port), opt); + }, + + removeOutPort: function(port, opt) { + + return this._removeGroupPort(port, 'outPorts', opt); + }, + + removeInPort: function(port, opt) { + + return this._removeGroupPort(port, 'inPorts', opt); + }, + + _changeGroup: function(group, properties, opt) { + + return this.prop('ports/groups/' + group, isObject(properties) ? properties : {}, opt); + }, + + changeInGroup: function(properties, opt) { + + return this._changeGroup('in', properties, opt); + }, + + changeOutGroup: function(properties, opt) { + + return this._changeGroup('out', properties, opt); + } +}); + +const Atomic = Model.define('devs.Atomic', { + size: { + width: 80, + height: 80 + }, + attrs: { + '.label': { + text: 'Atomic' + } + } +}); + +const Coupled = Model.define('devs.Coupled', { + size: { + width: 200, + height: 300 + }, + attrs: { + '.label': { + text: 'Coupled' + } + } +}); + +const devs_Link = Link.define('devs.Link', { + attrs: { + '.connection': { + 'stroke-width': 2 + } + } +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/logic.mjs + + + +const Gate = Generic.define('logic.Gate', { + size: { width: 80, height: 40 }, + attrs: { + '.': { magnet: false }, + '.body': { width: 100, height: 50 }, + circle: { r: 7, stroke: 'black', fill: 'transparent', 'stroke-width': 2 } + } +}, { + operation: function() { + return true; + } +}); + +const IO = Gate.define('logic.IO', { + size: { width: 60, height: 30 }, + attrs: { + '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 }, + '.wire': { ref: '.body', 'ref-y': .5, stroke: 'black' }, + text: { + fill: 'black', + ref: '.body', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle', + 'text-anchor': 'middle', + 'font-weight': 'bold', + 'font-variant': 'small-caps', + 'text-transform': 'capitalize', + 'font-size': '14px' + } + } +}, { + markup: '', +}); + +const Input = IO.define('logic.Input', { + attrs: { + '.wire': { 'ref-dx': 0, d: 'M 0 0 L 23 0' }, + circle: { ref: '.body', 'ref-dx': 30, 'ref-y': 0.5, magnet: true, 'class': 'output', port: 'out' }, + text: { text: 'input' } + } +}); + +const Output = IO.define('logic.Output', { + attrs: { + '.wire': { 'ref-x': 0, d: 'M 0 0 L -23 0' }, + circle: { ref: '.body', 'ref-x': -30, 'ref-y': 0.5, magnet: 'passive', 'class': 'input', port: 'in' }, + text: { text: 'output' } + } +}); + +const Gate11 = Gate.define('logic.Gate11', { + attrs: { + '.input': { ref: '.body', 'ref-x': -2, 'ref-y': 0.5, magnet: 'passive', port: 'in' }, + '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' } + } +}, { + markup: '', +}); + +const Gate21 = Gate.define('logic.Gate21', { + attrs: { + '.input1': { ref: '.body', 'ref-x': -2, 'ref-y': 0.3, magnet: 'passive', port: 'in1' }, + '.input2': { ref: '.body', 'ref-x': -2, 'ref-y': 0.7, magnet: 'passive', port: 'in2' }, + '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' } + } +}, { + markup: '', +}); + +const Repeater = Gate11.define('logic.Repeater', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PVCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjgiCiAgICAgaW5rc2NhcGU6Y3g9Ijg0LjY4NTM1MiIKICAgICBpbmtzY2FwZTpjeT0iMTUuMjg4NjI4IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzIuMTU2OTEsMjUgTCA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAyOS4wNDM0NzgsMjUgTCA1LjA0MzQ3ODEsMjUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSAyOC45Njg3NSwyLjU5Mzc1IEwgMjguOTY4NzUsNSBMIDI4Ljk2ODc1LDQ1IEwgMjguOTY4NzUsNDcuNDA2MjUgTCAzMS4xMjUsNDYuMzQzNzUgTCA3Mi4xNTYyNSwyNi4zNDM3NSBMIDcyLjE1NjI1LDIzLjY1NjI1IEwgMzEuMTI1LDMuNjU2MjUgTCAyOC45Njg3NSwyLjU5Mzc1IHogTSAzMS45Njg3NSw3LjQwNjI1IEwgNjguMDkzNzUsMjUgTCAzMS45Njg3NSw0Mi41OTM3NSBMIDMxLjk2ODc1LDcuNDA2MjUgeiIKICAgICAgIGlkPSJwYXRoMjYzOCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjYyIgLz4KICA8L2c+Cjwvc3ZnPgo=' }} +}, { + operation: function(input) { + return input; + } +}); + +const Not = Gate11.define('logic.Not', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PVCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjgiCiAgICAgaW5rc2NhcGU6Y3g9Ijg0LjY4NTM1MiIKICAgICBpbmtzY2FwZTpjeT0iMTUuMjg4NjI4IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzkuMTU2OTEsMjUgTCA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAyOS4wNDM0NzgsMjUgTCA1LjA0MzQ3ODEsMjUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSAyOC45Njg3NSwyLjU5Mzc1IEwgMjguOTY4NzUsNSBMIDI4Ljk2ODc1LDQ1IEwgMjguOTY4NzUsNDcuNDA2MjUgTCAzMS4xMjUsNDYuMzQzNzUgTCA3Mi4xNTYyNSwyNi4zNDM3NSBMIDcyLjE1NjI1LDIzLjY1NjI1IEwgMzEuMTI1LDMuNjU2MjUgTCAyOC45Njg3NSwyLjU5Mzc1IHogTSAzMS45Njg3NSw3LjQwNjI1IEwgNjguMDkzNzUsMjUgTCAzMS45Njg3NSw0Mi41OTM3NSBMIDMxLjk2ODc1LDcuNDA2MjUgeiIKICAgICAgIGlkPSJwYXRoMjYzOCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDI2NzEiCiAgICAgICBzb2RpcG9kaTpjeD0iNzYiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA4MCwyNSBBIDQsNCAwIDEgMSA3MiwyNSBBIDQsNCAwIDEgMSA4MCwyNSB6IgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEsMCkiIC8+CiAgPC9nPgo8L3N2Zz4K' }} +}, { + operation: function(input) { + return !input; + } +}); + +const Or = Gate21.define('logic.Or', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik9SIEFOU0kuc3ZnIgogICBpbmtzY2FwZTpvdXRwdXRfZXh0ZW5zaW9uPSJvcmcuaW5rc2NhcGUub3V0cHV0LnN2Zy5pbmtzY2FwZSI+CiAgPGRlZnMKICAgICBpZD0iZGVmczQiPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjI1IDogMTAgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjcxNCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iMSA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMC41IDogMC4zMzMzMzMzMyA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyODA2IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyODE5IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjM3Mi4wNDcyNCA6IDM1MC43ODczOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI3NDQuMDk0NDggOiA1MjYuMTgxMDkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzc3IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49Ijc1IDogNDAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iMTUwIDogNjAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDYwIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTMyNzUiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNTAgOiAzMy4zMzMzMzMgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iMTAwIDogNTAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDUwIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTU1MzMiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzIgOiAyMS4zMzMzMzMgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNjQgOiAzMiA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMzIgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjU1NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDE2LjY2NjY2NyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDI1IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAyNSA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogIDwvZGVmcz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6em9vbT0iNCIKICAgICBpbmtzY2FwZTpjeD0iMTEzLjAwMDM5IgogICAgIGlua3NjYXBlOmN5PSIxMi44OTM3MzEiCiAgICAgaW5rc2NhcGU6ZG9jdW1lbnQtdW5pdHM9InB4IgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9ImcyNTYwIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTpncmlkLWJib3g9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1wb2ludHM9InRydWUiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAwMDAiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxMzk5IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijg3NCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMzciCiAgICAgaW5rc2NhcGU6d2luZG93LXk9Ii00IgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNzAsMjUgYyAyMCwwIDI1LDAgMjUsMCIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMSwxNSA1LDE1IgogICAgICAgaWQ9InBhdGgzMDYxIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzIsMzUgNSwzNSIKICAgICAgIGlkPSJwYXRoMzk0NCIgLz4KICAgIDxnCiAgICAgICBpZD0iZzI1NjAiCiAgICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2LjUsLTM5LjUpIj4KICAgICAgPHBhdGgKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjQwNjI1LDQ0LjUgTCAtMC40MDYyNSw0Ni45Mzc1IEMgLTAuNDA2MjUsNDYuOTM3NSA1LjI1LDUzLjkzNzU0OSA1LjI1LDY0LjUgQyA1LjI1LDc1LjA2MjQ1MSAtMC40MDYyNSw4Mi4wNjI1IC0wLjQwNjI1LDgyLjA2MjUgTCAtMi40MDYyNSw4NC41IEwgMC43NSw4NC41IEwgMTQuNzUsODQuNSBDIDE3LjE1ODA3Niw4NC41MDAwMDEgMjIuNDM5Njk5LDg0LjUyNDUxNCAyOC4zNzUsODIuMDkzNzUgQyAzNC4zMTAzMDEsNzkuNjYyOTg2IDQwLjkxMTUzNiw3NC43NTA0ODQgNDYuMDYyNSw2NS4yMTg3NSBMIDQ0Ljc1LDY0LjUgTCA0Ni4wNjI1LDYzLjc4MTI1IEMgMzUuNzU5Mzg3LDQ0LjcxNTU5IDE5LjUwNjU3NCw0NC41IDE0Ljc1LDQ0LjUgTCAwLjc1LDQ0LjUgTCAtMi40MDYyNSw0NC41IHogTSAzLjQ2ODc1LDQ3LjUgTCAxNC43NSw0Ny41IEMgMTkuNDM0MTczLDQ3LjUgMzMuMDM2ODUsNDcuMzY5NzkzIDQyLjcxODc1LDY0LjUgQyAzNy45NTE5NjQsNzIuOTI5MDc1IDMyLjE5NzQ2OSw3Ny4xODM5MSAyNyw3OS4zMTI1IEMgMjEuNjM5MzM5LDgxLjUwNzkyNCAxNy4xNTgwNzUsODEuNTAwMDAxIDE0Ljc1LDgxLjUgTCAzLjUsODEuNSBDIDUuMzczNTg4NCw3OC4zOTE1NjYgOC4yNSw3Mi40NTA2NSA4LjI1LDY0LjUgQyA4LjI1LDU2LjUyNjY0NiA1LjM0MTQ2ODYsNTAuNTk5ODE1IDMuNDY4NzUsNDcuNSB6IgogICAgICAgICBpZD0icGF0aDQ5NzMiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NzY2NjY3NjY2NjY2NjY2NzY2NzYyIgLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=' }} +}, { + operation: function(input1, input2) { + return input1 || input2; + } +}); + +const And = Gate21.define('logic.And', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IkFORCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgPC9kZWZzPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSI4IgogICAgIGlua3NjYXBlOmN4PSI1Ni42OTgzNDgiCiAgICAgaW5rc2NhcGU6Y3k9IjI1LjMyNjg5OSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDcwLDI1IGMgMjAsMCAyNSwwIDI1LDAiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEsMTUgNSwxNSIKICAgICAgIGlkPSJwYXRoMzA2MSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxLjk5OTk5OTg4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMyLDM1IDUsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTptZWRpdW07Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDt0ZXh0LWluZGVudDowO3RleHQtYWxpZ246c3RhcnQ7dGV4dC1kZWNvcmF0aW9uOm5vbmU7bGluZS1oZWlnaHQ6bm9ybWFsO2xldHRlci1zcGFjaW5nOm5vcm1hbDt3b3JkLXNwYWNpbmc6bm9ybWFsO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlyZWN0aW9uOmx0cjtibG9jay1wcm9ncmVzc2lvbjp0Yjt3cml0aW5nLW1vZGU6bHItdGI7dGV4dC1hbmNob3I6c3RhcnQ7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDozO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGU7Zm9udC1mYW1pbHk6Qml0c3RyZWFtIFZlcmEgU2FuczstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkJpdHN0cmVhbSBWZXJhIFNhbnMiCiAgICAgICBkPSJNIDMwLDUgTCAzMCw2LjQyODU3MTQgTCAzMCw0My41NzE0MjkgTCAzMCw0NSBMIDMxLjQyODU3MSw0NSBMIDUwLjQ3NjE5LDQ1IEMgNjEuNzQ0MDk4LDQ1IDcwLjQ3NjE5LDM1Ljk5OTk1NSA3MC40NzYxOSwyNSBDIDcwLjQ3NjE5LDE0LjAwMDA0NSA2MS43NDQwOTksNS4wMDAwMDAyIDUwLjQ3NjE5LDUgQyA1MC40NzYxOSw1IDUwLjQ3NjE5LDUgMzEuNDI4NTcxLDUgTCAzMCw1IHogTSAzMi44NTcxNDMsNy44NTcxNDI5IEMgNDAuODM0MjY0LDcuODU3MTQyOSA0NS45MTgzNjgsNy44NTcxNDI5IDQ4LjA5NTIzOCw3Ljg1NzE0MjkgQyA0OS4yODU3MTQsNy44NTcxNDI5IDQ5Ljg4MDk1Miw3Ljg1NzE0MjkgNTAuMTc4NTcxLDcuODU3MTQyOSBDIDUwLjMyNzM4MSw3Ljg1NzE0MjkgNTAuNDA5MjI3LDcuODU3MTQyOSA1MC40NDY0MjksNy44NTcxNDI5IEMgNTAuNDY1MDI5LDcuODU3MTQyOSA1MC40NzE1NDMsNy44NTcxNDI5IDUwLjQ3NjE5LDcuODU3MTQyOSBDIDYwLjIzNjg1Myw3Ljg1NzE0MyA2Ny4xNDI4NTcsMTUuNDk3MDk4IDY3LjE0Mjg1NywyNSBDIDY3LjE0Mjg1NywzNC41MDI5MDIgNTkuNzYwNjYyLDQyLjE0Mjg1NyA1MCw0Mi4xNDI4NTcgTCAzMi44NTcxNDMsNDIuMTQyODU3IEwgMzIuODU3MTQzLDcuODU3MTQyOSB6IgogICAgICAgaWQ9InBhdGgyODg0IgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NjY2NzY2NjY3Nzc3NzY2NjIiAvPgogIDwvZz4KPC9zdmc+Cg==' }} + +}, { + operation: function(input1, input2) { + return input1 && input2; + } +}); + +const Nor = Gate21.define('logic.Nor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PUiBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjEiCiAgICAgaW5rc2NhcGU6Y3g9Ijc4LjY3NzY0NCIKICAgICBpbmtzY2FwZTpjeT0iMjIuMTAyMzQ0IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjM3IgogICAgIGlua3NjYXBlOndpbmRvdy15PSItNCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDc5LDI1IEMgOTksMjUgOTUsMjUgOTUsMjUiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEsMTUgNSwxNSIKICAgICAgIGlkPSJwYXRoMzA2MSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxLjk5OTk5OTg4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMyLDM1IDUsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8ZwogICAgICAgaWQ9ImcyNTYwIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNi41LC0zOS41KSI+CiAgICAgIDxwYXRoCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi40MDYyNSw0NC41IEwgLTAuNDA2MjUsNDYuOTM3NSBDIC0wLjQwNjI1LDQ2LjkzNzUgNS4yNSw1My45Mzc1NDkgNS4yNSw2NC41IEMgNS4yNSw3NS4wNjI0NTEgLTAuNDA2MjUsODIuMDYyNSAtMC40MDYyNSw4Mi4wNjI1IEwgLTIuNDA2MjUsODQuNSBMIDAuNzUsODQuNSBMIDE0Ljc1LDg0LjUgQyAxNy4xNTgwNzYsODQuNTAwMDAxIDIyLjQzOTY5OSw4NC41MjQ1MTQgMjguMzc1LDgyLjA5Mzc1IEMgMzQuMzEwMzAxLDc5LjY2Mjk4NiA0MC45MTE1MzYsNzQuNzUwNDg0IDQ2LjA2MjUsNjUuMjE4NzUgTCA0NC43NSw2NC41IEwgNDYuMDYyNSw2My43ODEyNSBDIDM1Ljc1OTM4Nyw0NC43MTU1OSAxOS41MDY1NzQsNDQuNSAxNC43NSw0NC41IEwgMC43NSw0NC41IEwgLTIuNDA2MjUsNDQuNSB6IE0gMy40Njg3NSw0Ny41IEwgMTQuNzUsNDcuNSBDIDE5LjQzNDE3Myw0Ny41IDMzLjAzNjg1LDQ3LjM2OTc5MyA0Mi43MTg3NSw2NC41IEMgMzcuOTUxOTY0LDcyLjkyOTA3NSAzMi4xOTc0NjksNzcuMTgzOTEgMjcsNzkuMzEyNSBDIDIxLjYzOTMzOSw4MS41MDc5MjQgMTcuMTU4MDc1LDgxLjUwMDAwMSAxNC43NSw4MS41IEwgMy41LDgxLjUgQyA1LjM3MzU4ODQsNzguMzkxNTY2IDguMjUsNzIuNDUwNjUgOC4yNSw2NC41IEMgOC4yNSw1Ni41MjY2NDYgNS4zNDE0Njg2LDUwLjU5OTgxNSAzLjQ2ODc1LDQ3LjUgeiIKICAgICAgICAgaWQ9InBhdGg0OTczIgogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjc2NjY2NzY2NjY2NjY2Njc2Njc2MiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc3R5bGU9ImZpbGw6bm9uZTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgICBpZD0icGF0aDI2MDQiCiAgICAgICAgIHNvZGlwb2RpOmN4PSI3NSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjI1IgogICAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgICAgc29kaXBvZGk6cnk9IjQiCiAgICAgICAgIGQ9Ik0gNzksMjUgQSA0LDQgMCAxIDEgNzEsMjUgQSA0LDQgMCAxIDEgNzksMjUgeiIKICAgICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI2LjUsMzkuNSkiIC8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K' }} +}, { + operation: function(input1, input2) { + return !(input1 || input2); + } +}); + +const Nand = Gate21.define('logic.Nand', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5BTkQgQU5TSS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRlbnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxMCA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzE0IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIwLjUgOiAwLjMzMzMzMzMzIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MDYiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MTkiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzcyLjA0NzI0IDogMzUwLjc4NzM5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9Ijc0NC4wOTQ0OCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTI2LjE4MTA5IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3NzciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNzUgOiA0MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxNTAgOiA2MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNjAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMzI3NSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI1MCA6IDMzLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxMDAgOiA1MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlNTUzMyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzMiA6IDIxLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI2NCA6IDMyIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAzMiA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogIDwvZGVmcz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6em9vbT0iMTYiCiAgICAgaW5rc2NhcGU6Y3g9Ijc4LjI4MzMwNyIKICAgICBpbmtzY2FwZTpjeT0iMTYuNDQyODQzIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzksMjUgQyA5MS44LDI1IDk1LDI1IDk1LDI1IgogICAgICAgaWQ9InBhdGgzMDU5IgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMxLDE1IDUsMTUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk4ODtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMiwzNSA1LDM1IgogICAgICAgaWQ9InBhdGgzOTQ0IiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmb250LXNpemU6bWVkaXVtO2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7dGV4dC1pbmRlbnQ6MDt0ZXh0LWFsaWduOnN0YXJ0O3RleHQtZGVjb3JhdGlvbjpub25lO2xpbmUtaGVpZ2h0Om5vcm1hbDtsZXR0ZXItc3BhY2luZzpub3JtYWw7d29yZC1zcGFjaW5nOm5vcm1hbDt0ZXh0LXRyYW5zZm9ybTpub25lO2RpcmVjdGlvbjpsdHI7YmxvY2stcHJvZ3Jlc3Npb246dGI7d3JpdGluZy1tb2RlOmxyLXRiO3RleHQtYW5jaG9yOnN0YXJ0O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MzttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlO2ZvbnQtZmFtaWx5OkJpdHN0cmVhbSBWZXJhIFNhbnM7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpCaXRzdHJlYW0gVmVyYSBTYW5zIgogICAgICAgZD0iTSAzMCw1IEwgMzAsNi40Mjg1NzE0IEwgMzAsNDMuNTcxNDI5IEwgMzAsNDUgTCAzMS40Mjg1NzEsNDUgTCA1MC40NzYxOSw0NSBDIDYxLjc0NDA5OCw0NSA3MC40NzYxOSwzNS45OTk5NTUgNzAuNDc2MTksMjUgQyA3MC40NzYxOSwxNC4wMDAwNDUgNjEuNzQ0MDk5LDUuMDAwMDAwMiA1MC40NzYxOSw1IEMgNTAuNDc2MTksNSA1MC40NzYxOSw1IDMxLjQyODU3MSw1IEwgMzAsNSB6IE0gMzIuODU3MTQzLDcuODU3MTQyOSBDIDQwLjgzNDI2NCw3Ljg1NzE0MjkgNDUuOTE4MzY4LDcuODU3MTQyOSA0OC4wOTUyMzgsNy44NTcxNDI5IEMgNDkuMjg1NzE0LDcuODU3MTQyOSA0OS44ODA5NTIsNy44NTcxNDI5IDUwLjE3ODU3MSw3Ljg1NzE0MjkgQyA1MC4zMjczODEsNy44NTcxNDI5IDUwLjQwOTIyNyw3Ljg1NzE0MjkgNTAuNDQ2NDI5LDcuODU3MTQyOSBDIDUwLjQ2NTAyOSw3Ljg1NzE0MjkgNTAuNDcxNTQzLDcuODU3MTQyOSA1MC40NzYxOSw3Ljg1NzE0MjkgQyA2MC4yMzY4NTMsNy44NTcxNDMgNjcuMTQyODU3LDE1LjQ5NzA5OCA2Ny4xNDI4NTcsMjUgQyA2Ny4xNDI4NTcsMzQuNTAyOTAyIDU5Ljc2MDY2Miw0Mi4xNDI4NTcgNTAsNDIuMTQyODU3IEwgMzIuODU3MTQzLDQyLjE0Mjg1NyBMIDMyLjg1NzE0Myw3Ljg1NzE0MjkgeiIKICAgICAgIGlkPSJwYXRoMjg4NCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2Njc2NjY2Nzc3Nzc2NjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDQwMDgiCiAgICAgICBzb2RpcG9kaTpjeD0iNzUiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA3OSwyNSBBIDQsNCAwIDEgMSA3MSwyNSBBIDQsNCAwIDEgMSA3OSwyNSB6IiAvPgogIDwvZz4KPC9zdmc+Cg==' }} +}, { + operation: function(input1, input2) { + return !(input1 && input2); + } +}); + +const Xor = Gate21.define('logic.Xor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IlhPUiBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjUuNjU2ODU0MiIKICAgICBpbmtzY2FwZTpjeD0iMjUuOTM4MTE2IgogICAgIGlua3NjYXBlOmN5PSIxNy4yMzAwNSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDcwLDI1IGMgMjAsMCAyNSwwIDI1LDAiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzAuMzg1NzE3LDE1IEwgNC45OTk5OTk4LDE1IgogICAgICAgaWQ9InBhdGgzMDYxIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5NzY7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEuMzYyMDkxLDM1IEwgNC45OTk5OTk4LDM1IgogICAgICAgaWQ9InBhdGgzOTQ0IiAvPgogICAgPGcKICAgICAgIGlkPSJnMjU2MCIKICAgICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYuNSwtMzkuNSkiPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDM1MTYiCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi4yNSw4MS41MDAwMDUgQyAtMy44NDczNzQsODQuMTQ0NDA1IC00LjUsODQuNTAwMDA1IC00LjUsODQuNTAwMDA1IEwgLTguMTU2MjUsODQuNTAwMDA1IEwgLTYuMTU2MjUsODIuMDYyNTA1IEMgLTYuMTU2MjUsODIuMDYyNTA1IC0wLjUsNzUuMDYyNDUxIC0wLjUsNjQuNSBDIC0wLjUsNTMuOTM3NTQ5IC02LjE1NjI1LDQ2LjkzNzUgLTYuMTU2MjUsNDYuOTM3NSBMIC04LjE1NjI1LDQ0LjUgTCAtNC41LDQ0LjUgQyAtMy43MTg3NSw0NS40Mzc1IC0zLjA3ODEyNSw0Ni4xNTYyNSAtMi4yODEyNSw0Ny41IEMgLTAuNDA4NTMxLDUwLjU5OTgxNSAyLjUsNTYuNTI2NjQ2IDIuNSw2NC41IEMgMi41LDcyLjQ1MDY1IC0wLjM5NjY5Nyw3OC4zNzk0MjUgLTIuMjUsODEuNTAwMDA1IHoiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY3NjY2Njc2MiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi40MDYyNSw0NC41IEwgLTAuNDA2MjUsNDYuOTM3NSBDIC0wLjQwNjI1LDQ2LjkzNzUgNS4yNSw1My45Mzc1NDkgNS4yNSw2NC41IEMgNS4yNSw3NS4wNjI0NTEgLTAuNDA2MjUsODIuMDYyNSAtMC40MDYyNSw4Mi4wNjI1IEwgLTIuNDA2MjUsODQuNSBMIDAuNzUsODQuNSBMIDE0Ljc1LDg0LjUgQyAxNy4xNTgwNzYsODQuNTAwMDAxIDIyLjQzOTY5OSw4NC41MjQ1MTQgMjguMzc1LDgyLjA5Mzc1IEMgMzQuMzEwMzAxLDc5LjY2Mjk4NiA0MC45MTE1MzYsNzQuNzUwNDg0IDQ2LjA2MjUsNjUuMjE4NzUgTCA0NC43NSw2NC41IEwgNDYuMDYyNSw2My43ODEyNSBDIDM1Ljc1OTM4Nyw0NC43MTU1OSAxOS41MDY1NzQsNDQuNSAxNC43NSw0NC41IEwgMC43NSw0NC41IEwgLTIuNDA2MjUsNDQuNSB6IE0gMy40Njg3NSw0Ny41IEwgMTQuNzUsNDcuNSBDIDE5LjQzNDE3Myw0Ny41IDMzLjAzNjg1LDQ3LjM2OTc5MyA0Mi43MTg3NSw2NC41IEMgMzcuOTUxOTY0LDcyLjkyOTA3NSAzMi4xOTc0NjksNzcuMTgzOTEgMjcsNzkuMzEyNSBDIDIxLjYzOTMzOSw4MS41MDc5MjQgMTcuMTU4MDc1LDgxLjUwMDAwMSAxNC43NSw4MS41IEwgMy41LDgxLjUgQyA1LjM3MzU4ODQsNzguMzkxNTY2IDguMjUsNzIuNDUwNjUgOC4yNSw2NC41IEMgOC4yNSw1Ni41MjY2NDYgNS4zNDE0Njg2LDUwLjU5OTgxNSAzLjQ2ODc1LDQ3LjUgeiIKICAgICAgICAgaWQ9InBhdGg0OTczIgogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjc2NjY2NzY2NjY2NjY2Njc2Njc2MiIC8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K' }} +}, { + operation: function(input1, input2) { + return (!input1 || input2) && (input1 || !input2); + } +}); + +const Xnor = Gate21.define('logic.Xnor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IlhOT1IgQU5TSS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRlbnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxMCA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzE0IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIwLjUgOiAwLjMzMzMzMzMzIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MDYiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MTkiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzcyLjA0NzI0IDogMzUwLjc4NzM5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9Ijc0NC4wOTQ0OCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTI2LjE4MTA5IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3NzciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNzUgOiA0MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxNTAgOiA2MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNjAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMzI3NSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI1MCA6IDMzLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxMDAgOiA1MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlNTUzMyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzMiA6IDIxLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI2NCA6IDMyIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAzMiA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNTU3IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjI1IDogMTYuNjY2NjY3IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUwIDogMjUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDI1IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgPC9kZWZzPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSI0IgogICAgIGlua3NjYXBlOmN4PSI5NS43MjM2NiIKICAgICBpbmtzY2FwZTpjeT0iLTI2Ljc3NTAyMyIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyLjAwMDAwMDI0O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDc4LjMzMzMzMiwyNSBDIDkxLjY2NjY2NiwyNSA5NSwyNSA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk4ODtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMC4zODU3MTcsMTUgTCA0Ljk5OTk5OTgsMTUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk3NjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMS4zNjIwOTEsMzUgTCA0Ljk5OTk5OTgsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8ZwogICAgICAgaWQ9ImcyNTYwIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNi41LC0zOS41KSI+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMzUxNiIKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjI1LDgxLjUwMDAwNSBDIC0zLjg0NzM3NCw4NC4xNDQ0MDUgLTQuNSw4NC41MDAwMDUgLTQuNSw4NC41MDAwMDUgTCAtOC4xNTYyNSw4NC41MDAwMDUgTCAtNi4xNTYyNSw4Mi4wNjI1MDUgQyAtNi4xNTYyNSw4Mi4wNjI1MDUgLTAuNSw3NS4wNjI0NTEgLTAuNSw2NC41IEMgLTAuNSw1My45Mzc1NDkgLTYuMTU2MjUsNDYuOTM3NSAtNi4xNTYyNSw0Ni45Mzc1IEwgLTguMTU2MjUsNDQuNSBMIC00LjUsNDQuNSBDIC0zLjcxODc1LDQ1LjQzNzUgLTMuMDc4MTI1LDQ2LjE1NjI1IC0yLjI4MTI1LDQ3LjUgQyAtMC40MDg1MzEsNTAuNTk5ODE1IDIuNSw1Ni41MjY2NDYgMi41LDY0LjUgQyAyLjUsNzIuNDUwNjUgLTAuMzk2Njk3LDc4LjM3OTQyNSAtMi4yNSw4MS41MDAwMDUgeiIKICAgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2Njc2NjY2NzYyIgLz4KICAgICAgPHBhdGgKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjQwNjI1LDQ0LjUgTCAtMC40MDYyNSw0Ni45Mzc1IEMgLTAuNDA2MjUsNDYuOTM3NSA1LjI1LDUzLjkzNzU0OSA1LjI1LDY0LjUgQyA1LjI1LDc1LjA2MjQ1MSAtMC40MDYyNSw4Mi4wNjI1IC0wLjQwNjI1LDgyLjA2MjUgTCAtMi40MDYyNSw4NC41IEwgMC43NSw4NC41IEwgMTQuNzUsODQuNSBDIDE3LjE1ODA3Niw4NC41MDAwMDEgMjIuNDM5Njk5LDg0LjUyNDUxNCAyOC4zNzUsODIuMDkzNzUgQyAzNC4zMTAzMDEsNzkuNjYyOTg2IDQwLjkxMTUzNiw3NC43NTA0ODQgNDYuMDYyNSw2NS4yMTg3NSBMIDQ0Ljc1LDY0LjUgTCA0Ni4wNjI1LDYzLjc4MTI1IEMgMzUuNzU5Mzg3LDQ0LjcxNTU5IDE5LjUwNjU3NCw0NC41IDE0Ljc1LDQ0LjUgTCAwLjc1LDQ0LjUgTCAtMi40MDYyNSw0NC41IHogTSAzLjQ2ODc1LDQ3LjUgTCAxNC43NSw0Ny41IEMgMTkuNDM0MTczLDQ3LjUgMzMuMDM2ODUsNDcuMzY5NzkzIDQyLjcxODc1LDY0LjUgQyAzNy45NTE5NjQsNzIuOTI5MDc1IDMyLjE5NzQ2OSw3Ny4xODM5MSAyNyw3OS4zMTI1IEMgMjEuNjM5MzM5LDgxLjUwNzkyNCAxNy4xNTgwNzUsODEuNTAwMDAxIDE0Ljc1LDgxLjUgTCAzLjUsODEuNSBDIDUuMzczNTg4NCw3OC4zOTE1NjYgOC4yNSw3Mi40NTA2NSA4LjI1LDY0LjUgQyA4LjI1LDU2LjUyNjY0NiA1LjM0MTQ2ODYsNTAuNTk5ODE1IDMuNDY4NzUsNDcuNSB6IgogICAgICAgICBpZD0icGF0aDQ5NzMiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NzY2NjY3NjY2NjY2NjY2NzY2NzYyIgLz4KICAgIDwvZz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDM1NTEiCiAgICAgICBzb2RpcG9kaTpjeD0iNzUiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA3OSwyNSBBIDQsNCAwIDEgMSA3MSwyNSBBIDQsNCAwIDEgMSA3OSwyNSB6IiAvPgogIDwvZz4KPC9zdmc+Cg==' }} +}, { + operation: function(input1, input2) { + return (!input1 || !input2) && (input1 || input2); + } +}); + +const Wire = Link.define('logic.Wire', { + attrs: { + '.connection': { 'stroke-width': 2 }, + '.marker-vertex': { r: 7 } + }, + + router: { name: 'orthogonal' }, + connector: { name: 'rounded', args: { radius: 10 }} +}, { + arrowheadMarkup: [ + '', + '', + '' + ].join(''), + + vertexMarkup: [ + '', + '', + '', + '', + '', + 'Remove vertex.', + '', + '', + '' + ].join('') +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/chess.mjs + + +const KingWhite = Generic.define('chess.KingWhite', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +const KingBlack = Generic.define('chess.KingBlack', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +const QueenWhite = Generic.define('chess.QueenWhite', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +const QueenBlack = Generic.define('chess.QueenBlack', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +const RookWhite = Generic.define('chess.RookWhite', { + size: { width: 32, height: 34 } +}, { + markup: ' ' +}); + +const RookBlack = Generic.define('chess.RookBlack', { + size: { width: 32, height: 34 } +}, { + markup: ' ' +}); + +const BishopWhite = Generic.define('chess.BishopWhite', { + size: { width: 38, height: 38 } +}, { + markup: ' ' +}); + +const BishopBlack = Generic.define('chess.BishopBlack', { + size: { width: 38, height: 38 } +}, { + markup: ' ' +}); + +const KnightWhite = Generic.define('chess.KnightWhite', { + size: { width: 38, height: 37 } +}, { + markup: ' ' +}); + +const KnightBlack = Generic.define('chess.KnightBlack', { + size: { width: 38, height: 37 } +}, { + markup: ' ' +}); + +const PawnWhite = Generic.define('chess.PawnWhite', { + size: { width: 28, height: 33 } +}, { + markup: '' +}); + +const PawnBlack = Generic.define('chess.PawnBlack', { + size: { width: 28, height: 33 } +}, { + markup: '' +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/erd.mjs + + + +const Entity = Element_Element.define('erd.Entity', { + size: { width: 150, height: 60 }, + attrs: { + '.outer': { + fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2, + points: '100,0 100,60 0,60 0,0' + }, + '.inner': { + fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2, + points: '95,5 95,55 5,55 5,5', + display: 'none' + }, + text: { + text: 'Entity', + 'font-family': 'Arial', 'font-size': 14, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +const WeakEntity = Entity.define('erd.WeakEntity', { + attrs: { + '.inner': { display: 'auto' }, + text: { text: 'Weak Entity' } + } +}); + +const Relationship = Element_Element.define('erd.Relationship', { + size: { width: 80, height: 80 }, + attrs: { + '.outer': { + fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2, + points: '40,0 80,40 40,80 0,40' + }, + '.inner': { + fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2, + points: '40,5 75,40 40,75 5,40', + display: 'none' + }, + text: { + text: 'Relationship', + 'font-family': 'Arial', 'font-size': 12, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +const IdentifyingRelationship = Relationship.define('erd.IdentifyingRelationship', { + attrs: { + '.inner': { display: 'auto' }, + text: { text: 'Identifying' } + } +}); + +const Attribute = Element_Element.define('erd.Attribute', { + size: { width: 100, height: 50 }, + attrs: { + 'ellipse': { + transform: 'translate(50, 25)' + }, + '.outer': { + stroke: '#D35400', 'stroke-width': 2, + cx: 0, cy: 0, rx: 50, ry: 25, + fill: '#E67E22' + }, + '.inner': { + stroke: '#D35400', 'stroke-width': 2, + cx: 0, cy: 0, rx: 45, ry: 20, + fill: '#E67E22', display: 'none' + }, + text: { + 'font-family': 'Arial', 'font-size': 14, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +const Multivalued = Attribute.define('erd.Multivalued', { + attrs: { + '.inner': { display: 'block' }, + text: { text: 'multivalued' } + } +}); + +const Derived = Attribute.define('erd.Derived', { + attrs: { + '.outer': { 'stroke-dasharray': '3,5' }, + text: { text: 'derived' } + } +}); + +const Key = Attribute.define('erd.Key', { + attrs: { + ellipse: { 'stroke-width': 4 }, + text: { text: 'key', 'font-weight': '800', 'text-decoration': 'underline' } + } +}); + +const Normal = Attribute.define('erd.Normal', { + attrs: { text: { text: 'Normal' }} +}); + +const ISA = Element_Element.define('erd.ISA', { + type: 'erd.ISA', + size: { width: 100, height: 50 }, + attrs: { + polygon: { + points: '0,0 50,50 100,0', + fill: '#F1C40F', stroke: '#F39C12', 'stroke-width': 2 + }, + text: { + text: 'ISA', 'font-size': 18, + 'ref-x': .5, 'ref-y': .3, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +const erd_Line = Link.define('erd.Line', {}, { + cardinality: function(value) { + this.set('labels', [{ position: -20, attrs: { text: { dy: -8, text: value }}}]); + } +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/fsa.mjs + + + + +const State = Circle.define('fsa.State', { + attrs: { + circle: { 'stroke-width': 3 }, + text: { 'font-weight': '800' } + } +}); + +const StartState = Element_Element.define('fsa.StartState', { + size: { width: 20, height: 20 }, + attrs: { + circle: { + transform: 'translate(10, 10)', + r: 10, + fill: '#000000' + } + } +}, { + markup: '', +}); + +const EndState = Element_Element.define('fsa.EndState', { + size: { width: 20, height: 20 }, + attrs: { + '.outer': { + transform: 'translate(10, 10)', + r: 10, + fill: '#ffffff', + stroke: '#000000' + }, + + '.inner': { + transform: 'translate(10, 10)', + r: 6, + fill: '#000000' + } + } +}, { + markup: '', +}); + +const Arrow = Link.define('fsa.Arrow', { + attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' }}, + smooth: true +}); + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/org.mjs + + + +const Member = Element_Element.define('org.Member', { + size: { width: 180, height: 70 }, + attrs: { + rect: { width: 170, height: 60 }, + + '.card': { + fill: '#FFFFFF', stroke: '#000000', 'stroke-width': 2, + 'pointer-events': 'visiblePainted', rx: 10, ry: 10 + }, + + image: { + width: 48, height: 48, + ref: '.card', 'ref-x': 10, 'ref-y': 5 + }, + + '.rank': { + 'text-decoration': 'underline', + ref: '.card', 'ref-x': 0.9, 'ref-y': 0.2, + 'font-family': 'Courier New', 'font-size': 14, + 'text-anchor': 'end' + }, + + '.name': { + 'font-weight': '800', + ref: '.card', 'ref-x': 0.9, 'ref-y': 0.6, + 'font-family': 'Courier New', 'font-size': 14, + 'text-anchor': 'end' + } + } +}, { + markup: '', +}); + +const org_Arrow = Link.define('org.Arrow', { + source: { selector: '.card' }, target: { selector: '.card' }, + attrs: { '.connection': { stroke: '#585858', 'stroke-width': 3 }}, + z: -1 +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/pn.mjs + + + + + +const Place = Generic.define('pn.Place', { + size: { width: 50, height: 50 }, + attrs: { + '.root': { + r: 25, + fill: '#ffffff', + stroke: '#000000', + transform: 'translate(25, 25)' + }, + '.label': { + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': -20, + ref: '.root', + fill: '#000000', + 'font-size': 12 + }, + '.tokens > circle': { + fill: '#000000', + r: 5 + }, + '.tokens.one > circle': { transform: 'translate(25, 25)' }, + + '.tokens.two > circle:nth-child(1)': { transform: 'translate(19, 25)' }, + '.tokens.two > circle:nth-child(2)': { transform: 'translate(31, 25)' }, + + '.tokens.three > circle:nth-child(1)': { transform: 'translate(18, 29)' }, + '.tokens.three > circle:nth-child(2)': { transform: 'translate(25, 19)' }, + '.tokens.three > circle:nth-child(3)': { transform: 'translate(32, 29)' }, + + '.tokens.alot > text': { + transform: 'translate(25, 18)', + 'text-anchor': 'middle', + fill: '#000000' + } + } +}, { + markup: '', +}); + +const PlaceView = ElementView.extend({ + + presentationAttributes: ElementView.addPresentationAttributes({ + tokens: ['TOKENS'] + }), + + initFlag: ElementView.prototype.initFlag.concat(['TOKENS']), + + confirmUpdate: function(...args) { + let flags = ElementView.prototype.confirmUpdate.call(this, ...args); + if (this.hasFlag(flags, 'TOKENS')) { + this.renderTokens(); + this.update(); + flags = this.removeFlag(flags, 'TOKENS'); + } + return flags; + }, + + renderTokens: function() { + + const vTokens = this.vel.findOne('.tokens').empty(); + ['one', 'two', 'three', 'alot'].forEach(function(className) { + vTokens.removeClass(className); + }); + + var tokens = this.model.get('tokens'); + if (!tokens) return; + + switch (tokens) { + + case 1: + vTokens.addClass('one'); + vTokens.append(src_V('circle')); + break; + + case 2: + vTokens.addClass('two'); + vTokens.append([src_V('circle'), src_V('circle')]); + break; + + case 3: + vTokens.addClass('three'); + vTokens.append([src_V('circle'), src_V('circle'), src_V('circle')]); + break; + + default: + vTokens.addClass('alot'); + vTokens.append(src_V('text').text(tokens + '')); + break; + } + } +}); + +const Transition = Generic.define('pn.Transition', { + size: { width: 12, height: 50 }, + attrs: { + 'rect': { + width: 12, + height: 50, + fill: '#000000', + stroke: '#000000' + }, + '.label': { + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': -20, + ref: 'rect', + fill: '#000000', + 'font-size': 12 + } + } +}, { + markup: '', +}); + +const pn_Link = Link.define('pn.Link', { + attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' }} +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/uml.mjs + + + + +const Class = Generic.define('uml.Class', { + attrs: { + rect: { 'width': 200 }, + + '.uml-class-name-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#3498db' }, + '.uml-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' }, + '.uml-class-methods-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' }, + + '.uml-class-name-text': { + 'ref': '.uml-class-name-rect', + 'ref-y': .5, + 'ref-x': .5, + 'text-anchor': 'middle', + 'y-alignment': 'middle', + 'font-weight': 'bold', + 'fill': 'black', + 'font-size': 12, + 'font-family': 'Times New Roman' + }, + '.uml-class-attrs-text': { + 'ref': '.uml-class-attrs-rect', 'ref-y': 5, 'ref-x': 5, + 'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman' + }, + '.uml-class-methods-text': { + 'ref': '.uml-class-methods-rect', 'ref-y': 5, 'ref-x': 5, + 'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman' + } + }, + + name: [], + attributes: [], + methods: [] +}, { + markup: [ + '', + '', + '', + '', + '', + '' + ].join(''), + + initialize: function() { + + this.on('change:name change:attributes change:methods', function() { + this.updateRectangles(); + this.trigger('uml-update'); + }, this); + + this.updateRectangles(); + + Generic.prototype.initialize.apply(this, arguments); + }, + + getClassName: function() { + return this.get('name'); + }, + + updateRectangles: function() { + + var attrs = this.get('attrs'); + + var rects = [ + { type: 'name', text: this.getClassName() }, + { type: 'attrs', text: this.get('attributes') }, + { type: 'methods', text: this.get('methods') } + ]; + + var offsetY = 0; + + rects.forEach(function(rect) { + + var lines = Array.isArray(rect.text) ? rect.text : [rect.text]; + var rectHeight = lines.length * 20 + 20; + + attrs['.uml-class-' + rect.type + '-text'].text = lines.join('\n'); + attrs['.uml-class-' + rect.type + '-rect'].height = rectHeight; + attrs['.uml-class-' + rect.type + '-rect'].transform = 'translate(0,' + offsetY + ')'; + + offsetY += rectHeight; + }); + } + +}); + +const ClassView = ElementView.extend({ + + initialize: function() { + + ElementView.prototype.initialize.apply(this, arguments); + + this.listenTo(this.model, 'uml-update', function() { + this.update(); + this.resize(); + }); + } +}); + +const Abstract = Class.define('uml.Abstract', { + attrs: { + '.uml-class-name-rect': { fill: '#e74c3c' }, + '.uml-class-attrs-rect': { fill: '#c0392b' }, + '.uml-class-methods-rect': { fill: '#c0392b' } + } +}, { + + getClassName: function() { + return ['<>', this.get('name')]; + } + +}); +const AbstractView = ClassView; + +const Interface = Class.define('uml.Interface', { + attrs: { + '.uml-class-name-rect': { fill: '#f1c40f' }, + '.uml-class-attrs-rect': { fill: '#f39c12' }, + '.uml-class-methods-rect': { fill: '#f39c12' } + } +}, { + getClassName: function() { + return ['<>', this.get('name')]; + } +}); +const InterfaceView = ClassView; + +const Generalization = Link.define('uml.Generalization', { + attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }} +}); + +const Implementation = Link.define('uml.Implementation', { + attrs: { + '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }, + '.connection': { 'stroke-dasharray': '3,3' } + } +}); + +const Aggregation = Link.define('uml.Aggregation', { + attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'white' }} +}); + +const Composition = Link.define('uml.Composition', { + attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'black' }} +}); + +const Association = Link.define('uml.Association'); + +// Statechart + +const uml_State = Generic.define('uml.State', { + attrs: { + '.uml-state-body': { + 'width': 200, 'height': 200, 'rx': 10, 'ry': 10, + 'fill': '#ecf0f1', 'stroke': '#bdc3c7', 'stroke-width': 3 + }, + '.uml-state-separator': { + 'stroke': '#bdc3c7', 'stroke-width': 2 + }, + '.uml-state-name': { + 'ref': '.uml-state-body', 'ref-x': .5, 'ref-y': 5, 'text-anchor': 'middle', + 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 + }, + '.uml-state-events': { + 'ref': '.uml-state-separator', 'ref-x': 5, 'ref-y': 5, + 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 + } + }, + + name: 'State', + events: [] + +}, { + markup: [ + '', + '', + '', + '', + '', + '', + '', + '' + ].join(''), + + initialize: function() { + + this.on({ + 'change:name': this.updateName, + 'change:events': this.updateEvents, + 'change:size': this.updatePath + }, this); + + this.updateName(); + this.updateEvents(); + this.updatePath(); + + Generic.prototype.initialize.apply(this, arguments); + }, + + updateName: function() { + + this.attr('.uml-state-name/text', this.get('name')); + }, + + updateEvents: function() { + + this.attr('.uml-state-events/text', this.get('events').join('\n')); + }, + + updatePath: function() { + + var d = 'M 0 20 L ' + this.get('size').width + ' 20'; + + // We are using `silent: true` here because updatePath() is meant to be called + // on resize and there's no need to to update the element twice (`change:size` + // triggers also an update). + this.attr('.uml-state-separator/d', d, { silent: true }); + } +}); + +const uml_StartState = Circle.define('uml.StartState', { + type: 'uml.StartState', + attrs: { circle: { 'fill': '#34495e', 'stroke': '#2c3e50', 'stroke-width': 2, 'rx': 1 }} +}); + +const uml_EndState = Generic.define('uml.EndState', { + size: { width: 20, height: 20 }, + attrs: { + 'circle.outer': { + transform: 'translate(10, 10)', + r: 10, + fill: '#ffffff', + stroke: '#2c3e50' + }, + + 'circle.inner': { + transform: 'translate(10, 10)', + r: 6, + fill: '#34495e' + } + } +}, { + markup: '', +}); + +const uml_Transition = Link.define('uml.Transition', { + attrs: { + '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: '#34495e', stroke: '#2c3e50' }, + '.connection': { stroke: '#2c3e50' } + } +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/shapes/index.mjs + + + + + + + + + + + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/core.mjs + + + + + + + + + + + + + + + + + + + +const Vectorizer = src_V; +const layout = { PortLabel: portLabel_namespaceObject, Port: port_namespaceObject }; + + +const setTheme = function(theme, opt) { + + opt = opt || {}; + + invoke(views, 'setTheme', theme, opt); + + // Update the default theme on the view prototype. + View.prototype.defaultTheme = theme; +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/linkAnchors/index.mjs + + + +function connectionRatio(view, _magnet, _refPoint, opt) { + + var ratio = ('ratio' in opt) ? opt.ratio : 0.5; + return view.getPointAtRatio(ratio); +} + +function connectionLength(view, _magnet, _refPoint, opt) { + + var length = ('length' in opt) ? opt.length : 20; + return view.getPointAtLength(length); +} + +function _connectionPerpendicular(view, _magnet, refPoint, opt) { + + var OFFSET = 1e6; + var path = view.getConnection(); + var segmentSubdivisions = view.getConnectionSubdivisions(); + var verticalLine = new Line(refPoint.clone().offset(0, OFFSET), refPoint.clone().offset(0, -OFFSET)); + var horizontalLine = new Line(refPoint.clone().offset(OFFSET, 0), refPoint.clone().offset(-OFFSET, 0)); + var verticalIntersections = verticalLine.intersect(path, { segmentSubdivisions: segmentSubdivisions }); + var horizontalIntersections = horizontalLine.intersect(path, { segmentSubdivisions: segmentSubdivisions }); + var intersections = []; + if (verticalIntersections) Array.prototype.push.apply(intersections, verticalIntersections); + if (horizontalIntersections) Array.prototype.push.apply(intersections, horizontalIntersections); + if (intersections.length > 0) return refPoint.chooseClosest(intersections); + if ('fallbackAt' in opt) { + return getPointAtLink(view, opt.fallbackAt); + } + return connectionClosest(view, _magnet, refPoint, opt); +} + +function _connectionClosest(view, _magnet, refPoint, _opt) { + + var closestPoint = view.getClosestPoint(refPoint); + if (!closestPoint) return new Point(); + return closestPoint; +} + +function resolveRef(fn) { + return function(view, magnet, ref, opt) { + if (ref instanceof Element) { + var refView = this.paper.findView(ref); + var refPoint; + if (refView) { + if (refView.isNodeConnection(ref)) { + var distance = ('fixedAt' in opt) ? opt.fixedAt : '50%'; + refPoint = getPointAtLink(refView, distance); + } else { + refPoint = refView.getNodeBBox(ref).center(); + } + } else { + // Something went wrong + refPoint = new Point(); + } + return fn.call(this, view, magnet, refPoint, opt); + } + return fn.apply(this, arguments); + }; +} + +function getPointAtLink(view, value) { + var parsedValue = parseFloat(value); + if (isPercentage(value)) { + return view.getPointAtRatio(parsedValue / 100); + } else { + return view.getPointAtLength(parsedValue); + } +} + +// joint.linkAnchors + +const connectionPerpendicular = resolveRef(_connectionPerpendicular); +const connectionClosest = resolveRef(_connectionClosest); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/anchors/index.mjs + + + + +function bboxWrapper(method) { + + return function(view, magnet, ref, opt) { + + var rotate = !!opt.rotate; + var bbox = (rotate) ? view.getNodeUnrotatedBBox(magnet) : view.getNodeBBox(magnet); + var anchor = bbox[method](); + + var dx = opt.dx; + if (dx) { + var dxPercentage = isPercentage(dx); + dx = parseFloat(dx); + if (isFinite(dx)) { + if (dxPercentage) { + dx /= 100; + dx *= bbox.width; + } + anchor.x += dx; + } + } + + var dy = opt.dy; + if (dy) { + var dyPercentage = isPercentage(dy); + dy = parseFloat(dy); + if (isFinite(dy)) { + if (dyPercentage) { + dy /= 100; + dy *= bbox.height; + } + anchor.y += dy; + } + } + + return (rotate) ? anchor.rotate(view.model.getBBox().center(), -view.model.angle()) : anchor; + }; +} + +function _perpendicular(view, magnet, refPoint, opt) { + + var angle = view.model.angle(); + var bbox = view.getNodeBBox(magnet); + var anchor = bbox.center(); + var topLeft = bbox.origin(); + var bottomRight = bbox.corner(); + + var padding = opt.padding; + if (!isFinite(padding)) padding = 0; + + if ((topLeft.y + padding) <= refPoint.y && refPoint.y <= (bottomRight.y - padding)) { + var dy = (refPoint.y - anchor.y); + anchor.x += (angle === 0 || angle === 180) ? 0 : dy * 1 / Math.tan(toRad(angle)); + anchor.y += dy; + } else if ((topLeft.x + padding) <= refPoint.x && refPoint.x <= (bottomRight.x - padding)) { + var dx = (refPoint.x - anchor.x); + anchor.y += (angle === 90 || angle === 270) ? 0 : dx * Math.tan(toRad(angle)); + anchor.x += dx; + } + + return anchor; +} + +function _midSide(view, magnet, refPoint, opt) { + + var rotate = !!opt.rotate; + var bbox, angle, center; + if (rotate) { + bbox = view.getNodeUnrotatedBBox(magnet); + center = view.model.getBBox().center(); + angle = view.model.angle(); + } else { + bbox = view.getNodeBBox(magnet); + } + + var padding = opt.padding; + if (isFinite(padding)) bbox.inflate(padding); + + if (rotate) refPoint.rotate(center, angle); + + var side = bbox.sideNearestToPoint(refPoint); + var anchor; + switch (side) { + case 'left': + anchor = bbox.leftMiddle(); + break; + case 'right': + anchor = bbox.rightMiddle(); + break; + case 'top': + anchor = bbox.topMiddle(); + break; + case 'bottom': + anchor = bbox.bottomMiddle(); + break; + } + + return (rotate) ? anchor.rotate(center, -angle) : anchor; +} + +// Can find anchor from model, when there is no selector or the link end +// is connected to a port +function _modelCenter(view, _magnet, _refPoint, opt, endType) { + return view.model.getPointFromConnectedLink(this.model, endType).offset(opt.dx, opt.dy); +} + +//joint.anchors +const center = bboxWrapper('center'); +const anchors_top = bboxWrapper('topMiddle'); +const anchors_bottom = bboxWrapper('bottomMiddle'); +const anchors_left = bboxWrapper('leftMiddle'); +const anchors_right = bboxWrapper('rightMiddle'); +const topLeft = bboxWrapper('origin'); +const topRight = bboxWrapper('topRight'); +const bottomLeft = bboxWrapper('bottomLeft'); +const bottomRight = bboxWrapper('corner'); +const perpendicular = resolveRef(_perpendicular); +const midSide = resolveRef(_midSide); +const modelCenter = _modelCenter; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectionPoints/index.mjs + + + + +function offsetPoint(p1, p2, offset) { + if (isPlainObject(offset)) { + const { x, y } = offset; + if (isFinite(y)) { + const line = new Line(p2, p1); + const { start, end } = line.parallel(y); + p2 = start; + p1 = end; + } + offset = x; + } + if (!isFinite(offset)) return p1; + var length = p1.distance(p2); + if (offset === 0 && length > 0) return p1; + return p1.move(p2, -Math.min(offset, length - 1)); +} + +function stroke(magnet) { + + var stroke = magnet.getAttribute('stroke-width'); + if (stroke === null) return 0; + return parseFloat(stroke) || 0; +} + +function alignLine(line, type, offset = 0) { + let coordinate, a, b, direction; + const { start, end } = line; + switch (type) { + case 'left': + coordinate = 'x'; + a = end; + b = start; + direction = -1; + break; + case 'right': + coordinate = 'x'; + a = start; + b = end; + direction = 1; + break; + case 'top': + coordinate = 'y'; + a = end; + b = start; + direction = -1; + break; + case 'bottom': + coordinate = 'y'; + a = start; + b = end; + direction = 1; + break; + default: + return; + } + if (start[coordinate] < end[coordinate]) { + a[coordinate] = b[coordinate]; + } else { + b[coordinate] = a[coordinate]; + } + if (isFinite(offset)) { + a[coordinate] += direction * offset; + b[coordinate] += direction * offset; + } +} + +// Connection Points + +function anchorConnectionPoint(line, _view, _magnet, opt) { + let { offset, alignOffset, align } = opt; + if (align) alignLine(line, align, alignOffset); + return offsetPoint(line.end, line.start, offset); +} + +function bboxIntersection(line, view, magnet, opt) { + + var bbox = view.getNodeBBox(magnet); + if (opt.stroke) bbox.inflate(stroke(magnet) / 2); + var intersections = line.intersect(bbox); + var cp = (intersections) + ? line.start.chooseClosest(intersections) + : line.end; + return offsetPoint(cp, line.start, opt.offset); +} + +function rectangleIntersection(line, view, magnet, opt) { + + var angle = view.model.angle(); + if (angle === 0) { + return bboxIntersection(line, view, magnet, opt); + } + + var bboxWORotation = view.getNodeUnrotatedBBox(magnet); + if (opt.stroke) bboxWORotation.inflate(stroke(magnet) / 2); + var center = bboxWORotation.center(); + var lineWORotation = line.clone().rotate(center, angle); + var intersections = lineWORotation.setLength(1e6).intersect(bboxWORotation); + var cp = (intersections) + ? lineWORotation.start.chooseClosest(intersections).rotate(center, -angle) + : line.end; + return offsetPoint(cp, line.start, opt.offset); +} + +function findShapeNode(magnet) { + if (!magnet) return null; + var node = magnet; + do { + var tagName = node.tagName; + if (typeof tagName !== 'string') return null; + tagName = tagName.toUpperCase(); + if (tagName === 'G') { + node = node.firstElementChild; + } else if (tagName === 'TITLE') { + node = node.nextElementSibling; + } else break; + } while (node); + return node; +} + +var BNDR_SUBDIVISIONS = 'segmentSubdivisons'; +var BNDR_SHAPE_BBOX = 'shapeBBox'; + +function boundaryIntersection(line, view, magnet, opt) { + + var node, intersection; + var selector = opt.selector; + var anchor = line.end; + + if (typeof selector === 'string') { + node = view.findBySelector(selector)[0]; + } else if (selector === false) { + node = magnet; + } else if (Array.isArray(selector)) { + node = getByPath(magnet, selector); + } else { + node = findShapeNode(magnet); + } + + if (!src_V.isSVGGraphicsElement(node)) { + if (node === magnet || !src_V.isSVGGraphicsElement(magnet)) return anchor; + node = magnet; + } + + var localShape = view.getNodeShape(node); + var magnetMatrix = view.getNodeMatrix(node); + var translateMatrix = view.getRootTranslateMatrix(); + var rotateMatrix = view.getRootRotateMatrix(); + var targetMatrix = translateMatrix.multiply(rotateMatrix).multiply(magnetMatrix); + var localMatrix = targetMatrix.inverse(); + var localLine = src_V.transformLine(line, localMatrix); + var localRef = localLine.start.clone(); + var data = view.getNodeData(node); + + if (opt.insideout === false) { + if (!data[BNDR_SHAPE_BBOX]) data[BNDR_SHAPE_BBOX] = localShape.bbox(); + var localBBox = data[BNDR_SHAPE_BBOX]; + if (localBBox.containsPoint(localRef)) return anchor; + } + + // Caching segment subdivisions for paths + var pathOpt; + if (localShape instanceof Path) { + var precision = opt.precision || 2; + if (!data[BNDR_SUBDIVISIONS]) data[BNDR_SUBDIVISIONS] = localShape.getSegmentSubdivisions({ precision: precision }); + pathOpt = { + precision: precision, + segmentSubdivisions: data[BNDR_SUBDIVISIONS] + }; + } + + if (opt.extrapolate === true) localLine.setLength(1e6); + + intersection = localLine.intersect(localShape, pathOpt); + if (intersection) { + // More than one intersection + if (src_V.isArray(intersection)) intersection = localRef.chooseClosest(intersection); + } else if (opt.sticky === true) { + // No intersection, find the closest point instead + if (localShape instanceof Rect) { + intersection = localShape.pointNearestToPoint(localRef); + } else if (localShape instanceof Ellipse) { + intersection = localShape.intersectionWithLineFromCenterToPoint(localRef); + } else { + intersection = localShape.closestPoint(localRef, pathOpt); + } + } + + var cp = (intersection) ? src_V.transformPoint(intersection, targetMatrix) : anchor; + var cpOffset = opt.offset || 0; + if (opt.stroke) cpOffset += stroke(node) / 2; + + return offsetPoint(cp, line.start, cpOffset); +} + +const connectionPoints_anchor = anchorConnectionPoint; +const bbox = bboxIntersection; +const rectangle = rectangleIntersection; +const boundary = boundaryIntersection; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectionStrategies/index.mjs + + +function abs2rel(absolute, max) { + + if (max === 0) return '0%'; + // round to 3 decimal places + const dp = 1000; + const relative = Math.round(absolute / max * 100 * dp) / dp; + return `${relative}%`; +} + +function pin(relative) { + + return function(end, view, magnet, coords) { + var fn = (view.isNodeConnection(magnet)) ? pinnedLinkEnd : pinnedElementEnd; + return fn(relative, end, view, magnet, coords); + }; +} + +function pinnedElementEnd(relative, end, view, magnet, coords) { + + var angle = view.model.angle(); + var bbox = view.getNodeUnrotatedBBox(magnet); + var origin = view.model.getBBox().center(); + coords.rotate(origin, angle); + var dx = coords.x - bbox.x; + var dy = coords.y - bbox.y; + + if (relative) { + dx = abs2rel(dx, bbox.width); + dy = abs2rel(dy, bbox.height); + } + + end.anchor = { + name: 'topLeft', + args: { + dx: dx, + dy: dy, + rotate: true + } + }; + + return end; +} + +function pinnedLinkEnd(relative, end, view, _magnet, coords) { + + var connection = view.getConnection(); + if (!connection) return end; + var length = connection.closestPointLength(coords); + if (relative) { + var totalLength = connection.length(); + end.anchor = { + name: 'connectionRatio', + args: { + ratio: length / totalLength + } + }; + } else { + end.anchor = { + name: 'connectionLength', + args: { + length: length + } + }; + } + return end; +} + +const useDefaults = noop; +const pinAbsolute = pin(false); +const pinRelative = pin(true); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/straight.mjs + + +const CornerTypes = { + POINT: 'point', + CUBIC: 'cubic', + LINE: 'line', + GAP: 'gap' +}; + +const DEFINED_CORNER_TYPES = Object.values(CornerTypes); + +const CORNER_RADIUS = 10; +const PRECISION = 1; + +const straight = function(sourcePoint, targetPoint, routePoints = [], opt = {}) { + + const { + cornerType = CornerTypes.POINT, + cornerRadius = CORNER_RADIUS, + cornerPreserveAspectRatio = false, + precision = PRECISION, + raw = false + } = opt; + + if (DEFINED_CORNER_TYPES.indexOf(cornerType) === -1) { + // unknown `cornerType` provided => error + throw new Error('Invalid `cornerType` provided to `straight` connector.'); + } + + let path; + + if ((cornerType === CornerTypes.POINT) || !cornerRadius) { + // default option => normal connector + // simply connect all points with straight lines + const points = [sourcePoint].concat(routePoints).concat([targetPoint]); + const polyline = new Polyline(points); + path = new Path(polyline); + + } else { + // `cornerType` is not unknown and not 'point' (default) => must be one of other valid types + path = new Path(); + + // add initial gap segment = to source point + path.appendSegment(Path.createSegment('M', sourcePoint)); + + let nextDistance; + const routePointsLength = routePoints.length; + for (let i = 0; i < routePointsLength; i++) { + + const curr = new Point(routePoints[i]); + const prev = (routePoints[i - 1] || sourcePoint); + const next = (routePoints[i + 1] || targetPoint); + const prevDistance = (nextDistance || (curr.distance(prev) / 2)); // try to re-use previously-computed `nextDistance` + nextDistance = (curr.distance(next) / 2); + + let startMove, endMove; + if (!cornerPreserveAspectRatio) { + // `startMove` and `endMove` may be different + // (this happens when next or previous path point is closer than `2 * cornerRadius`) + startMove = -Math.min(cornerRadius, prevDistance); + endMove = -Math.min(cornerRadius, nextDistance); + } else { + // force `startMove` and `endMove` to be the same + startMove = endMove = -Math.min(cornerRadius, prevDistance, nextDistance); + } + + // to find `cornerStart` and `cornerEnd`, the logic is as follows (using `cornerStart` as example): + // - find a point lying on the line `prev - startMove` such that... + // - ...the point lies `abs(startMove)` distance away from `curr`... + // - ...and its coordinates are rounded to whole numbers + const cornerStart = curr.clone().move(prev, startMove).round(precision); + const cornerEnd = curr.clone().move(next, endMove).round(precision); + + // add in-between straight segment = from previous route point to corner start point + // (may have zero length) + path.appendSegment(Path.createSegment('L', cornerStart)); + + // add corner segment = from corner start point to corner end point + switch (cornerType) { + case CornerTypes.CUBIC: { + // corner is rounded + const _13 = (1 / 3); + const _23 = (2 / 3); + const control1 = new Point((_13 * cornerStart.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * cornerStart.y)); + const control2 = new Point((_13 * cornerEnd.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * cornerEnd.y)); + path.appendSegment(Path.createSegment('C', control1, control2, cornerEnd)); + break; + } + case CornerTypes.LINE: { + // corner has bevel + path.appendSegment(Path.createSegment('L', cornerEnd)); + break; + } + case CornerTypes.GAP: { + // corner has empty space + path.appendSegment(Path.createSegment('M', cornerEnd)); + break; + } + // default: no segment is created + } + } + + // add final straight segment = from last corner end point to target point + // (= or from start point to end point, if there are no route points) + // (may have zero length) + path.appendSegment(Path.createSegment('L', targetPoint)); + } + + return ((raw) ? path : path.serialize()); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/jumpover.mjs + + + +// default size of jump if not specified in options +var JUMP_SIZE = 5; + +// available jump types +// first one taken as default +var JUMP_TYPES = ['arc', 'gap', 'cubic']; + +// default radius +var RADIUS = 0; + +// takes care of math. error for case when jump is too close to end of line +var CLOSE_PROXIMITY_PADDING = 1; + +// list of connector types not to jump over. +var IGNORED_CONNECTORS = ['smooth']; + +// internal constants for round segment +var _13 = 1 / 3; +var _23 = 2 / 3; + +function sortPointsAscending(p1, p2) { + + let { x: x1, y: y1 } = p1; + let { x: x2, y: y2 } = p2; + + if (x1 > x2) { + + let swap = x1; + x1 = x2; + x2 = swap; + + swap = y1; + y1 = y2; + y2 = swap; + } + + if (y1 > y2) { + let swap = x1; + x1 = x2; + x2 = swap; + + swap = y1; + y1 = y2; + y2 = swap; + } + + return [new Point(x1, y1), new Point(x2, y2)]; +} + +function overlapExists(line1, line2) { + + const [{ x: x1, y: y1 }, { x: x2, y: y2 }] = sortPointsAscending(line1.start, line1.end); + const [{ x: x3, y: y3 }, { x: x4, y: y4 }] = sortPointsAscending(line2.start, line2.end); + + const xMatch = x1 <= x4 && x3 <= x2; + const yMatch = y1 <= y4 && y3 <= y2; + + return xMatch && yMatch; +} + +/** + * Transform start/end and route into series of lines + * @param {g.point} sourcePoint start point + * @param {g.point} targetPoint end point + * @param {g.point[]} route optional list of route + * @return {g.line[]} [description] + */ +function createLines(sourcePoint, targetPoint, route) { + // make a flattened array of all points + var points = [].concat(sourcePoint, route, targetPoint); + return points.reduce(function(resultLines, point, idx) { + // if there is a next point, make a line with it + var nextPoint = points[idx + 1]; + if (nextPoint != null) { + resultLines[idx] = line_line(point, nextPoint); + } + return resultLines; + }, []); +} + +function setupUpdating(jumpOverLinkView) { + var paper = jumpOverLinkView.paper; + var updateList = paper._jumpOverUpdateList; + + // first time setup for this paper + if (updateList == null) { + updateList = paper._jumpOverUpdateList = []; + var graph = paper.model; + graph.on('batch:stop', function() { + if (this.hasActiveBatch()) return; + updateJumpOver(paper); + }); + graph.on('reset', function() { + updateList = paper._jumpOverUpdateList = []; + }); + } + + // add this link to a list so it can be updated when some other link is updated + if (updateList.indexOf(jumpOverLinkView) < 0) { + updateList.push(jumpOverLinkView); + + // watch for change of connector type or removal of link itself + // to remove the link from a list of jump over connectors + jumpOverLinkView.listenToOnce(jumpOverLinkView.model, 'change:connector remove', function() { + updateList.splice(updateList.indexOf(jumpOverLinkView), 1); + }); + } +} + +/** + * Handler for a batch:stop event to force + * update of all registered links with jump over connector + * @param {object} batchEvent optional object with info about batch + */ +function updateJumpOver(paper) { + var updateList = paper._jumpOverUpdateList; + for (var i = 0; i < updateList.length; i++) { + const linkView = updateList[i]; + const updateFlag = linkView.getFlag(linkView.constructor.Flags.CONNECTOR); + linkView.requestUpdate(updateFlag); + } +} + +/** + * Utility function to collect all intersection points of a single + * line against group of other lines. + * @param {g.line} line where to find points + * @param {g.line[]} crossCheckLines lines to cross + * @return {g.point[]} list of intersection points + */ +function findLineIntersections(line, crossCheckLines) { + return toArray(crossCheckLines).reduce(function(res, crossCheckLine) { + var intersection = line.intersection(crossCheckLine); + if (intersection) { + res.push(intersection); + } + return res; + }, []); +} + +/** + * Sorting function for list of points by their distance. + * @param {g.point} p1 first point + * @param {g.point} p2 second point + * @return {number} squared distance between points + */ +function sortPoints(p1, p2) { + return line_line(p1, p2).squaredLength(); +} + +/** + * Split input line into multiple based on intersection points. + * @param {g.line} line input line to split + * @param {g.point[]} intersections points where to split the line + * @param {number} jumpSize the size of jump arc (length empty spot on a line) + * @return {g.line[]} list of lines being split + */ +function createJumps(line, intersections, jumpSize) { + return intersections.reduce(function(resultLines, point, idx) { + // skipping points that were merged with the previous line + // to make bigger arc over multiple lines that are close to each other + if (point.skip === true) { + return resultLines; + } + + // always grab the last line from buffer and modify it + var lastLine = resultLines.pop() || line; + + // calculate start and end of jump by moving by a given size of jump + var jumpStart = point_point(point).move(lastLine.start, -(jumpSize)); + var jumpEnd = point_point(point).move(lastLine.start, +(jumpSize)); + + // now try to look at the next intersection point + var nextPoint = intersections[idx + 1]; + if (nextPoint != null) { + var distance = jumpEnd.distance(nextPoint); + if (distance <= jumpSize) { + // next point is close enough, move the jump end by this + // difference and mark the next point to be skipped + jumpEnd = nextPoint.move(lastLine.start, distance); + nextPoint.skip = true; + } + } else { + // this block is inside of `else` as an optimization so the distance is + // not calculated when we know there are no other intersection points + var endDistance = jumpStart.distance(lastLine.end); + // if the end is too close to possible jump, draw remaining line instead of a jump + if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { + resultLines.push(lastLine); + return resultLines; + } + } + + var startDistance = jumpEnd.distance(lastLine.start); + if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { + // if the start of line is too close to jump, draw that line instead of a jump + resultLines.push(lastLine); + return resultLines; + } + + // finally create a jump line + var jumpLine = line_line(jumpStart, jumpEnd); + // it's just simple line but with a `isJump` property + jumpLine.isJump = true; + + resultLines.push( + line_line(lastLine.start, jumpStart), + jumpLine, + line_line(jumpEnd, lastLine.end) + ); + return resultLines; + }, []); +} + +/** + * Assemble `D` attribute of a SVG path by iterating given lines. + * @param {g.line[]} lines source lines to use + * @param {number} jumpSize the size of jump arc (length empty spot on a line) + * @param {number} radius the radius + * @return {string} + */ +function buildPath(lines, jumpSize, jumpType, radius) { + + var path = new Path(); + var segment; + + // first move to the start of a first line + segment = Path.createSegment('M', lines[0].start); + path.appendSegment(segment); + + // make a paths from lines + toArray(lines).forEach(function(line, index) { + + if (line.isJump) { + var angle, diff; + + var control1, control2; + + if (jumpType === 'arc') { // approximates semicircle with 2 curves + angle = -90; + // determine rotation of arc based on difference between points + diff = line.start.difference(line.end); + // make sure the arc always points up (or right) + var xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0)); + if (xAxisRotate) angle += 180; + + var midpoint = line.midpoint(); + var centerLine = new Line(midpoint, line.end).rotate(midpoint, angle); + + var halfLine; + + // first half + halfLine = new Line(line.start, midpoint); + + control1 = halfLine.pointAt(2 / 3).rotate(line.start, angle); + control2 = centerLine.pointAt(1 / 3).rotate(centerLine.end, -angle); + + segment = Path.createSegment('C', control1, control2, centerLine.end); + path.appendSegment(segment); + + // second half + halfLine = new Line(midpoint, line.end); + + control1 = centerLine.pointAt(1 / 3).rotate(centerLine.end, angle); + control2 = halfLine.pointAt(1 / 3).rotate(line.end, -angle); + + segment = Path.createSegment('C', control1, control2, line.end); + path.appendSegment(segment); + + } else if (jumpType === 'gap') { + segment = Path.createSegment('M', line.end); + path.appendSegment(segment); + + } else if (jumpType === 'cubic') { // approximates semicircle with 1 curve + angle = line.start.theta(line.end); + + var xOffset = jumpSize * 0.6; + var yOffset = jumpSize * 1.35; + + // determine rotation of arc based on difference between points + diff = line.start.difference(line.end); + // make sure the arc always points up (or right) + xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0)); + if (xAxisRotate) yOffset *= -1; + + control1 = Point(line.start.x + xOffset, line.start.y + yOffset).rotate(line.start, angle); + control2 = Point(line.end.x - xOffset, line.end.y + yOffset).rotate(line.end, angle); + + segment = Path.createSegment('C', control1, control2, line.end); + path.appendSegment(segment); + } + + } else { + var nextLine = lines[index + 1]; + if (radius == 0 || !nextLine || nextLine.isJump) { + segment = Path.createSegment('L', line.end); + path.appendSegment(segment); + } else { + buildRoundedSegment(radius, path, line.end, line.start, nextLine.end); + } + } + }); + + return path; +} + +function buildRoundedSegment(offset, path, curr, prev, next) { + var prevDistance = curr.distance(prev) / 2; + var nextDistance = curr.distance(next) / 2; + + var startMove = -Math.min(offset, prevDistance); + var endMove = -Math.min(offset, nextDistance); + + var roundedStart = curr.clone().move(prev, startMove).round(); + var roundedEnd = curr.clone().move(next, endMove).round(); + + var control1 = new Point((_13 * roundedStart.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedStart.y)); + var control2 = new Point((_13 * roundedEnd.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedEnd.y)); + + var segment; + segment = Path.createSegment('L', roundedStart); + path.appendSegment(segment); + + segment = Path.createSegment('C', control1, control2, roundedEnd); + path.appendSegment(segment); +} + +/** + * Actual connector function that will be run on every update. + * @param {g.point} sourcePoint start point of this link + * @param {g.point} targetPoint end point of this link + * @param {g.point[]} route of this link + * @param {object} opt options + * @property {number} size optional size of a jump arc + * @return {string} created `D` attribute of SVG path + */ +const jumpover = function(sourcePoint, targetPoint, route, opt) { // eslint-disable-line max-params + + setupUpdating(this); + + var raw = opt.raw; + var jumpSize = opt.size || JUMP_SIZE; + var jumpType = opt.jump && ('' + opt.jump).toLowerCase(); + var radius = opt.radius || RADIUS; + var ignoreConnectors = opt.ignoreConnectors || IGNORED_CONNECTORS; + + // grab the first jump type as a default if specified one is invalid + if (JUMP_TYPES.indexOf(jumpType) === -1) { + jumpType = JUMP_TYPES[0]; + } + + var paper = this.paper; + var graph = paper.model; + var allLinks = graph.getLinks(); + + // there is just one link, draw it directly + if (allLinks.length === 1) { + return buildPath( + createLines(sourcePoint, targetPoint, route), + jumpSize, jumpType, radius + ); + } + + var thisModel = this.model; + var thisIndex = allLinks.indexOf(thisModel); + var defaultConnector = paper.options.defaultConnector || {}; + + // not all links are meant to be jumped over. + var links = allLinks.filter(function(link, idx) { + + var connector = link.get('connector') || defaultConnector; + + // avoid jumping over links with connector type listed in `ignored connectors`. + if (toArray(ignoreConnectors).includes(connector.name)) { + return false; + } + // filter out links that are above this one and have the same connector type + // otherwise there would double hoops for each intersection + if (idx > thisIndex) { + return connector.name !== 'jumpover'; + } + return true; + }); + + // find views for all links + var linkViews = links.map(function(link) { + return paper.findViewByModel(link); + }); + + // create lines for this link + var thisLines = createLines( + sourcePoint, + targetPoint, + route + ); + + // create lines for all other links + var linkLines = linkViews.map(function(linkView) { + if (linkView == null) { + return []; + } + if (linkView === this) { + return thisLines; + } + return createLines( + linkView.sourcePoint, + linkView.targetPoint, + linkView.route + ); + }, this); + + // transform lines for this link by splitting with jump lines at + // points of intersection with other links + var jumpingLines = thisLines.reduce(function(resultLines, thisLine) { + // iterate all links and grab the intersections with this line + // these are then sorted by distance so the line can be split more easily + var intersections = links.reduce(function(res, link, i) { + // don't intersection with itself + if (link !== thisModel) { + + const linkLinesToTest = linkLines[i].slice(); + const overlapIndex = linkLinesToTest.findIndex((line) => overlapExists(thisLine, line)); + + // Overlap occurs and the end point of one segment lies on thisLine + if (overlapIndex > -1 && thisLine.containsPoint(linkLinesToTest[overlapIndex].end)) { + // Remove the next segment because there will never be a jump + linkLinesToTest.splice(overlapIndex + 1, 1); + } + const lineIntersections = findLineIntersections(thisLine, linkLinesToTest); + res.push.apply(res, lineIntersections); + } + return res; + }, []).sort(function(a, b) { + return sortPoints(thisLine.start, a) - sortPoints(thisLine.start, b); + }); + + if (intersections.length > 0) { + // split the line based on found intersection points + resultLines.push.apply(resultLines, createJumps(thisLine, intersections, jumpSize)); + } else { + // without any intersection the line goes uninterrupted + resultLines.push(thisLine); + } + return resultLines; + }, []); + + var path = buildPath(jumpingLines, jumpSize, jumpType, radius); + return (raw) ? path : path.serialize(); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/normal.mjs + + +const normal = function(sourcePoint, targetPoint, route = [], opt = {}) { + + const { raw } = opt; + const localOpt = { + cornerType: 'point', + raw + }; + + return straight(sourcePoint, targetPoint, route, localOpt); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/rounded.mjs + + +const rounded_CORNER_RADIUS = 10; +const rounded_PRECISION = 0; + +const rounded = function(sourcePoint, targetPoint, route = [], opt = {}) { + + const { radius = rounded_CORNER_RADIUS, raw } = opt; + const localOpt = { + cornerType: 'cubic', + cornerRadius: radius, + precision: rounded_PRECISION, + raw + }; + + return straight(sourcePoint, targetPoint, route, localOpt); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/smooth.mjs + + +const smooth = function(sourcePoint, targetPoint, route, opt) { + + var raw = opt && opt.raw; + var path; + + if (route && route.length !== 0) { + + var points = [sourcePoint].concat(route).concat([targetPoint]); + var curves = Curve.throughPoints(points); + + path = new Path(curves); + + } else { + // if we have no route, use a default cubic bezier curve + // cubic bezier requires two control points + // the control points have `x` midway between source and target + // this produces an S-like curve + + path = new Path(); + + var segment; + + segment = Path.createSegment('M', sourcePoint); + path.appendSegment(segment); + + if ((Math.abs(sourcePoint.x - targetPoint.x)) >= (Math.abs(sourcePoint.y - targetPoint.y))) { + var controlPointX = (sourcePoint.x + targetPoint.x) / 2; + + segment = Path.createSegment('C', controlPointX, sourcePoint.y, controlPointX, targetPoint.y, targetPoint.x, targetPoint.y); + path.appendSegment(segment); + + } else { + var controlPointY = (sourcePoint.y + targetPoint.y) / 2; + + segment = Path.createSegment('C', sourcePoint.x, controlPointY, targetPoint.x, controlPointY, targetPoint.x, targetPoint.y); + path.appendSegment(segment); + + } + } + + return (raw) ? path : path.serialize(); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/curve.mjs + + +const Directions = { + AUTO: 'auto', + HORIZONTAL: 'horizontal', + VERTICAL: 'vertical', + CLOSEST_POINT: 'closest-point', + OUTWARDS: 'outwards' +}; + +const TangentDirections = { + UP: 'up', + DOWN: 'down', + LEFT: 'left', + RIGHT: 'right', + AUTO: 'auto', + CLOSEST_POINT: 'closest-point', + OUTWARDS: 'outwards' +}; + +const curve = function(sourcePoint, targetPoint, route = [], opt = {}, linkView) { + const raw = Boolean(opt.raw); + // distanceCoefficient - a coefficient of the tangent vector length relative to the distance between points. + // angleTangentCoefficient - a coefficient of the end tangents length in the case of angles larger than 45 degrees. + // tension - a Catmull-Rom curve tension parameter. + // sourceTangent - a tangent vector along the curve at the sourcePoint. + // sourceDirection - a unit direction vector along the curve at the sourcePoint. + // targetTangent - a tangent vector along the curve at the targetPoint. + // targetDirection - a unit direction vector along the curve at the targetPoint. + // precision - a rounding precision for path values. + const { direction = Directions.AUTO, precision = 3 } = opt; + const options = { + coeff: opt.distanceCoefficient || 0.6, + angleTangentCoefficient: opt.angleTangentCoefficient || 80, + tau: opt.tension || 0.5, + sourceTangent: opt.sourceTangent ? new Point(opt.sourceTangent) : null, + targetTangent: opt.targetTangent ? new Point(opt.targetTangent) : null, + rotate: Boolean(opt.rotate) + }; + if (typeof opt.sourceDirection === 'string') + options.sourceDirection = opt.sourceDirection; + else if (typeof opt.sourceDirection === 'number') + options.sourceDirection = new Point(1, 0).rotate(null, opt.sourceDirection); + else + options.sourceDirection = opt.sourceDirection ? new Point(opt.sourceDirection).normalize() : null; + + if (typeof opt.targetDirection === 'string') + options.targetDirection = opt.targetDirection; + else if (typeof opt.targetDirection === 'number') + options.targetDirection = new Point(1, 0).rotate(null, opt.targetDirection); + else + options.targetDirection = opt.targetDirection ? new Point(opt.targetDirection).normalize() : null; + + const completeRoute = [sourcePoint, ...route, targetPoint].map(p => new Point(p)); + + // The calculation of a sourceTangent + let sourceTangent; + if (options.sourceTangent) { + sourceTangent = options.sourceTangent; + } else { + const sourceDirection = getSourceTangentDirection(linkView, completeRoute, direction, options); + const tangentLength = completeRoute[0].distance(completeRoute[1]) * options.coeff; + const pointsVector = completeRoute[1].difference(completeRoute[0]).normalize(); + const angle = angleBetweenVectors(sourceDirection, pointsVector); + if (angle > Math.PI / 4) { + const updatedLength = tangentLength + (angle - Math.PI / 4) * options.angleTangentCoefficient; + sourceTangent = sourceDirection.clone().scale(updatedLength, updatedLength); + } else { + sourceTangent = sourceDirection.clone().scale(tangentLength, tangentLength); + } + } + + // The calculation of a targetTangent + let targetTangent; + if (options.targetTangent) { + targetTangent = options.targetTangent; + } else { + const targetDirection = getTargetTangentDirection(linkView, completeRoute, direction, options); + const last = completeRoute.length - 1; + const tangentLength = completeRoute[last - 1].distance(completeRoute[last]) * options.coeff; + const pointsVector = completeRoute[last - 1].difference(completeRoute[last]).normalize(); + const angle = angleBetweenVectors(targetDirection, pointsVector); + if (angle > Math.PI / 4) { + const updatedLength = tangentLength + (angle - Math.PI / 4) * options.angleTangentCoefficient; + targetTangent = targetDirection.clone().scale(updatedLength, updatedLength); + } else { + targetTangent = targetDirection.clone().scale(tangentLength, tangentLength); + } + } + + const catmullRomCurves = createCatmullRomCurves(completeRoute, sourceTangent, targetTangent, options); + const bezierCurves = catmullRomCurves.map(curve => catmullRomToBezier(curve, options)); + const path = new Path(bezierCurves).round(precision); + + return (raw) ? path : path.serialize(); +}; +curve.Directions = Directions; +curve.TangentDirections = TangentDirections; + +function getHorizontalSourceDirection(linkView, route, options) { + const { sourceBBox } = linkView; + + let sourceSide; + let rotation; + if (!linkView.sourceView) { + if (sourceBBox.x > route[1].x) + sourceSide = 'right'; + else + sourceSide = 'left'; + } else { + rotation = linkView.sourceView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el); + const sourcePoint = route[0].clone(); + sourcePoint.rotate(sourceBBox.center(), rotation); + sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint); + } else { + sourceSide = sourceBBox.sideNearestToPoint(route[0]); + } + } + + let direction; + switch (sourceSide) { + case 'left': + direction = new Point(-1, 0); + break; + case 'right': + default: + direction = new Point(1, 0); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getHorizontalTargetDirection(linkView, route, options) { + const { targetBBox } = linkView; + + let targetSide; + let rotation; + if (!linkView.targetView) { + if (targetBBox.x > route[route.length - 2].x) + targetSide = 'left'; + else + targetSide = 'right'; + } else { + rotation = linkView.targetView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el); + const targetPoint = route[route.length - 1].clone(); + targetPoint.rotate(targetBBox.center(), rotation); + targetSide = unrotatedBBox.sideNearestToPoint(targetPoint); + } else { + targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]); + } + } + + let direction; + switch (targetSide) { + case 'left': + direction = new Point(-1, 0); + break; + case 'right': + default: + direction = new Point(1, 0); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getVerticalSourceDirection(linkView, route, options) { + const { sourceBBox } = linkView; + + let sourceSide; + let rotation; + if (!linkView.sourceView) { + if (sourceBBox.y > route[1].y) + sourceSide = 'bottom'; + else + sourceSide = 'top'; + } else { + rotation = linkView.sourceView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el); + const sourcePoint = route[0].clone(); + sourcePoint.rotate(sourceBBox.center(), rotation); + sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint); + } else { + sourceSide = sourceBBox.sideNearestToPoint(route[0]); + } + } + + let direction; + switch (sourceSide) { + case 'top': + direction = new Point(0, -1); + break; + case 'bottom': + default: + direction = new Point(0, 1); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getVerticalTargetDirection(linkView, route, options) { + const { targetBBox } = linkView; + + let targetSide; + let rotation; + if (!linkView.targetView) { + if (targetBBox.y > route[route.length - 2].y) + targetSide = 'top'; + else + targetSide = 'bottom'; + } else { + rotation = linkView.targetView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el); + const targetPoint = route[route.length - 1].clone(); + targetPoint.rotate(targetBBox.center(), rotation); + targetSide = unrotatedBBox.sideNearestToPoint(targetPoint); + } else { + targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]); + } + } + + + let direction; + switch (targetSide) { + case 'top': + direction = new Point(0, -1); + break; + case 'bottom': + default: + direction = new Point(0, 1); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getAutoSourceDirection(linkView, route, options) { + const { sourceBBox } = linkView; + + let sourceSide; + let rotation; + if (!linkView.sourceView) { + sourceSide = sourceBBox.sideNearestToPoint(route[1]); + } else { + rotation = linkView.sourceView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el); + const sourcePoint = route[0].clone(); + sourcePoint.rotate(sourceBBox.center(), rotation); + sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint); + } else { + sourceSide = sourceBBox.sideNearestToPoint(route[0]); + } + } + + let direction; + switch (sourceSide) { + case 'top': + direction = new Point(0, -1); + break; + case 'bottom': + direction = new Point(0, 1); + break; + case 'right': + direction = new Point(1, 0); + break; + case 'left': + direction = new Point(-1, 0); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getAutoTargetDirection(linkView, route, options) { + const { targetBBox } = linkView; + + let targetSide; + let rotation; + if (!linkView.targetView) { + targetSide = targetBBox.sideNearestToPoint(route[route.length - 2]); + } else { + rotation = linkView.targetView.model.angle(); + if (options.rotate && rotation) { + const unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el); + const targetPoint = route[route.length - 1].clone(); + targetPoint.rotate(targetBBox.center(), rotation); + targetSide = unrotatedBBox.sideNearestToPoint(targetPoint); + } else { + targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]); + } + } + + let direction; + switch (targetSide) { + case 'top': + direction = new Point(0, -1); + break; + case 'bottom': + direction = new Point(0, 1); + break; + case 'right': + direction = new Point(1, 0); + break; + case 'left': + direction = new Point(-1, 0); + break; + } + + if (options.rotate && rotation) { + direction.rotate(null, -rotation); + } + + return direction; +} + +function getClosestPointSourceDirection(linkView, route, options) { + return route[1].difference(route[0]).normalize(); +} + +function getClosestPointTargetDirection(linkView, route, options) { + const last = route.length - 1; + return route[last - 1].difference(route[last]).normalize(); +} + +function getOutwardsSourceDirection(linkView, route, options) { + const { sourceBBox } = linkView; + const sourceCenter = sourceBBox.center(); + return route[0].difference(sourceCenter).normalize(); +} + +function getOutwardsTargetDirection(linkView, route, options) { + const { targetBBox } = linkView; + const targetCenter = targetBBox.center(); + return route[route.length - 1].difference(targetCenter).normalize(); +} + +function getSourceTangentDirection(linkView, route, direction, options) { + if (options.sourceDirection) { + switch (options.sourceDirection) { + case TangentDirections.UP: + return new Point(0, -1); + case TangentDirections.DOWN: + return new Point(0, 1); + case TangentDirections.LEFT: + return new Point(-1, 0); + case TangentDirections.RIGHT: + return new Point(1, 0); + case TangentDirections.AUTO: + return getAutoSourceDirection(linkView, route, options); + case TangentDirections.CLOSEST_POINT: + return getClosestPointSourceDirection(linkView, route, options); + case TangentDirections.OUTWARDS: + return getOutwardsSourceDirection(linkView, route, options); + default: + return options.sourceDirection; + } + } + + switch (direction) { + case Directions.HORIZONTAL: + return getHorizontalSourceDirection(linkView, route, options); + case Directions.VERTICAL: + return getVerticalSourceDirection(linkView, route, options); + case Directions.CLOSEST_POINT: + return getClosestPointSourceDirection(linkView, route, options); + case Directions.OUTWARDS: + return getOutwardsSourceDirection(linkView, route, options); + case Directions.AUTO: + default: + return getAutoSourceDirection(linkView, route, options); + } +} + +function getTargetTangentDirection(linkView, route, direction, options) { + if (options.targetDirection) { + switch (options.targetDirection) { + case TangentDirections.UP: + return new Point(0, -1); + case TangentDirections.DOWN: + return new Point(0, 1); + case TangentDirections.LEFT: + return new Point(-1, 0); + case TangentDirections.RIGHT: + return new Point(0, 1); + case TangentDirections.AUTO: + return getAutoTargetDirection(linkView, route, options); + case TangentDirections.CLOSEST_POINT: + return getClosestPointTargetDirection(linkView, route, options); + case TangentDirections.OUTWARDS: + return getOutwardsTargetDirection(linkView, route, options); + default: + return options.targetDirection; + } + } + + switch (direction) { + case Directions.HORIZONTAL: + return getHorizontalTargetDirection(linkView, route, options); + case Directions.VERTICAL: + return getVerticalTargetDirection(linkView, route, options); + case Directions.CLOSEST_POINT: + return getClosestPointTargetDirection(linkView, route, options); + case Directions.OUTWARDS: + return getOutwardsTargetDirection(linkView, route, options); + case Directions.AUTO: + default: + return getAutoTargetDirection(linkView, route, options); + } +} + +function rotateVector(vector, angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const x = cos * vector.x - sin * vector.y; + const y = sin * vector.x + cos * vector.y; + vector.x = x; + vector.y = y; +} + +function angleBetweenVectors(v1, v2) { + let cos = v1.dot(v2) / (v1.magnitude() * v2.magnitude()); + if (cos < -1) cos = -1; + if (cos > 1) cos = 1; + return Math.acos(cos); +} + +function determinant(v1, v2) { + return v1.x * v2.y - v1.y * v2.x; +} + +function createCatmullRomCurves(points, sourceTangent, targetTangent, options) { + const { tau, coeff } = options; + const distances = []; + const tangents = []; + const catmullRomCurves = []; + const n = points.length - 1; + + for (let i = 0; i < n; i++) { + distances[i] = points[i].distance(points[i + 1]); + } + + tangents[0] = sourceTangent; + tangents[n] = targetTangent; + + // The calculation of tangents of vertices + for (let i = 1; i < n; i++) { + let tpPrev; + let tpNext; + if (i === 1) { + tpPrev = points[i - 1].clone().offset(tangents[i - 1].x, tangents[i - 1].y); + } else { + tpPrev = points[i - 1].clone(); + } + if (i === n - 1) { + tpNext = points[i + 1].clone().offset(tangents[i + 1].x, tangents[i + 1].y); + } else { + tpNext = points[i + 1].clone(); + } + const v1 = tpPrev.difference(points[i]).normalize(); + const v2 = tpNext.difference(points[i]).normalize(); + const vAngle = angleBetweenVectors(v1, v2); + + let rot = (Math.PI - vAngle) / 2; + let t; + const vectorDeterminant = determinant(v1, v2); + let pointsDeterminant; + pointsDeterminant = determinant(points[i].difference(points[i + 1]), points[i].difference(points[i - 1])); + if (vectorDeterminant < 0) { + rot = -rot; + } + if ((vAngle < Math.PI / 2) && ((rot < 0 && pointsDeterminant < 0) || (rot > 0 && pointsDeterminant > 0))) { + rot = rot - Math.PI; + } + t = v2.clone(); + rotateVector(t, rot); + + const t1 = t.clone(); + const t2 = t.clone(); + const scaleFactor1 = distances[i - 1] * coeff; + const scaleFactor2 = distances[i] * coeff; + t1.scale(scaleFactor1, scaleFactor1); + t2.scale(scaleFactor2, scaleFactor2); + + tangents[i] = [t1, t2]; + } + + // The building of a Catmull-Rom curve based of tangents of points + for (let i = 0; i < n; i++) { + let p0; + let p3; + if (i === 0) { + p0 = points[i + 1].difference(tangents[i].x / tau, tangents[i].y / tau); + } else { + p0 = points[i + 1].difference(tangents[i][1].x / tau, tangents[i][1].y / tau); + } + if (i === n - 1) { + p3 = points[i].clone().offset(tangents[i + 1].x / tau, tangents[i + 1].y / tau); + } else { + p3 = points[i].difference(tangents[i + 1][0].x / tau, tangents[i + 1][0].y / tau); + } + + catmullRomCurves[i] = [p0, points[i], points[i + 1], p3]; + } + return catmullRomCurves; +} + +// The function to convert Catmull-Rom curve to Bezier curve using the tension (tau) +function catmullRomToBezier(points, options) { + const { tau } = options; + + const bcp1 = new Point(); + bcp1.x = points[1].x + (points[2].x - points[0].x) / (6 * tau); + bcp1.y = points[1].y + (points[2].y - points[0].y) / (6 * tau); + + const bcp2 = new Point(); + bcp2.x = points[2].x + (points[3].x - points[1].x) / (6 * tau); + bcp2.y = points[2].y + (points[3].y - points[1].y) / (6 * tau); + return new Curve( + points[1], + bcp1, + bcp2, + points[2] + ); +} + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/connectors/index.mjs + + + + + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/PaperLayer.mjs + + + +const LayersNames = { + CELLS: 'cells', + BACK: 'back', + FRONT: 'front', + TOOLS: 'tools', + LABELS: 'labels' +}; + +const PaperLayer = View.extend({ + + tagName: 'g', + svgElement: true, + pivotNodes: null, + defaultTheme: null, + + options: { + name: '' + }, + + className: function() { + return addClassNamePrefix(`${this.options.name}-layer`); + }, + + init: function() { + this.pivotNodes = {}; + }, + + insertSortedNode: function(node, z) { + this.el.insertBefore(node, this.insertPivot(z)); + }, + + insertNode: function(node) { + const { el } = this; + if (node.parentNode !== el) { + el.appendChild(node); + } + }, + + insertPivot: function(z) { + const { el, pivotNodes } = this; + z = +z; + z || (z = 0); + let pivotNode = pivotNodes[z]; + if (pivotNode) return pivotNode; + pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1)); + let neighborZ = -Infinity; + for (let currentZ in pivotNodes) { + currentZ = +currentZ; + if (currentZ < z && currentZ > neighborZ) { + neighborZ = currentZ; + if (neighborZ === z - 1) continue; + } + } + if (neighborZ !== -Infinity) { + const neighborPivot = pivotNodes[neighborZ]; + // Insert After + el.insertBefore(pivotNode, neighborPivot.nextSibling); + } else { + // First Child + el.insertBefore(pivotNode, el.firstChild); + } + return pivotNode; + }, + + removePivots: function() { + const { el, pivotNodes } = this; + for (let z in pivotNodes) el.removeChild(pivotNodes[z]); + this.pivotNodes = {}; + } + +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/normal.mjs +// Does not make any changes to vertices. +// Returns the arguments that are passed to it, unchanged. +const normal_normal = function(vertices, opt, linkView) { + + return vertices; +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/oneSide.mjs + + +// Routes the link always to/from a certain side +// +// Arguments: +// padding ... gap between the element and the first vertex. :: Default 40. +// side ... 'left' | 'right' | 'top' | 'bottom' :: Default 'bottom'. +// +const oneSide = function(vertices, opt, linkView) { + + var side = opt.side || 'bottom'; + var padding = normalizeSides(opt.padding || 40); + + // LinkView contains cached source an target bboxes. + // Note that those are Geometry rectangle objects. + var sourceBBox = linkView.sourceBBox; + var targetBBox = linkView.targetBBox; + var sourcePoint = sourceBBox.center(); + var targetPoint = targetBBox.center(); + + var coordinate, dimension, direction; + + switch (side) { + case 'bottom': + direction = 1; + coordinate = 'y'; + dimension = 'height'; + break; + case 'top': + direction = -1; + coordinate = 'y'; + dimension = 'height'; + break; + case 'left': + direction = -1; + coordinate = 'x'; + dimension = 'width'; + break; + case 'right': + direction = 1; + coordinate = 'x'; + dimension = 'width'; + break; + default: + throw new Error('Router: invalid side'); + } + + // move the points from the center of the element to outside of it. + sourcePoint[coordinate] += direction * (sourceBBox[dimension] / 2 + padding[side]); + targetPoint[coordinate] += direction * (targetBBox[dimension] / 2 + padding[side]); + + // make link orthogonal (at least the first and last vertex). + if ((direction * (sourcePoint[coordinate] - targetPoint[coordinate])) > 0) { + targetPoint[coordinate] = sourcePoint[coordinate]; + } else { + sourcePoint[coordinate] = targetPoint[coordinate]; + } + + return [sourcePoint].concat(vertices, targetPoint); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/orthogonal.mjs + + + +// bearing -> opposite bearing +var opposites = { + N: 'S', + S: 'N', + E: 'W', + W: 'E' +}; + +// bearing -> radians +var radians = { + N: -Math.PI / 2 * 3, + S: -Math.PI / 2, + E: 0, + W: Math.PI +}; + +// HELPERS // + +// returns a point `p` where lines p,p1 and p,p2 are perpendicular and p is not contained +// in the given box +function freeJoin(p1, p2, bbox) { + + var p = new Point(p1.x, p2.y); + if (bbox.containsPoint(p)) p = new Point(p2.x, p1.y); + // kept for reference + // if (bbox.containsPoint(p)) p = null; + + return p; +} + +// returns either width or height of a bbox based on the given bearing +function getBBoxSize(bbox, bearing) { + + return bbox[(bearing === 'W' || bearing === 'E') ? 'width' : 'height']; +} + +// simple bearing method (calculates only orthogonal cardinals) +function getBearing(from, to) { + + if (from.x === to.x) return (from.y > to.y) ? 'N' : 'S'; + if (from.y === to.y) return (from.x > to.x) ? 'W' : 'E'; + return null; +} + +// transform point to a rect +function getPointBox(p) { + + return new Rect(p.x, p.y, 0, 0); +} + +function getPaddingBox(opt) { + + // if both provided, opt.padding wins over opt.elementPadding + var sides = normalizeSides(opt.padding || opt.elementPadding || 20); + + return { + x: -sides.left, + y: -sides.top, + width: sides.left + sides.right, + height: sides.top + sides.bottom + }; +} + +// return source bbox +function getSourceBBox(linkView, opt) { + + return linkView.sourceBBox.clone().moveAndExpand(getPaddingBox(opt)); +} + +// return target bbox +function getTargetBBox(linkView, opt) { + + return linkView.targetBBox.clone().moveAndExpand(getPaddingBox(opt)); +} + +// return source anchor +function getSourceAnchor(linkView, opt) { + + if (linkView.sourceAnchor) return linkView.sourceAnchor; + + // fallback: center of bbox + var sourceBBox = getSourceBBox(linkView, opt); + return sourceBBox.center(); +} + +// return target anchor +function getTargetAnchor(linkView, opt) { + + if (linkView.targetAnchor) return linkView.targetAnchor; + + // fallback: center of bbox + var targetBBox = getTargetBBox(linkView, opt); + return targetBBox.center(); // default +} + +// PARTIAL ROUTERS // + +function vertexVertex(from, to, bearing) { + + var p1 = new Point(from.x, to.y); + var p2 = new Point(to.x, from.y); + var d1 = getBearing(from, p1); + var d2 = getBearing(from, p2); + var opposite = opposites[bearing]; + + var p = (d1 === bearing || (d1 !== opposite && (d2 === opposite || d2 !== bearing))) ? p1 : p2; + + return { points: [p], direction: getBearing(p, to) }; +} + +function elementVertex(from, to, fromBBox) { + + var p = freeJoin(from, to, fromBBox); + + return { points: [p], direction: getBearing(p, to) }; +} + +function vertexElement(from, to, toBBox, bearing) { + + var route = {}; + + var points = [new Point(from.x, to.y), new Point(to.x, from.y)]; + var freePoints = points.filter(function(pt) { + return !toBBox.containsPoint(pt); + }); + var freeBearingPoints = freePoints.filter(function(pt) { + return getBearing(pt, from) !== bearing; + }); + + var p; + + if (freeBearingPoints.length > 0) { + // Try to pick a point which bears the same direction as the previous segment. + + p = freeBearingPoints.filter(function(pt) { + return getBearing(from, pt) === bearing; + }).pop(); + p = p || freeBearingPoints[0]; + + route.points = [p]; + route.direction = getBearing(p, to); + + } else { + // Here we found only points which are either contained in the element or they would create + // a link segment going in opposite direction from the previous one. + // We take the point inside element and move it outside the element in the direction the + // route is going. Now we can join this point with the current end (using freeJoin). + + p = difference(points, freePoints)[0]; + + var p2 = (new Point(to)).move(p, -getBBoxSize(toBBox, bearing) / 2); + var p1 = freeJoin(p2, from, toBBox); + + route.points = [p1, p2]; + route.direction = getBearing(p2, to); + } + + return route; +} + +function elementElement(from, to, fromBBox, toBBox) { + + var route = elementVertex(to, from, toBBox); + var p1 = route.points[0]; + + if (fromBBox.containsPoint(p1)) { + + route = elementVertex(from, to, fromBBox); + var p2 = route.points[0]; + + if (toBBox.containsPoint(p2)) { + + var fromBorder = (new Point(from)).move(p2, -getBBoxSize(fromBBox, getBearing(from, p2)) / 2); + var toBorder = (new Point(to)).move(p1, -getBBoxSize(toBBox, getBearing(to, p1)) / 2); + var mid = (new Line(fromBorder, toBorder)).midpoint(); + + var startRoute = elementVertex(from, mid, fromBBox); + var endRoute = vertexVertex(mid, to, startRoute.direction); + + route.points = [startRoute.points[0], endRoute.points[0]]; + route.direction = endRoute.direction; + } + } + + return route; +} + +// Finds route for situations where one element is inside the other. +// Typically the route is directed outside the outer element first and +// then back towards the inner element. +function insideElement(from, to, fromBBox, toBBox, bearing) { + + var route = {}; + var boundary = fromBBox.union(toBBox).inflate(1); + + // start from the point which is closer to the boundary + var reversed = boundary.center().distance(to) > boundary.center().distance(from); + var start = reversed ? to : from; + var end = reversed ? from : to; + + var p1, p2, p3; + + if (bearing) { + // Points on circle with radius equals 'W + H` are always outside the rectangle + // with width W and height H if the center of that circle is the center of that rectangle. + p1 = Point.fromPolar(boundary.width + boundary.height, radians[bearing], start); + p1 = boundary.pointNearestToPoint(p1).move(p1, -1); + + } else { + p1 = boundary.pointNearestToPoint(start).move(start, 1); + } + + p2 = freeJoin(p1, end, boundary); + + if (p1.round().equals(p2.round())) { + p2 = Point.fromPolar(boundary.width + boundary.height, toRad(p1.theta(start)) + Math.PI / 2, end); + p2 = boundary.pointNearestToPoint(p2).move(end, 1).round(); + p3 = freeJoin(p1, p2, boundary); + route.points = reversed ? [p2, p3, p1] : [p1, p3, p2]; + + } else { + route.points = reversed ? [p2, p1] : [p1, p2]; + } + + route.direction = reversed ? getBearing(p1, to) : getBearing(p2, to); + + return route; +} + +// MAIN ROUTER // + +// Return points through which a connection needs to be drawn in order to obtain an orthogonal link +// routing from source to target going through `vertices`. +function orthogonal(vertices, opt, linkView) { + + var sourceBBox = getSourceBBox(linkView, opt); + var targetBBox = getTargetBBox(linkView, opt); + + var sourceAnchor = getSourceAnchor(linkView, opt); + var targetAnchor = getTargetAnchor(linkView, opt); + + // if anchor lies outside of bbox, the bbox expands to include it + sourceBBox = sourceBBox.union(getPointBox(sourceAnchor)); + targetBBox = targetBBox.union(getPointBox(targetAnchor)); + + vertices = toArray(vertices).map(Point); + vertices.unshift(sourceAnchor); + vertices.push(targetAnchor); + + var bearing; // bearing of previous route segment + + var orthogonalVertices = []; // the array of found orthogonal vertices to be returned + for (var i = 0, max = vertices.length - 1; i < max; i++) { + + var route = null; + + var from = vertices[i]; + var to = vertices[i + 1]; + + var isOrthogonal = !!getBearing(from, to); + + if (i === 0) { // source + + if (i + 1 === max) { // route source -> target + + // Expand one of the elements by 1px to detect situations when the two + // elements are positioned next to each other with no gap in between. + if (sourceBBox.intersect(targetBBox.clone().inflate(1))) { + route = insideElement(from, to, sourceBBox, targetBBox); + + } else if (!isOrthogonal) { + route = elementElement(from, to, sourceBBox, targetBBox); + } + + } else { // route source -> vertex + + if (sourceBBox.containsPoint(to)) { + route = insideElement(from, to, sourceBBox, getPointBox(to).moveAndExpand(getPaddingBox(opt))); + + } else if (!isOrthogonal) { + route = elementVertex(from, to, sourceBBox); + } + } + + } else if (i + 1 === max) { // route vertex -> target + + // prevent overlaps with previous line segment + var isOrthogonalLoop = isOrthogonal && getBearing(to, from) === bearing; + + if (targetBBox.containsPoint(from) || isOrthogonalLoop) { + route = insideElement(from, to, getPointBox(from).moveAndExpand(getPaddingBox(opt)), targetBBox, bearing); + + } else if (!isOrthogonal) { + route = vertexElement(from, to, targetBBox, bearing); + } + + } else if (!isOrthogonal) { // route vertex -> vertex + route = vertexVertex(from, to, bearing); + } + + // applicable to all routes: + + // set bearing for next iteration + if (route) { + Array.prototype.push.apply(orthogonalVertices, route.points); + bearing = route.direction; + + } else { + // orthogonal route and not looped + bearing = getBearing(from, to); + } + + // push `to` point to identified orthogonal vertices array + if (i + 1 < max) { + orthogonalVertices.push(to); + } + } + + return orthogonalVertices; +} + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/manhattan.mjs + + + + +var manhattan_config = { + + // size of the step to find a route (the grid of the manhattan pathfinder) + step: 10, + + // the number of route finding loops that cause the router to abort + // returns fallback route instead + maximumLoops: 2000, + + // the number of decimal places to round floating point coordinates + precision: 1, + + // maximum change of direction + maxAllowedDirectionChange: 90, + + // should the router use perpendicular linkView option? + // does not connect anchor of element but rather a point close-by that is orthogonal + // this looks much better + perpendicular: true, + + // should the source and/or target not be considered as obstacles? + excludeEnds: [], // 'source', 'target' + + // should certain types of elements not be considered as obstacles? + excludeTypes: ['basic.Text'], + + // possible starting directions from an element + startDirections: ['top', 'right', 'bottom', 'left'], + + // possible ending directions to an element + endDirections: ['top', 'right', 'bottom', 'left'], + + // specify the directions used above and what they mean + directionMap: { + top: { x: 0, y: -1 }, + right: { x: 1, y: 0 }, + bottom: { x: 0, y: 1 }, + left: { x: -1, y: 0 } + }, + + // cost of an orthogonal step + cost: function() { + + return this.step; + }, + + // an array of directions to find next points on the route + // different from start/end directions + directions: function() { + + var step = this.step; + var cost = this.cost(); + + return [ + { offsetX: step, offsetY: 0, cost: cost }, + { offsetX: -step, offsetY: 0, cost: cost }, + { offsetX: 0, offsetY: step, cost: cost }, + { offsetX: 0, offsetY: -step, cost: cost } + ]; + }, + + // a penalty received for direction change + penalties: function() { + + return { + 0: 0, + 45: this.step / 2, + 90: this.step / 2 + }; + }, + + // padding applied on the element bounding boxes + paddingBox: function() { + + var step = this.step; + + return { + x: -step, + y: -step, + width: 2 * step, + height: 2 * step + }; + }, + + // A function that determines whether a given point is an obstacle or not. + // If used, the `padding`, `excludeEnds`and `excludeTypes` options are ignored. + // (point: dia.Point) => boolean; + isPointObstacle: null, + + // a router to use when the manhattan router fails + // (one of the partial routes returns null) + fallbackRouter: function(vertices, opt, linkView) { + + if (!isFunction(orthogonal)) { + throw new Error('Manhattan requires the orthogonal router as default fallback.'); + } + + return orthogonal(vertices, utilHelpers_assign({}, manhattan_config, opt), linkView); + }, + + /* Deprecated */ + // a simple route used in situations when main routing method fails + // (exceed max number of loop iterations, inaccessible) + fallbackRoute: function(from, to, opt) { + + return null; // null result will trigger the fallbackRouter + + // left for reference: + /*// Find an orthogonal route ignoring obstacles. + + var point = ((opt.previousDirAngle || 0) % 180 === 0) + ? new g.Point(from.x, to.y) + : new g.Point(to.x, from.y); + + return [point];*/ + }, + + // if a function is provided, it's used to route the link while dragging an end + // i.e. function(from, to, opt) { return []; } + draggingRoute: null +}; + +// HELPER CLASSES // + +// Map of obstacles +// Helper structure to identify whether a point lies inside an obstacle. +function ObstacleMap(opt) { + + this.map = {}; + this.options = opt; + // tells how to divide the paper when creating the elements map + this.mapGridSize = 100; +} + +ObstacleMap.prototype.build = function(graph, link) { + + var opt = this.options; + + // source or target element could be excluded from set of obstacles + var excludedEnds = toArray(opt.excludeEnds).reduce(function(res, item) { + + var end = link.get(item); + if (end) { + var cell = graph.getCell(end.id); + if (cell) { + res.push(cell); + } + } + + return res; + }, []); + + // Exclude any embedded elements from the source and the target element. + var excludedAncestors = []; + + var source = graph.getCell(link.get('source').id); + if (source) { + excludedAncestors = union(excludedAncestors, source.getAncestors().map(function(cell) { + return cell.id; + })); + } + + var target = graph.getCell(link.get('target').id); + if (target) { + excludedAncestors = union(excludedAncestors, target.getAncestors().map(function(cell) { + return cell.id; + })); + } + + // Builds a map of all elements for quicker obstacle queries (i.e. is a point contained + // in any obstacle?) (a simplified grid search). + // The paper is divided into smaller cells, where each holds information about which + // elements belong to it. When we query whether a point lies inside an obstacle we + // don't need to go through all obstacles, we check only those in a particular cell. + var mapGridSize = this.mapGridSize; + + graph.getElements().reduce(function(map, element) { + + var isExcludedType = toArray(opt.excludeTypes).includes(element.get('type')); + var isExcludedEnd = excludedEnds.find(function(excluded) { + return excluded.id === element.id; + }); + var isExcludedAncestor = excludedAncestors.includes(element.id); + + var isExcluded = isExcludedType || isExcludedEnd || isExcludedAncestor; + if (!isExcluded) { + var bbox = element.getBBox().moveAndExpand(opt.paddingBox); + + var origin = bbox.origin().snapToGrid(mapGridSize); + var corner = bbox.corner().snapToGrid(mapGridSize); + + for (var x = origin.x; x <= corner.x; x += mapGridSize) { + for (var y = origin.y; y <= corner.y; y += mapGridSize) { + var gridKey = x + '@' + y; + map[gridKey] = map[gridKey] || []; + map[gridKey].push(bbox); + } + } + } + + return map; + }, this.map); + + return this; +}; + +ObstacleMap.prototype.isPointAccessible = function(point) { + + var mapKey = point.clone().snapToGrid(this.mapGridSize).toString(); + + return toArray(this.map[mapKey]).every(function(obstacle) { + return !obstacle.containsPoint(point); + }); +}; + +// Sorted Set +// Set of items sorted by given value. +function SortedSet() { + this.items = []; + this.hash = {}; + this.values = {}; + this.OPEN = 1; + this.CLOSE = 2; +} + +SortedSet.prototype.add = function(item, value) { + + if (this.hash[item]) { + // item removal + this.items.splice(this.items.indexOf(item), 1); + } else { + this.hash[item] = this.OPEN; + } + + this.values[item] = value; + + var index = sortedIndex(this.items, item, function(i) { + return this.values[i]; + }.bind(this)); + + this.items.splice(index, 0, item); +}; + +SortedSet.prototype.remove = function(item) { + + this.hash[item] = this.CLOSE; +}; + +SortedSet.prototype.isOpen = function(item) { + + return this.hash[item] === this.OPEN; +}; + +SortedSet.prototype.isClose = function(item) { + + return this.hash[item] === this.CLOSE; +}; + +SortedSet.prototype.isEmpty = function() { + + return this.items.length === 0; +}; + +SortedSet.prototype.pop = function() { + + var item = this.items.shift(); + this.remove(item); + return item; +}; + +// HELPERS // + +// return source bbox +function manhattan_getSourceBBox(linkView, opt) { + + // expand by padding box + if (opt && opt.paddingBox) return linkView.sourceBBox.clone().moveAndExpand(opt.paddingBox); + + return linkView.sourceBBox.clone(); +} + +// return target bbox +function manhattan_getTargetBBox(linkView, opt) { + + // expand by padding box + if (opt && opt.paddingBox) return linkView.targetBBox.clone().moveAndExpand(opt.paddingBox); + + return linkView.targetBBox.clone(); +} + +// return source anchor +function manhattan_getSourceAnchor(linkView, opt) { + + if (linkView.sourceAnchor) return linkView.sourceAnchor; + + // fallback: center of bbox + var sourceBBox = manhattan_getSourceBBox(linkView, opt); + return sourceBBox.center(); +} + +// return target anchor +function manhattan_getTargetAnchor(linkView, opt) { + + if (linkView.targetAnchor) return linkView.targetAnchor; + + // fallback: center of bbox + var targetBBox = manhattan_getTargetBBox(linkView, opt); + return targetBBox.center(); // default +} + +// returns a direction index from start point to end point +// corrects for grid deformation between start and end +function getDirectionAngle(start, end, numDirections, grid, opt) { + + var quadrant = 360 / numDirections; + var angleTheta = start.theta(fixAngleEnd(start, end, grid, opt)); + var normalizedAngle = normalizeAngle(angleTheta + (quadrant / 2)); + return quadrant * Math.floor(normalizedAngle / quadrant); +} + +// helper function for getDirectionAngle() +// corrects for grid deformation +// (if a point is one grid steps away from another in both dimensions, +// it is considered to be 45 degrees away, even if the real angle is different) +// this causes visible angle discrepancies if `opt.step` is much larger than `paper.gridSize` +function fixAngleEnd(start, end, grid, opt) { + + var step = opt.step; + + var diffX = end.x - start.x; + var diffY = end.y - start.y; + + var gridStepsX = diffX / grid.x; + var gridStepsY = diffY / grid.y; + + var distanceX = gridStepsX * step; + var distanceY = gridStepsY * step; + + return new Point(start.x + distanceX, start.y + distanceY); +} + +// return the change in direction between two direction angles +function getDirectionChange(angle1, angle2) { + + var directionChange = Math.abs(angle1 - angle2); + return (directionChange > 180) ? (360 - directionChange) : directionChange; +} + +// fix direction offsets according to current grid +function getGridOffsets(directions, grid, opt) { + + var step = opt.step; + + toArray(opt.directions).forEach(function(direction) { + + direction.gridOffsetX = (direction.offsetX / step) * grid.x; + direction.gridOffsetY = (direction.offsetY / step) * grid.y; + }); +} + +// get grid size in x and y dimensions, adapted to source and target positions +function getGrid(step, source, target) { + + return { + source: source.clone(), + x: getGridDimension(target.x - source.x, step), + y: getGridDimension(target.y - source.y, step) + }; +} + +// helper function for getGrid() +function getGridDimension(diff, step) { + + // return step if diff = 0 + if (!diff) return step; + + var absDiff = Math.abs(diff); + var numSteps = Math.round(absDiff / step); + + // return absDiff if less than one step apart + if (!numSteps) return absDiff; + + // otherwise, return corrected step + var roundedDiff = numSteps * step; + var remainder = absDiff - roundedDiff; + var stepCorrection = remainder / numSteps; + + return step + stepCorrection; +} + +// return a clone of point snapped to grid +function manhattan_snapToGrid(point, grid) { + + var source = grid.source; + + var snappedX = snapToGrid(point.x - source.x, grid.x) + source.x; + var snappedY = snapToGrid(point.y - source.y, grid.y) + source.y; + + return new Point(snappedX, snappedY); +} + +// round the point to opt.precision +function manhattan_round(point, precision) { + + return point.round(precision); +} + +// snap to grid and then round the point +function align(point, grid, precision) { + + return manhattan_round(manhattan_snapToGrid(point.clone(), grid), precision); +} + +// return a string representing the point +// string is rounded in both dimensions +function getKey(point) { + + return point.clone().toString(); +} + +// return a normalized vector from given point +// used to determine the direction of a difference of two points +function normalizePoint(point) { + + return new Point( + point.x === 0 ? 0 : Math.abs(point.x) / point.x, + point.y === 0 ? 0 : Math.abs(point.y) / point.y + ); +} + +// PATHFINDING // + +// reconstructs a route by concatenating points with their parents +function reconstructRoute(parents, points, tailPoint, from, to, grid, opt) { + + var route = []; + + var prevDiff = normalizePoint(to.difference(tailPoint)); + + // tailPoint is assumed to be aligned already + var currentKey = getKey(tailPoint); + var parent = parents[currentKey]; + + var point; + while (parent) { + + // point is assumed to be aligned already + point = points[currentKey]; + + var diff = normalizePoint(point.difference(parent)); + if (!diff.equals(prevDiff)) { + route.unshift(point); + prevDiff = diff; + } + + // parent is assumed to be aligned already + currentKey = getKey(parent); + parent = parents[currentKey]; + } + + // leadPoint is assumed to be aligned already + var leadPoint = points[currentKey]; + + var fromDiff = normalizePoint(leadPoint.difference(from)); + if (!fromDiff.equals(prevDiff)) { + route.unshift(leadPoint); + } + + return route; +} + +// heuristic method to determine the distance between two points +function estimateCost(from, endPoints) { + + var min = Infinity; + + for (var i = 0, len = endPoints.length; i < len; i++) { + var cost = from.manhattanDistance(endPoints[i]); + if (cost < min) min = cost; + } + + return min; +} + +// find points around the bbox taking given directions into account +// lines are drawn from anchor in given directions, intersections recorded +// if anchor is outside bbox, only those directions that intersect get a rect point +// the anchor itself is returned as rect point (representing some directions) +// (since those directions are unobstructed by the bbox) +function getRectPoints(anchor, bbox, directionList, grid, opt) { + + var precision = opt.precision; + var directionMap = opt.directionMap; + + var anchorCenterVector = anchor.difference(bbox.center()); + + var keys = isObject(directionMap) ? Object.keys(directionMap) : []; + var dirList = toArray(directionList); + var rectPoints = keys.reduce(function(res, key) { + + if (dirList.includes(key)) { + var direction = directionMap[key]; + + // create a line that is guaranteed to intersect the bbox if bbox is in the direction + // even if anchor lies outside of bbox + var endpoint = new Point( + anchor.x + direction.x * (Math.abs(anchorCenterVector.x) + bbox.width), + anchor.y + direction.y * (Math.abs(anchorCenterVector.y) + bbox.height) + ); + var intersectionLine = new Line(anchor, endpoint); + + // get the farther intersection, in case there are two + // (that happens if anchor lies next to bbox) + var intersections = intersectionLine.intersect(bbox) || []; + var numIntersections = intersections.length; + var farthestIntersectionDistance; + var farthestIntersection = null; + for (var i = 0; i < numIntersections; i++) { + var currentIntersection = intersections[i]; + var distance = anchor.squaredDistance(currentIntersection); + if ((farthestIntersectionDistance === undefined) || (distance > farthestIntersectionDistance)) { + farthestIntersectionDistance = distance; + farthestIntersection = currentIntersection; + } + } + + // if an intersection was found in this direction, it is our rectPoint + if (farthestIntersection) { + var point = align(farthestIntersection, grid, precision); + + // if the rectPoint lies inside the bbox, offset it by one more step + if (bbox.containsPoint(point)) { + point = align(point.offset(direction.x * grid.x, direction.y * grid.y), grid, precision); + } + + // then add the point to the result array + // aligned + res.push(point); + } + } + + return res; + }, []); + + // if anchor lies outside of bbox, add it to the array of points + if (!bbox.containsPoint(anchor)) { + // aligned + rectPoints.push(align(anchor, grid, precision)); + } + + return rectPoints; +} + +// finds the route between two points/rectangles (`from`, `to`) implementing A* algorithm +// rectangles get rect points assigned by getRectPoints() +function findRoute(from, to, isPointObstacle, opt) { + + var precision = opt.precision; + + // Get grid for this route. + + var sourceAnchor, targetAnchor; + + if (from instanceof Rect) { // `from` is sourceBBox + sourceAnchor = manhattan_round(manhattan_getSourceAnchor(this, opt).clone(), precision); + } else { + sourceAnchor = manhattan_round(from.clone(), precision); + } + + if (to instanceof Rect) { // `to` is targetBBox + targetAnchor = manhattan_round(manhattan_getTargetAnchor(this, opt).clone(), precision); + } else { + targetAnchor = manhattan_round(to.clone(), precision); + } + + var grid = getGrid(opt.step, sourceAnchor, targetAnchor); + + // Get pathfinding points. + + var start, end; // aligned with grid by definition + var startPoints, endPoints; // assumed to be aligned with grid already + + // set of points we start pathfinding from + if (from instanceof Rect) { // `from` is sourceBBox + start = sourceAnchor; + startPoints = getRectPoints(start, from, opt.startDirections, grid, opt); + + } else { + start = sourceAnchor; + startPoints = [start]; + } + + // set of points we want the pathfinding to finish at + if (to instanceof Rect) { // `to` is targetBBox + end = targetAnchor; + endPoints = getRectPoints(targetAnchor, to, opt.endDirections, grid, opt); + + } else { + end = targetAnchor; + endPoints = [end]; + } + + // take into account only accessible rect points (those not under obstacles) + startPoints = startPoints.filter(p => !isPointObstacle(p)); + endPoints = endPoints.filter(p => !isPointObstacle(p)); + + // Check that there is an accessible route point on both sides. + // Otherwise, use fallbackRoute(). + if (startPoints.length > 0 && endPoints.length > 0) { + + // The set of tentative points to be evaluated, initially containing the start points. + // Rounded to nearest integer for simplicity. + var openSet = new SortedSet(); + // Keeps reference to actual points for given elements of the open set. + var points = {}; + // Keeps reference to a point that is immediate predecessor of given element. + var parents = {}; + // Cost from start to a point along best known path. + var costs = {}; + + for (var i = 0, n = startPoints.length; i < n; i++) { + // startPoint is assumed to be aligned already + var startPoint = startPoints[i]; + + var key = getKey(startPoint); + + openSet.add(key, estimateCost(startPoint, endPoints)); + points[key] = startPoint; + costs[key] = 0; + } + + var previousRouteDirectionAngle = opt.previousDirectionAngle; // undefined for first route + var isPathBeginning = (previousRouteDirectionAngle === undefined); + + // directions + var direction, directionChange; + var directions = opt.directions; + getGridOffsets(directions, grid, opt); + + var numDirections = directions.length; + + var endPointsKeys = toArray(endPoints).reduce(function(res, endPoint) { + // endPoint is assumed to be aligned already + + var key = getKey(endPoint); + res.push(key); + return res; + }, []); + + // main route finding loop + var loopsRemaining = opt.maximumLoops; + while (!openSet.isEmpty() && loopsRemaining > 0) { + + // remove current from the open list + var currentKey = openSet.pop(); + var currentPoint = points[currentKey]; + var currentParent = parents[currentKey]; + var currentCost = costs[currentKey]; + + var isRouteBeginning = (currentParent === undefined); // undefined for route starts + var isStart = currentPoint.equals(start); // (is source anchor or `from` point) = can leave in any direction + + var previousDirectionAngle; + if (!isRouteBeginning) previousDirectionAngle = getDirectionAngle(currentParent, currentPoint, numDirections, grid, opt); // a vertex on the route + else if (!isPathBeginning) previousDirectionAngle = previousRouteDirectionAngle; // beginning of route on the path + else if (!isStart) previousDirectionAngle = getDirectionAngle(start, currentPoint, numDirections, grid, opt); // beginning of path, start rect point + else previousDirectionAngle = null; // beginning of path, source anchor or `from` point + + // check if we reached any endpoint + var samePoints = startPoints.length === endPoints.length; + if (samePoints) { + for (var j = 0; j < startPoints.length; j++) { + if (!startPoints[j].equals(endPoints[j])) { + samePoints = false; + break; + } + } + } + var skipEndCheck = (isRouteBeginning && samePoints); + if (!skipEndCheck && (endPointsKeys.indexOf(currentKey) >= 0)) { + opt.previousDirectionAngle = previousDirectionAngle; + return reconstructRoute(parents, points, currentPoint, start, end, grid, opt); + } + + // go over all possible directions and find neighbors + for (i = 0; i < numDirections; i++) { + direction = directions[i]; + + var directionAngle = direction.angle; + directionChange = getDirectionChange(previousDirectionAngle, directionAngle); + + // if the direction changed rapidly, don't use this point + // any direction is allowed for starting points + if (!(isPathBeginning && isStart) && directionChange > opt.maxAllowedDirectionChange) continue; + + var neighborPoint = align(currentPoint.clone().offset(direction.gridOffsetX, direction.gridOffsetY), grid, precision); + var neighborKey = getKey(neighborPoint); + + // Closed points from the openSet were already evaluated. + if (openSet.isClose(neighborKey) || isPointObstacle(neighborPoint)) continue; + + // We can only enter end points at an acceptable angle. + if (endPointsKeys.indexOf(neighborKey) >= 0) { // neighbor is an end point + + var isNeighborEnd = neighborPoint.equals(end); // (is target anchor or `to` point) = can be entered in any direction + + if (!isNeighborEnd) { + var endDirectionAngle = getDirectionAngle(neighborPoint, end, numDirections, grid, opt); + var endDirectionChange = getDirectionChange(directionAngle, endDirectionAngle); + + if (endDirectionChange > opt.maxAllowedDirectionChange) continue; + } + } + + // The current direction is ok. + + var neighborCost = direction.cost; + var neighborPenalty = isStart ? 0 : opt.penalties[directionChange]; // no penalties for start point + var costFromStart = currentCost + neighborCost + neighborPenalty; + + if (!openSet.isOpen(neighborKey) || (costFromStart < costs[neighborKey])) { + // neighbor point has not been processed yet + // or the cost of the path from start is lower than previously calculated + + points[neighborKey] = neighborPoint; + parents[neighborKey] = currentPoint; + costs[neighborKey] = costFromStart; + openSet.add(neighborKey, costFromStart + estimateCost(neighborPoint, endPoints)); + } + } + + loopsRemaining--; + } + } + + // no route found (`to` point either wasn't accessible or finding route took + // way too much calculation) + return opt.fallbackRoute.call(this, start, end, opt); +} + +// resolve some of the options +function resolveOptions(opt) { + + opt.directions = result(opt, 'directions'); + opt.penalties = result(opt, 'penalties'); + opt.paddingBox = result(opt, 'paddingBox'); + opt.padding = result(opt, 'padding'); + + if (opt.padding) { + // if both provided, opt.padding wins over opt.paddingBox + var sides = normalizeSides(opt.padding); + opt.paddingBox = { + x: -sides.left, + y: -sides.top, + width: sides.left + sides.right, + height: sides.top + sides.bottom + }; + } + + toArray(opt.directions).forEach(function(direction) { + + var point1 = new Point(0, 0); + var point2 = new Point(direction.offsetX, direction.offsetY); + + direction.angle = normalizeAngle(point1.theta(point2)); + }); +} + +// initialization of the route finding +function router(vertices, opt, linkView) { + + resolveOptions(opt); + + // enable/disable linkView perpendicular option + linkView.options.perpendicular = !!opt.perpendicular; + + var sourceBBox = manhattan_getSourceBBox(linkView, opt); + var targetBBox = manhattan_getTargetBBox(linkView, opt); + + var sourceAnchor = manhattan_getSourceAnchor(linkView, opt); + //var targetAnchor = getTargetAnchor(linkView, opt); + + // pathfinding + let isPointObstacle; + if (typeof opt.isPointObstacle === 'function') { + isPointObstacle = opt.isPointObstacle; + } else { + const map = new ObstacleMap(opt); + map.build(linkView.paper.model, linkView.model); + isPointObstacle = (point) => !map.isPointAccessible(point); + } + + var oldVertices = toArray(vertices).map(Point); + var newVertices = []; + var tailPoint = sourceAnchor; // the origin of first route's grid, does not need snapping + + // find a route by concatenating all partial routes (routes need to pass through vertices) + // source -> vertex[1] -> ... -> vertex[n] -> target + var to, from; + + for (var i = 0, len = oldVertices.length; i <= len; i++) { + + var partialRoute = null; + + from = to || sourceBBox; + to = oldVertices[i]; + + if (!to) { + // this is the last iteration + // we ran through all vertices in oldVertices + // 'to' is not a vertex. + + to = targetBBox; + + // If the target is a point (i.e. it's not an element), we + // should use dragging route instead of main routing method if it has been provided. + var isEndingAtPoint = !linkView.model.get('source').id || !linkView.model.get('target').id; + + if (isEndingAtPoint && isFunction(opt.draggingRoute)) { + // Make sure we are passing points only (not rects). + var dragFrom = (from === sourceBBox) ? sourceAnchor : from; + var dragTo = to.origin(); + + partialRoute = opt.draggingRoute.call(linkView, dragFrom, dragTo, opt); + } + } + + // if partial route has not been calculated yet use the main routing method to find one + partialRoute = partialRoute || findRoute.call(linkView, from, to, isPointObstacle, opt); + + if (partialRoute === null) { // the partial route cannot be found + return opt.fallbackRouter(vertices, opt, linkView); + } + + var leadPoint = partialRoute[0]; + + // remove the first point if the previous partial route had the same point as last + if (leadPoint && leadPoint.equals(tailPoint)) partialRoute.shift(); + + // save tailPoint for next iteration + tailPoint = partialRoute[partialRoute.length - 1] || tailPoint; + + Array.prototype.push.apply(newVertices, partialRoute); + } + + return newVertices; +} + +// public function +const manhattan = function(vertices, opt, linkView) { + return router(vertices, utilHelpers_assign({}, manhattan_config, opt), linkView); +}; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/metro.mjs + + + + +var metro_config = { + + maxAllowedDirectionChange: 45, + + // cost of a diagonal step + diagonalCost: function() { + + var step = this.step; + return Math.ceil(Math.sqrt(step * step << 1)); + }, + + // an array of directions to find next points on the route + // different from start/end directions + directions: function() { + + var step = this.step; + var cost = this.cost(); + var diagonalCost = this.diagonalCost(); + + return [ + { offsetX: step, offsetY: 0, cost: cost }, + { offsetX: step, offsetY: step, cost: diagonalCost }, + { offsetX: 0, offsetY: step, cost: cost }, + { offsetX: -step, offsetY: step, cost: diagonalCost }, + { offsetX: -step, offsetY: 0, cost: cost }, + { offsetX: -step, offsetY: -step, cost: diagonalCost }, + { offsetX: 0, offsetY: -step, cost: cost }, + { offsetX: step, offsetY: -step, cost: diagonalCost } + ]; + }, + + // a simple route used in situations when main routing method fails + // (exceed max number of loop iterations, inaccessible) + fallbackRoute: function(from, to, opt) { + + // Find a route which breaks by 45 degrees ignoring all obstacles. + + var theta = from.theta(to); + + var route = []; + + var a = { x: to.x, y: from.y }; + var b = { x: from.x, y: to.y }; + + if (theta % 180 > 90) { + var t = a; + a = b; + b = t; + } + + var p1 = (theta % 90) < 45 ? a : b; + var l1 = new Line(from, p1); + + var alpha = 90 * Math.ceil(theta / 90); + + var p2 = Point.fromPolar(l1.squaredLength(), toRad(alpha + 135), p1); + var l2 = new Line(to, p2); + + var intersectionPoint = l1.intersection(l2); + var point = intersectionPoint ? intersectionPoint : to; + + var directionFrom = intersectionPoint ? point : from; + + var quadrant = 360 / opt.directions.length; + var angleTheta = directionFrom.theta(to); + var normalizedAngle = normalizeAngle(angleTheta + (quadrant / 2)); + var directionAngle = quadrant * Math.floor(normalizedAngle / quadrant); + + opt.previousDirectionAngle = directionAngle; + + if (point) route.push(point.round()); + route.push(to); + + return route; + } +}; + +// public function +const metro = function(vertices, opt, linkView) { + + if (!isFunction(manhattan)) { + throw new Error('Metro requires the manhattan router.'); + } + + return manhattan(vertices, utilHelpers_assign({}, metro_config, opt), linkView); +}; + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/rightAngle.mjs + + +const rightAngle_Directions = { + AUTO: 'auto', + LEFT: 'left', + RIGHT: 'right', + TOP: 'top', + BOTTOM: 'bottom', + ANCHOR_SIDE: 'anchor-side', + MAGNET_SIDE: 'magnet-side' +}; + +const DEFINED_DIRECTIONS = [rightAngle_Directions.LEFT, rightAngle_Directions.RIGHT, rightAngle_Directions.TOP, rightAngle_Directions.BOTTOM]; + +function getDirectionForLinkConnection(linkOrigin, connectionPoint, linkView) { + const tangent = linkView.getTangentAtLength(linkView.getClosestPointLength(connectionPoint)); + const roundedAngle = Math.round(tangent.angle() / 90) * 90; + + switch (roundedAngle) { + case 0: + case 360: + return linkOrigin.y < connectionPoint.y ? rightAngle_Directions.TOP : rightAngle_Directions.BOTTOM; + case 90: + return linkOrigin.x < connectionPoint.x ? rightAngle_Directions.LEFT : rightAngle_Directions.RIGHT; + case 180: + return linkOrigin.y < connectionPoint.y ? rightAngle_Directions.TOP : rightAngle_Directions.BOTTOM; + case 270: + return linkOrigin.x < connectionPoint.x ? rightAngle_Directions.LEFT : rightAngle_Directions.RIGHT; + } +} + +function rightAngleRouter(_vertices, opt, linkView) { + const margin = opt.margin || 20; + let { sourceDirection = rightAngle_Directions.AUTO, targetDirection = rightAngle_Directions.AUTO } = opt; + + const sourceView = linkView.sourceView; + const targetView = linkView.targetView; + + const isSourcePort = !!linkView.model.source().port; + const isTargetPort = !!linkView.model.target().port; + + if (sourceDirection === rightAngle_Directions.AUTO) { + sourceDirection = isSourcePort ? rightAngle_Directions.MAGNET_SIDE : rightAngle_Directions.ANCHOR_SIDE; + } + + if (targetDirection === rightAngle_Directions.AUTO) { + targetDirection = isTargetPort ? rightAngle_Directions.MAGNET_SIDE : rightAngle_Directions.ANCHOR_SIDE; + } + + const sourceBBox = linkView.sourceBBox; + const targetBBox = linkView.targetBBox; + const sourcePoint = linkView.sourceAnchor; + const targetPoint = linkView.targetAnchor; + let { + x: sx0, + y: sy0, + width: sourceWidth = 0, + height: sourceHeight = 0 + } = sourceView && sourceView.model.isElement() ? Rect.fromRectUnion(sourceBBox, sourceView.model.getBBox()) : linkView.sourceAnchor; + + let { + x: tx0, + y: ty0, + width: targetWidth = 0, + height: targetHeight = 0 + } = targetView && targetView.model.isElement() ? Rect.fromRectUnion(targetBBox, targetView.model.getBBox()) : linkView.targetAnchor; + + const tx1 = tx0 + targetWidth; + const ty1 = ty0 + targetHeight; + const sx1 = sx0 + sourceWidth; + const sy1 = sy0 + sourceHeight; + + // Key coordinates including the margin + const smx0 = sx0 - margin; + const smx1 = sx1 + margin; + const smy0 = sy0 - margin; + const smy1 = sy1 + margin; + const tmx0 = tx0 - margin; + const tmx1 = tx1 + margin; + const tmy0 = ty0 - margin; + const tmy1 = ty1 + margin; + + const sourceOutsidePoint = sourcePoint.clone(); + + let sourceSide; + + if (!sourceView) { + const sourceLinkAnchorBBox = new Rect(sx0, sy0, 0, 0); + sourceSide = DEFINED_DIRECTIONS.includes(sourceDirection) ? sourceDirection : sourceLinkAnchorBBox.sideNearestToPoint(targetPoint); + } else if (sourceView.model.isLink()) { + sourceSide = getDirectionForLinkConnection(targetPoint, sourcePoint, sourceView); + } else if (sourceDirection === rightAngle_Directions.ANCHOR_SIDE) { + sourceSide = sourceBBox.sideNearestToPoint(sourcePoint); + } else if (sourceDirection === rightAngle_Directions.MAGNET_SIDE) { + sourceSide = sourceView.model.getBBox().sideNearestToPoint(sourcePoint); + } else { + sourceSide = sourceDirection; + } + + switch (sourceSide) { + case 'left': + sourceOutsidePoint.x = smx0; + break; + case 'right': + sourceOutsidePoint.x = smx1; + break; + case 'top': + sourceOutsidePoint.y = smy0; + break; + case 'bottom': + sourceOutsidePoint.y = smy1; + break; + } + const targetOutsidePoint = targetPoint.clone(); + + + let targetSide; + + + if (!targetView) { + const targetLinkAnchorBBox = new Rect(tx0, ty0, 0, 0); + targetSide = DEFINED_DIRECTIONS.includes(targetDirection) ? targetDirection : targetLinkAnchorBBox.sideNearestToPoint(sourcePoint); + } else if (targetView.model.isLink()) { + targetSide = getDirectionForLinkConnection(sourcePoint, targetPoint, targetView); + } else if (targetDirection === rightAngle_Directions.ANCHOR_SIDE) { + targetSide = targetBBox.sideNearestToPoint(targetPoint); + } else if (targetDirection === rightAngle_Directions.MAGNET_SIDE) { + targetSide = targetView.model.getBBox().sideNearestToPoint(targetPoint); + } else { + targetSide = targetDirection; + } + + switch (targetSide) { + case 'left': + targetOutsidePoint.x = tmx0; + break; + case 'right': + targetOutsidePoint.x = tmx1; + break; + case 'top': + targetOutsidePoint.y = tmy0; + break; + case 'bottom': + targetOutsidePoint.y = tmy1; + break; + } + + const { x: sox, y: soy } = sourceOutsidePoint; + const { x: tox, y: toy } = targetOutsidePoint; + const tcx = (tx0 + tx1) / 2; + const tcy = (ty0 + ty1) / 2; + const scx = (sx0 + sx1) / 2; + const scy = (sy0 + sy1) / 2; + const middleOfVerticalSides = (scx < tcx ? (sx1 + tx0) : (tx1 + sx0)) / 2; + const middleOfHorizontalSides = (scy < tcy ? (sy1 + ty0) : (ty1 + sy0)) / 2; + + if (sourceSide === 'left' && targetSide === 'right') { + if (smx0 <= tx1) { + let y = middleOfHorizontalSides; + if (sx1 <= tx0) { + if (ty1 >= smy0 && toy < soy) { + y = Math.min(tmy0, smy0); + } else if (ty0 <= smy1 && toy >= soy) { + y = Math.max(tmy1, smy1); + } + } + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + + const x = (sox + tox) / 2; + return [ + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'right' && targetSide === 'left') { + if (smx1 >= tx0) { + let y = middleOfHorizontalSides; + if (sox > tx1) { + if (ty1 >= smy0 && toy < soy) { + y = Math.min(tmy0, smy0); + } else if (ty0 <= smy1 && toy >= soy) { + y = Math.max(tmy1, smy1); + } + } + + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + + const x = (sox + tox) / 2; + return [ + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'top' && targetSide === 'bottom') { + if (soy < toy) { + let x = middleOfVerticalSides; + let y = soy; + + if (soy < ty0) { + if (tx1 >= smx0 && tox < sox) { + x = Math.min(tmx0, smx0); + } else if (tx0 <= smx1 && tox >= sox) { + x = Math.max(tmx1, smx1); + } + } + + return [ + { x: sox, y }, + { x, y }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + const y = (soy + toy) / 2; + return [ + { x: sox, y }, + { x: tox, y } + ]; + } else if (sourceSide === 'bottom' && targetSide === 'top') { + if (soy - margin > toy) { + let x = middleOfVerticalSides; + let y = soy; + + if (soy > ty1) { + if (tx1 >= smx0 && tox < sox) { + x = Math.min(tmx0, smx0); + } else if (tx0 <= smx1 && tox >= sox) { + x = Math.max(tmx1, smx1); + } + } + + return [ + { x: sox, y }, + { x, y }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + const y = (soy + toy) / 2; + return [ + { x: sox, y }, + { x: tox, y } + ]; + } else if (sourceSide === 'top' && targetSide === 'top') { + let x; + let y1 = Math.min((sy1 + ty0) / 2, toy); + let y2 = Math.min((sy0 + ty1) / 2, soy); + + if (toy < soy) { + if (sox >= tmx1 || sox <= tmx0) { + return [ + { x: sox, y: Math.min(soy,toy) }, + { x: tox, y: Math.min(soy,toy) } + ]; + } else if (tox > sox) { + x = Math.min(sox, tmx0); + } else { + x = Math.max(sox, tmx1); + } + } else { + if (tox >= smx1 || tox <= smx0) { + return [ + { x: sox, y: Math.min(soy,toy) }, + { x: tox, y: Math.min(soy,toy) } + ]; + } else if (tox >= sox) { + x = Math.max(tox, smx1); + } else { + x = Math.min(tox, smx0); + } + } + + return [ + { x: sox, y: y2 }, + { x, y: y2 }, + { x, y: y1 }, + { x: tox, y: y1 } + ]; + } else if (sourceSide === 'bottom' && targetSide === 'bottom') { + if (tx0 >= sox + margin || tx1 <= sox - margin) { + return [ + { x: sox, y: Math.max(soy, toy) }, + { x: tox, y: Math.max(soy, toy) } + ]; + } + + let x; + let y1; + let y2; + + if (toy > soy) { + y1 = Math.max((sy1 + ty0) / 2, toy); + y2 = Math.max((sy1 + ty0) / 2, soy); + + if (tox > sox) { + x = Math.min(sox, tmx0); + } else { + x = Math.max(sox, tmx1); + } + } else { + y1 = Math.max((sy0 + ty1) / 2, toy); + y2 = Math.max((sy0 + ty1) / 2, soy); + + if (tox > sox) { + x = Math.min(tox, smx0); + } else { + x = Math.max(tox, smx1); + } + } + + return [ + { x: sox, y: y2 }, + { x, y: y2 }, + { x, y: y1 }, + { x: tox, y: y1 } + ]; + } else if (sourceSide === 'left' && targetSide === 'left') { + let y; + let x1 = Math.min((sx1 + tx0) / 2, tox); + let x2 = Math.min((sx0 + tx1) / 2, sox); + + if (tox > sox) { + if (toy <= soy) { + y = Math.min(smy0, toy); + } else { + y = Math.max(smy1, toy); + } + } else { + if (toy >= soy) { + y = Math.min(tmy0, soy); + } else { + y = Math.max(tmy1, soy); + } + } + + return [ + { x: x2, y: soy }, + { x: x2, y }, + { x: x1, y }, + { x: x1, y: toy } + ]; + } else if (sourceSide === 'right' && targetSide === 'right') { + let y; + let x1 = Math.max((sx0 + tx1) / 2, tox); + let x2 = Math.max((sx1 + tx0) / 2, sox); + + if (tox < sox) { + if (toy <= soy) { + y = Math.min(smy0, toy); + } else { + y = Math.max(smy1, toy); + } + } else { + if (toy >= soy) { + y = Math.min(tmy0, soy); + } else { + y = Math.max(tmy1, soy); + } + } + + return [ + { x: x2, y: soy }, + { x: x2, y }, + { x: x1, y }, + { x: x1, y: toy } + ]; + } else if (sourceSide === 'top' && targetSide === 'right') { + if (soy > toy) { + if (sox < tox) { + let y = (sy0 + ty1) / 2; + if (y > tcy && y < tmy1 && sox < tmx0) { + y = tmy0; + } + return [ + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + return [{ x: sox, y: toy }]; + } + + const x = (sx0 + tx1) / 2; + + if (sox > tox && sy1 >= toy) { + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy }]; + } + + if (x > smx0 && soy < ty1) { + const y = Math.min(sy0, ty0) - margin; + const x = Math.max(sx1, tx1) + margin; + return [ + { x: sox, y }, + { x, y }, + { x, y: toy } + ]; + } + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'top' && targetSide === 'left') { + if (soy > toy) { + if (sox > tox) { + let y = (sy0 + ty1) / 2; + if (y > tcy && y < tmy1 && sox > tmx1) { + y = tmy0; + } + return [ + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + return [{ x: sox, y: toy }]; + } + + const x = (sx1 + tx0) / 2; + + if (sox < tox && sy1 >= toy) { + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy }]; + } + + if (x < smx1 && soy < ty1) { + const y = Math.min(sy0, ty0) - margin; + const x = Math.min(sx0, tx0) - margin; + return [ + { x: sox, y }, + { x, y }, + { x, y: toy } + ]; + } + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'bottom' && targetSide === 'right') { + if (soy < toy) { + if (sox < tox) { + let y = (sy1 + ty0) / 2; + if (y < tcy && y > tmy0 && sox < tmx0) { + y = tmy1; + } + return [ + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + return [{ x: sox, y: toy }]; + } else { + if (sx0 < tox) { + const y = Math.max(sy1, ty1) + margin; + const x = Math.max(sx1, tx1) + margin; + return [ + { x: sox, y }, + { x, y }, + { x, y: toy } + ]; + } + } + + const x = middleOfVerticalSides; + + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'bottom' && targetSide === 'left') { + if (soy < toy) { + if (sox > tox) { + let y = (sy1 + ty0) / 2; + if (y < tcy && y > tmy0 && sox > tmx1) { + y = tmy1; + } + return [ + { x: sox, y }, + { x: tox, y }, + { x: tox, y: toy } + ]; + } + return [{ x: sox, y: toy }]; + } else { + if (sx1 > tox) { + const y = Math.max(sy1, ty1) + margin; + const x = Math.min(sx0, tx0) - margin; + return [ + { x: sox, y }, + { x, y }, + { x, y: toy } + ]; + } + } + + const x = middleOfVerticalSides; + + return [ + { x: sox, y: soy }, + { x, y: soy }, + { x, y: toy } + ]; + } else if (sourceSide === 'left' && targetSide === 'bottom') { + if (sox > tox && soy >= tmy1) { + return [{ x: tox, y: soy }]; + } + + if (sox >= tx1 && soy < toy) { + const x = (sx1 + tx0) / 2; + return [ + { x, y: soy }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + + if (tox < sx1 && ty1 <= sy0) { + const y = (sy0 + ty1) / 2; + + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y } + ]; + } + + const x = Math.min(tmx0, sox); + const y = Math.max(sy1, ty1) + margin; + + return [ + { x, y: soy }, + { x, y }, + { x: tox, y } + ]; + } else if (sourceSide === 'left' && targetSide === 'top') { + if (sox > tox && soy < tmy0) { + return [{ x: tox, y: soy }]; + } + + if (sox >= tx1) { + if (soy > toy) { + const x = (sx0 + tx1) / 2; + return [ + { x, y: soy }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + } + + if (tox <= sx1 && toy > soy) { + const y = (ty0 + sy1) / 2; + + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y }, + ]; + } + + const x = toy < soy ? Math.min(sx0, tx0) - margin : smx0; + const y = Math.min(sy0, ty0) - margin; + + return [ + { x, y: soy }, + { x, y }, + { x: tox, y } + ]; + + } else if (sourceSide === 'right' && targetSide === 'top') { + if (sox < tox && soy < tmy0) { + return [{ x: tox, y: soy }]; + } + + if (sx1 < tx0 && soy > toy) { + let x = (sx1 + tx0) / 2; + return [ + { x, y: soy }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + + if (tox < sox && ty0 > sy1) { + const y = (sy1 + ty0) / 2; + + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y } + ]; + } + + const x = Math.max(sx1, tx1) + margin; + const y = Math.min(sy0, ty0) - margin; + return [ + { x, y: soy }, + { x, y }, + { x: tox, y } + ]; + } else if (sourceSide === 'right' && targetSide === 'bottom') { + if (sox < tox && soy >= tmy1) { + return [{ x: tox, y: soy }]; + } + + if (sox <= tx0 && soy < toy) { + const x = (sx1 + tx0) / 2; + return [ + { x, y: soy }, + { x, y: toy }, + { x: tox, y: toy } + ]; + } + + if (tox > sx0 && ty1 < sy0) { + const y = (sy0 + ty1) / 2; + + return [ + { x: sox, y: soy }, + { x: sox, y }, + { x: tox, y } + ]; + } + + const x = Math.max(tmx1, sox); + const y = Math.max(sy1, ty1) + margin; + + return [ + { x, y: soy }, + { x, y }, + { x: tox, y } + ]; + } +} + +rightAngleRouter.Directions = rightAngle_Directions; + +const rightAngle = rightAngleRouter; + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/routers/index.mjs + + + + + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/LinkView.mjs + + + + + + + + + +const LinkView_Flags = { + TOOLS: CellView.Flags.TOOLS, + RENDER: 'RENDER', + UPDATE: 'UPDATE', + LEGACY_TOOLS: 'LEGACY_TOOLS', + LABELS: 'LABELS', + VERTICES: 'VERTICES', + SOURCE: 'SOURCE', + TARGET: 'TARGET', + CONNECTOR: 'CONNECTOR' +}; + +// Link base view and controller. +// ---------------------------------------- + +const LinkView = CellView.extend({ + + className: function() { + + var classNames = CellView.prototype.className.apply(this).split(' '); + + classNames.push('link'); + + return classNames.join(' '); + }, + + options: { + + shortLinkLength: 105, + doubleLinkTools: false, + longLinkLength: 155, + linkToolsOffset: 40, + doubleLinkToolsOffset: 65, + sampleInterval: 50 + }, + + _labelCache: null, + _labelSelectors: null, + _markerCache: null, + _V: null, + _dragData: null, // deprecated + + metrics: null, + decimalsRounding: 2, + + initialize: function() { + + CellView.prototype.initialize.apply(this, arguments); + + // `_.labelCache` is a mapping of indexes of labels in the `this.get('labels')` array to + // `` nodes wrapped by Vectorizer. This allows for quick access to the + // nodes in `updateLabelPosition()` in order to update the label positions. + this._labelCache = {}; + + // a cache of label selectors + this._labelSelectors = {}; + + // keeps markers bboxes and positions again for quicker access + this._markerCache = {}; + + // cache of default markup nodes + this._V = {}; + + // connection path metrics + this.cleanNodesCache(); + }, + + presentationAttributes: { + markup: [LinkView_Flags.RENDER], + attrs: [LinkView_Flags.UPDATE], + router: [LinkView_Flags.UPDATE], + connector: [LinkView_Flags.CONNECTOR], + smooth: [LinkView_Flags.UPDATE], + manhattan: [LinkView_Flags.UPDATE], + toolMarkup: [LinkView_Flags.LEGACY_TOOLS], + labels: [LinkView_Flags.LABELS], + labelMarkup: [LinkView_Flags.LABELS], + vertices: [LinkView_Flags.VERTICES, LinkView_Flags.UPDATE], + vertexMarkup: [LinkView_Flags.VERTICES], + source: [LinkView_Flags.SOURCE, LinkView_Flags.UPDATE], + target: [LinkView_Flags.TARGET, LinkView_Flags.UPDATE] + }, + + initFlag: [LinkView_Flags.RENDER, LinkView_Flags.SOURCE, LinkView_Flags.TARGET, LinkView_Flags.TOOLS], + + UPDATE_PRIORITY: 1, + + confirmUpdate: function(flags, opt) { + + opt || (opt = {}); + + if (this.hasFlag(flags, LinkView_Flags.SOURCE)) { + if (!this.updateEndProperties('source')) return flags; + flags = this.removeFlag(flags, LinkView_Flags.SOURCE); + } + + if (this.hasFlag(flags, LinkView_Flags.TARGET)) { + if (!this.updateEndProperties('target')) return flags; + flags = this.removeFlag(flags, LinkView_Flags.TARGET); + } + + const { paper, sourceView, targetView } = this; + if (paper && ((sourceView && !paper.isViewMounted(sourceView)) || (targetView && !paper.isViewMounted(targetView)))) { + // Wait for the sourceView and targetView to be rendered + return flags; + } + + if (this.hasFlag(flags, LinkView_Flags.RENDER)) { + this.render(); + this.updateHighlighters(true); + this.updateTools(opt); + flags = this.removeFlag(flags, [LinkView_Flags.RENDER, LinkView_Flags.UPDATE, LinkView_Flags.VERTICES, LinkView_Flags.LABELS, LinkView_Flags.TOOLS, LinkView_Flags.LEGACY_TOOLS, LinkView_Flags.CONNECTOR]); + return flags; + } + + let updateHighlighters = false; + + if (this.hasFlag(flags, LinkView_Flags.VERTICES)) { + this.renderVertexMarkers(); + flags = this.removeFlag(flags, LinkView_Flags.VERTICES); + } + + const { model } = this; + const { attributes } = model; + let updateLabels = this.hasFlag(flags, LinkView_Flags.LABELS); + let updateLegacyTools = this.hasFlag(flags, LinkView_Flags.LEGACY_TOOLS); + + if (updateLabels) { + this.onLabelsChange(model, attributes.labels, opt); + flags = this.removeFlag(flags, LinkView_Flags.LABELS); + updateHighlighters = true; + } + + if (updateLegacyTools) { + this.renderTools(); + flags = this.removeFlag(flags, LinkView_Flags.LEGACY_TOOLS); + } + + const updateAll = this.hasFlag(flags, LinkView_Flags.UPDATE); + const updateConnector = this.hasFlag(flags, LinkView_Flags.CONNECTOR); + if (updateAll || updateConnector) { + if (!updateAll) { + // Keep the current route and update the geometry + this.updatePath(); + this.updateDOM(); + } else if (opt.translateBy && model.isRelationshipEmbeddedIn(opt.translateBy)) { + // The link is being translated by an ancestor that will + // shift source point, target point and all vertices + // by an equal distance. + this.translate(opt.tx, opt.ty); + } else { + this.update(); + } + this.updateTools(opt); + flags = this.removeFlag(flags, [LinkView_Flags.UPDATE, LinkView_Flags.TOOLS, LinkView_Flags.CONNECTOR]); + updateLabels = false; + updateLegacyTools = false; + updateHighlighters = true; + } + + if (updateLabels) { + this.updateLabelPositions(); + } + + if (updateLegacyTools) { + this.updateToolsPosition(); + } + + if (updateHighlighters) { + this.updateHighlighters(); + } + + if (this.hasFlag(flags, LinkView_Flags.TOOLS)) { + this.updateTools(opt); + flags = this.removeFlag(flags, LinkView_Flags.TOOLS); + } + + return flags; + }, + + requestConnectionUpdate: function(opt) { + this.requestUpdate(this.getFlag(LinkView_Flags.UPDATE), opt); + }, + + isLabelsRenderRequired: function(opt = {}) { + + const previousLabels = this.model.previous('labels'); + if (!previousLabels) return true; + + // Here is an optimization for cases when we know, that change does + // not require re-rendering of all labels. + if (('propertyPathArray' in opt) && ('propertyValue' in opt)) { + // The label is setting by `prop()` method + var pathArray = opt.propertyPathArray || []; + var pathLength = pathArray.length; + if (pathLength > 1) { + // We are changing a single label here e.g. 'labels/0/position' + var labelExists = !!previousLabels[pathArray[1]]; + if (labelExists) { + if (pathLength === 2) { + // We are changing the entire label. Need to check if the + // markup is also being changed. + return ('markup' in Object(opt.propertyValue)); + } else if (pathArray[2] !== 'markup') { + // We are changing a label property but not the markup + return false; + } + } + } + } + + return true; + }, + + onLabelsChange: function(_link, _labels, opt) { + + // Note: this optimization works in async=false mode only + if (this.isLabelsRenderRequired(opt)) { + this.renderLabels(); + } else { + this.updateLabels(); + } + }, + + // Rendering. + // ---------- + + render: function() { + + this.vel.empty(); + this.unmountLabels(); + this._V = {}; + this.renderMarkup(); + // rendering labels has to be run after the link is appended to DOM tree. (otherwise bbox + // returns zero values) + this.renderLabels(); + this.update(); + + return this; + }, + + renderMarkup: function() { + + var link = this.model; + var markup = link.get('markup') || link.markup; + if (!markup) throw new Error('dia.LinkView: markup required'); + if (Array.isArray(markup)) return this.renderJSONMarkup(markup); + if (typeof markup === 'string') return this.renderStringMarkup(markup); + throw new Error('dia.LinkView: invalid markup'); + }, + + renderJSONMarkup: function(markup) { + + var doc = this.parseDOMJSON(markup, this.el); + // Selectors + this.selectors = doc.selectors; + // Fragment + this.vel.append(doc.fragment); + }, + + renderStringMarkup: function(markup) { + + // A special markup can be given in the `properties.markup` property. This might be handy + // if e.g. arrowhead markers should be `` elements or any other element than ``s. + // `.connection`, `.connection-wrap`, `.marker-source` and `.marker-target` selectors + // of elements with special meaning though. Therefore, those classes should be preserved in any + // special markup passed in `properties.markup`. + var children = src_V(markup); + // custom markup may contain only one children + if (!Array.isArray(children)) children = [children]; + // Cache all children elements for quicker access. + var cache = this._V; // vectorized markup; + for (var i = 0, n = children.length; i < n; i++) { + var child = children[i]; + var className = child.attr('class'); + if (className) { + // Strip the joint class name prefix, if there is one. + className = removeClassNamePrefix(className); + cache[jquery.camelCase(className)] = child; + } + } + // partial rendering + this.renderTools(); + this.renderVertexMarkers(); + this.renderArrowheadMarkers(); + this.vel.append(children); + }, + + _getLabelMarkup: function(labelMarkup) { + + if (!labelMarkup) return undefined; + + if (Array.isArray(labelMarkup)) return this.parseDOMJSON(labelMarkup, null); + if (typeof labelMarkup === 'string') return this._getLabelStringMarkup(labelMarkup); + throw new Error('dia.linkView: invalid label markup'); + }, + + _getLabelStringMarkup: function(labelMarkup) { + + var children = src_V(labelMarkup); + var fragment = document.createDocumentFragment(); + + if (!Array.isArray(children)) { + fragment.appendChild(children.node); + + } else { + for (var i = 0, n = children.length; i < n; i++) { + var currentChild = children[i].node; + fragment.appendChild(currentChild); + } + } + + return { fragment: fragment, selectors: {}}; // no selectors + }, + + // Label markup fragment may come wrapped in , or not. + // If it doesn't, add the container here. + _normalizeLabelMarkup: function(markup) { + + if (!markup) return undefined; + + var fragment = markup.fragment; + if (!(markup.fragment instanceof DocumentFragment) || !markup.fragment.hasChildNodes()) throw new Error('dia.LinkView: invalid label markup.'); + + var vNode; + var childNodes = fragment.childNodes; + + if ((childNodes.length > 1) || childNodes[0].nodeName.toUpperCase() !== 'G') { + // default markup fragment is not wrapped in + // add a container + vNode = src_V('g').append(fragment); + } else { + vNode = src_V(childNodes[0]); + } + + vNode.addClass('label'); + + return { node: vNode.node, selectors: markup.selectors }; + }, + + renderLabels: function() { + + var cache = this._V; + var vLabels = cache.labels; + var labelCache = this._labelCache = {}; + var labelSelectors = this._labelSelectors = {}; + var model = this.model; + var labels = model.attributes.labels || []; + var labelsCount = labels.length; + + if (labelsCount === 0) { + if (vLabels) vLabels.remove(); + return this; + } + + if (vLabels) { + vLabels.empty(); + } else { + // there is no label container in the markup but some labels are defined + // add a container + vLabels = cache.labels = src_V('g').addClass('labels'); + if (this.options.labelsLayer) { + vLabels.addClass(addClassNamePrefix(result(this, 'className'))); + vLabels.attr('model-id', model.id); + } + } + + for (var i = 0; i < labelsCount; i++) { + + var label = labels[i]; + var labelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(label.markup)); + var labelNode; + var selectors; + if (labelMarkup) { + + labelNode = labelMarkup.node; + selectors = labelMarkup.selectors; + + } else { + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(builtinDefaultLabel.markup)); + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(defaultLabel.markup)); + var defaultMarkup = defaultLabelMarkup || builtinDefaultLabelMarkup; + + labelNode = defaultMarkup.node; + selectors = defaultMarkup.selectors; + } + + labelNode.setAttribute('label-idx', i); // assign label-idx + vLabels.append(labelNode); + labelCache[i] = labelNode; // cache node for `updateLabels()` so it can just update label node positions + + var rootSelector = this.selector; + if (selectors[rootSelector]) throw new Error('dia.LinkView: ambiguous label root selector.'); + selectors[rootSelector] = labelNode; + + labelSelectors[i] = selectors; // cache label selectors for `updateLabels()` + } + if (!vLabels.parent()) { + this.mountLabels(); + } + + this.updateLabels(); + + return this; + }, + + mountLabels: function() { + const { el, paper, model, _V, options } = this; + const { labels: vLabels } = _V; + if (!vLabels || !model.hasLabels()) return; + const { node } = vLabels; + if (options.labelsLayer) { + paper.getLayerView(options.labelsLayer).insertSortedNode(node, model.get('z')); + } else { + if (node.parentNode !== el) { + el.appendChild(node); + } + } + }, + + unmountLabels: function() { + const { options, _V } = this; + if (!_V) return; + const { labels: vLabels } = _V; + if (vLabels && options.labelsLayer) { + vLabels.remove(); + } + }, + + findLabelNode: function(labelIndex, selector) { + const labelRoot = this._labelCache[labelIndex]; + if (!labelRoot) return null; + const labelSelectors = this._labelSelectors[labelIndex]; + const [node = null] = this.findBySelector(selector, labelRoot, labelSelectors); + return node; + }, + + + // merge default label attrs into label attrs (or use built-in default label attrs if neither is provided) + // keep `undefined` or `null` because `{}` means something else + _mergeLabelAttrs: function(hasCustomMarkup, labelAttrs, defaultLabelAttrs, builtinDefaultLabelAttrs) { + + if (labelAttrs === null) return null; + if (labelAttrs === undefined) { + + if (defaultLabelAttrs === null) return null; + if (defaultLabelAttrs === undefined) { + + if (hasCustomMarkup) return undefined; + return builtinDefaultLabelAttrs; + } + + if (hasCustomMarkup) return defaultLabelAttrs; + return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs); + } + + if (hasCustomMarkup) return merge({}, defaultLabelAttrs, labelAttrs); + return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs, labelAttrs); + }, + + // merge default label size into label size (no built-in default) + // keep `undefined` or `null` because `{}` means something else + _mergeLabelSize: function(labelSize, defaultLabelSize) { + + if (labelSize === null) return null; + if (labelSize === undefined) { + + if (defaultLabelSize === null) return null; + if (defaultLabelSize === undefined) return undefined; + + return defaultLabelSize; + } + + return merge({}, defaultLabelSize, labelSize); + }, + + updateLabels: function() { + + if (!this._V.labels) return this; + + var model = this.model; + var labels = model.get('labels') || []; + var canLabelMove = this.can('labelMove'); + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelAttrs = builtinDefaultLabel.attrs; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = defaultLabel.markup; + var defaultLabelAttrs = defaultLabel.attrs; + var defaultLabelSize = defaultLabel.size; + + for (var i = 0, n = labels.length; i < n; i++) { + + var labelNode = this._labelCache[i]; + labelNode.setAttribute('cursor', (canLabelMove ? 'move' : 'default')); + + var selectors = this._labelSelectors[i]; + + var label = labels[i]; + var labelMarkup = label.markup; + var labelAttrs = label.attrs; + var labelSize = label.size; + + var attrs = this._mergeLabelAttrs( + (labelMarkup || defaultLabelMarkup), + labelAttrs, + defaultLabelAttrs, + builtinDefaultLabelAttrs + ); + + var size = this._mergeLabelSize( + labelSize, + defaultLabelSize + ); + + this.updateDOMSubtreeAttributes(labelNode, attrs, { + rootBBox: new Rect(size), + selectors: selectors + }); + } + + return this; + }, + + renderTools: function() { + + if (!this._V.linkTools) return this; + + // Tools are a group of clickable elements that manipulate the whole link. + // A good example of this is the remove tool that removes the whole link. + // Tools appear after hovering the link close to the `source` element/point of the link + // but are offset a bit so that they don't cover the `marker-arrowhead`. + + var $tools = jquery(this._V.linkTools.node).empty(); + var toolTemplate = template(this.model.get('toolMarkup') || this.model.toolMarkup); + var tool = src_V(toolTemplate()); + + $tools.append(tool.node); + + // Cache the tool node so that the `updateToolsPosition()` can update the tool position quickly. + this._toolCache = tool; + + // If `doubleLinkTools` is enabled, we render copy of the tools on the other side of the + // link as well but only if the link is longer than `longLinkLength`. + if (this.options.doubleLinkTools) { + + var tool2; + if (this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup) { + toolTemplate = template(this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup); + tool2 = src_V(toolTemplate()); + } else { + tool2 = tool.clone(); + } + + $tools.append(tool2.node); + this._tool2Cache = tool2; + } + + return this; + }, + + renderVertexMarkers: function() { + + if (!this._V.markerVertices) return this; + + var $markerVertices = jquery(this._V.markerVertices.node).empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = template(this.model.get('vertexMarkup') || this.model.vertexMarkup); + + this.model.vertices().forEach(function(vertex, idx) { + $markerVertices.append(src_V(markupTemplate(utilHelpers_assign({ idx: idx }, vertex))).node); + }); + + return this; + }, + + renderArrowheadMarkers: function() { + + // Custom markups might not have arrowhead markers. Therefore, jump of this function immediately if that's the case. + if (!this._V.markerArrowheads) return this; + + var $markerArrowheads = jquery(this._V.markerArrowheads.node); + + $markerArrowheads.empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = template(this.model.get('arrowheadMarkup') || this.model.arrowheadMarkup); + + this._V.sourceArrowhead = src_V(markupTemplate({ end: 'source' })); + this._V.targetArrowhead = src_V(markupTemplate({ end: 'target' })); + + $markerArrowheads.append(this._V.sourceArrowhead.node, this._V.targetArrowhead.node); + + return this; + }, + + // remove vertices that lie on (or nearly on) straight lines within the link + // return the number of removed points + removeRedundantLinearVertices: function(opt) { + + const SIMPLIFY_THRESHOLD = 0.001; + + const link = this.model; + const vertices = link.vertices(); + const routePoints = [this.sourceAnchor, ...vertices, this.targetAnchor]; + const numRoutePoints = routePoints.length; + + // put routePoints into a polyline and try to simplify + const polyline = new Polyline(routePoints); + polyline.simplify({ threshold: SIMPLIFY_THRESHOLD }); + const polylinePoints = polyline.points.map((point) => (point.toJSON())); // JSON of points after simplification + const numPolylinePoints = polylinePoints.length; // number of points after simplification + + // shortcut if simplification did not remove any redundant vertices: + if (numRoutePoints === numPolylinePoints) return 0; + + // else: set simplified polyline points as link vertices + // remove first and last polyline points again (= source/target anchors) + link.vertices(polylinePoints.slice(1, numPolylinePoints - 1), opt); + return (numRoutePoints - numPolylinePoints); + }, + + updateDefaultConnectionPath: function() { + + var cache = this._V; + + if (cache.connection) { + cache.connection.attr('d', this.getSerializedConnection()); + } + + if (cache.connectionWrap) { + cache.connectionWrap.attr('d', this.getSerializedConnection()); + } + + if (cache.markerSource && cache.markerTarget) { + this._translateAndAutoOrientArrows(cache.markerSource, cache.markerTarget); + } + }, + + getEndView: function(type) { + switch (type) { + case 'source': + return this.sourceView || null; + case 'target': + return this.targetView || null; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndAnchor: function(type) { + switch (type) { + case 'source': + return new Point(this.sourceAnchor); + case 'target': + return new Point(this.targetAnchor); + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndConnectionPoint: function(type) { + switch (type) { + case 'source': + return new Point(this.sourcePoint); + case 'target': + return new Point(this.targetPoint); + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndMagnet: function(type) { + switch (type) { + case 'source': + var sourceView = this.sourceView; + if (!sourceView) break; + return this.sourceMagnet || sourceView.el; + case 'target': + var targetView = this.targetView; + if (!targetView) break; + return this.targetMagnet || targetView.el; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + return null; + }, + + + // Updating. + // --------- + + update: function() { + this.updateRoute(); + this.updatePath(); + this.updateDOM(); + return this; + }, + + translate: function(tx = 0, ty = 0) { + const { route, path } = this; + if (!route || !path) return; + // translate the route + const polyline = new Polyline(route); + polyline.translate(tx, ty); + this.route = polyline.points; + // translate source and target connection and marker points. + this._translateConnectionPoints(tx, ty); + // translate the geometry path + path.translate(tx, ty); + this.updateDOM(); + }, + + updateDOM() { + const { el, model, selectors } = this; + this.cleanNodesCache(); + // update SVG attributes defined by 'attrs/'. + this.updateDOMSubtreeAttributes(el, model.attr(), { selectors }); + // legacy link path update + this.updateDefaultConnectionPath(); + // update the label position etc. + this.updateLabelPositions(); + this.updateToolsPosition(); + this.updateArrowheadMarkers(); + // *Deprecated* + // Local perpendicular flag (as opposed to one defined on paper). + // Could be enabled inside a connector/router. It's valid only + // during the update execution. + this.options.perpendicular = null; + }, + + updateRoute: function() { + const { model } = this; + const vertices = model.vertices(); + // 1. Find Anchors + const anchors = this.findAnchors(vertices); + const sourceAnchor = this.sourceAnchor = anchors.source; + const targetAnchor = this.targetAnchor = anchors.target; + // 2. Find Route + const route = this.findRoute(vertices); + this.route = route; + // 3. Find Connection Points + var connectionPoints = this.findConnectionPoints(route, sourceAnchor, targetAnchor); + this.sourcePoint = connectionPoints.source; + this.targetPoint = connectionPoints.target; + }, + + updatePath: function() { + const { route, sourcePoint, targetPoint } = this; + // 3b. Find Marker Connection Point - Backwards Compatibility + const markerPoints = this.findMarkerPoints(route, sourcePoint, targetPoint); + // 4. Find Connection + const path = this.findPath(route, markerPoints.source || sourcePoint, markerPoints.target || targetPoint); + this.path = path; + }, + + findMarkerPoints: function(route, sourcePoint, targetPoint) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + + // Move the source point by the width of the marker taking into account + // its scale around x-axis. Note that scale is the only transform that + // makes sense to be set in `.marker-source` attributes object + // as all other transforms (translate/rotate) will be replaced + // by the `translateAndAutoOrient()` function. + var cache = this._markerCache; + // cache source and target points + var sourceMarkerPoint, targetMarkerPoint; + + if (this._V.markerSource) { + + cache.sourceBBox = cache.sourceBBox || this._V.markerSource.getBBox(); + sourceMarkerPoint = Point(sourcePoint).move( + firstWaypoint || targetPoint, + cache.sourceBBox.width * this._V.markerSource.scale().sx * -1 + ).round(); + } + + if (this._V.markerTarget) { + + cache.targetBBox = cache.targetBBox || this._V.markerTarget.getBBox(); + targetMarkerPoint = Point(targetPoint).move( + lastWaypoint || sourcePoint, + cache.targetBBox.width * this._V.markerTarget.scale().sx * -1 + ).round(); + } + + // if there was no markup for the marker, use the connection point. + cache.sourcePoint = sourceMarkerPoint || sourcePoint.clone(); + cache.targetPoint = targetMarkerPoint || targetPoint.clone(); + + return { + source: sourceMarkerPoint, + target: targetMarkerPoint + }; + }, + + findAnchorsOrdered: function(firstEndType, firstRef, secondEndType, secondRef) { + + var firstAnchor, secondAnchor; + var firstAnchorRef, secondAnchorRef; + var model = this.model; + var firstDef = model.get(firstEndType); + var secondDef = model.get(secondEndType); + var firstView = this.getEndView(firstEndType); + var secondView = this.getEndView(secondEndType); + var firstMagnet = this.getEndMagnet(firstEndType); + var secondMagnet = this.getEndMagnet(secondEndType); + + // Anchor first + if (firstView) { + if (firstRef) { + firstAnchorRef = new Point(firstRef); + } else if (secondView) { + firstAnchorRef = secondMagnet; + } else { + firstAnchorRef = new Point(secondDef); + } + firstAnchor = this.getAnchor(firstDef.anchor, firstView, firstMagnet, firstAnchorRef, firstEndType); + } else { + firstAnchor = new Point(firstDef); + } + + // Anchor second + if (secondView) { + secondAnchorRef = new Point(secondRef || firstAnchor); + secondAnchor = this.getAnchor(secondDef.anchor, secondView, secondMagnet, secondAnchorRef, secondEndType); + } else { + secondAnchor = new Point(secondDef); + } + + var res = {}; + res[firstEndType] = firstAnchor; + res[secondEndType] = secondAnchor; + return res; + }, + + findAnchors: function(vertices) { + + var model = this.model; + var firstVertex = vertices[0]; + var lastVertex = vertices[vertices.length - 1]; + + if (model.target().priority && !model.source().priority) { + // Reversed order + return this.findAnchorsOrdered('target', lastVertex, 'source', firstVertex); + } + + // Usual order + return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex); + }, + + findConnectionPoints: function(route, sourceAnchor, targetAnchor) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + var model = this.model; + var sourceDef = model.get('source'); + var targetDef = model.get('target'); + var sourceView = this.sourceView; + var targetView = this.targetView; + var paperOptions = this.paper.options; + var sourceMagnet, targetMagnet; + + // Connection Point Source + var sourcePoint; + if (sourceView && !sourceView.isNodeConnection(this.sourceMagnet)) { + sourceMagnet = (this.sourceMagnet || sourceView.el); + var sourceConnectionPointDef = sourceDef.connectionPoint || paperOptions.defaultConnectionPoint; + var sourcePointRef = firstWaypoint || targetAnchor; + var sourceLine = new Line(sourcePointRef, sourceAnchor); + sourcePoint = this.getConnectionPoint( + sourceConnectionPointDef, + sourceView, + sourceMagnet, + sourceLine, + 'source' + ); + } else { + sourcePoint = sourceAnchor; + } + // Connection Point Target + var targetPoint; + if (targetView && !targetView.isNodeConnection(this.targetMagnet)) { + targetMagnet = (this.targetMagnet || targetView.el); + var targetConnectionPointDef = targetDef.connectionPoint || paperOptions.defaultConnectionPoint; + var targetPointRef = lastWaypoint || sourceAnchor; + var targetLine = new Line(targetPointRef, targetAnchor); + targetPoint = this.getConnectionPoint( + targetConnectionPointDef, + targetView, + targetMagnet, + targetLine, + 'target' + ); + } else { + targetPoint = targetAnchor; + } + + return { + source: sourcePoint, + target: targetPoint + }; + }, + + getAnchor: function(anchorDef, cellView, magnet, ref, endType) { + + var isConnection = cellView.isNodeConnection(magnet); + var paperOptions = this.paper.options; + if (!anchorDef) { + if (isConnection) { + anchorDef = paperOptions.defaultLinkAnchor; + } else { + if (paperOptions.perpendicularLinks || this.options.perpendicular) { + // Backwards compatibility + // If `perpendicularLinks` flag is set on the paper and there are vertices + // on the link, then try to find a connection point that makes the link perpendicular + // even though the link won't point to the center of the targeted object. + anchorDef = { name: 'perpendicular' }; + } else { + anchorDef = paperOptions.defaultAnchor; + } + } + } + + if (!anchorDef) throw new Error('Anchor required.'); + var anchorFn; + if (typeof anchorDef === 'function') { + anchorFn = anchorDef; + } else { + var anchorName = anchorDef.name; + var anchorNamespace = isConnection ? 'linkAnchorNamespace' : 'anchorNamespace'; + anchorFn = paperOptions[anchorNamespace][anchorName]; + if (typeof anchorFn !== 'function') throw new Error('Unknown anchor: ' + anchorName); + } + var anchor = anchorFn.call( + this, + cellView, + magnet, + ref, + anchorDef.args || {}, + endType, + this + ); + if (!anchor) return new Point(); + return anchor.round(this.decimalsRounding); + }, + + + getConnectionPoint: function(connectionPointDef, view, magnet, line, endType) { + + var connectionPoint; + var anchor = line.end; + var paperOptions = this.paper.options; + + // Backwards compatibility + if (typeof paperOptions.linkConnectionPoint === 'function') { + var linkConnectionMagnet = (magnet === view.el) ? undefined : magnet; + connectionPoint = paperOptions.linkConnectionPoint(this, view, linkConnectionMagnet, line.start, endType); + if (connectionPoint) return connectionPoint; + } + + if (!connectionPointDef) return anchor; + var connectionPointFn; + if (typeof connectionPointDef === 'function') { + connectionPointFn = connectionPointDef; + } else { + var connectionPointName = connectionPointDef.name; + connectionPointFn = paperOptions.connectionPointNamespace[connectionPointName]; + if (typeof connectionPointFn !== 'function') throw new Error('Unknown connection point: ' + connectionPointName); + } + connectionPoint = connectionPointFn.call(this, line, view, magnet, connectionPointDef.args || {}, endType, this); + if (!connectionPoint) return anchor; + return connectionPoint.round(this.decimalsRounding); + }, + + _translateConnectionPoints: function(tx, ty) { + + var cache = this._markerCache; + + cache.sourcePoint.offset(tx, ty); + cache.targetPoint.offset(tx, ty); + this.sourcePoint.offset(tx, ty); + this.targetPoint.offset(tx, ty); + this.sourceAnchor.offset(tx, ty); + this.targetAnchor.offset(tx, ty); + }, + + // combine default label position with built-in default label position + _getDefaultLabelPositionProperty: function() { + + var model = this.model; + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelPosition = builtinDefaultLabel.position; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelPosition = this._normalizeLabelPosition(defaultLabel.position); + + return merge({}, builtinDefaultLabelPosition, defaultLabelPosition); + }, + + // if label position is a number, normalize it to a position object + // this makes sure that label positions can be merged properly + _normalizeLabelPosition: function(labelPosition) { + + if (typeof labelPosition === 'number') return { distance: labelPosition, offset: null, angle: 0, args: null }; + return labelPosition; + }, + + // expects normalized position properties + // e.g. `this._normalizeLabelPosition(labelPosition)` and `this._getDefaultLabelPositionProperty()` + _mergeLabelPositionProperty: function(normalizedLabelPosition, normalizedDefaultLabelPosition) { + + if (normalizedLabelPosition === null) return null; + if (normalizedLabelPosition === undefined) { + + if (normalizedDefaultLabelPosition === null) return null; + return normalizedDefaultLabelPosition; + } + + return merge({}, normalizedDefaultLabelPosition, normalizedLabelPosition); + }, + + updateLabelPositions: function() { + + if (!this._V.labels) return this; + + var path = this.path; + if (!path) return this; + + // This method assumes all the label nodes are stored in the `this._labelCache` hash table + // by their indices in the `this.get('labels')` array. This is done in the `renderLabels()` method. + + var model = this.model; + var labels = model.get('labels') || []; + if (!labels.length) return this; + + var defaultLabelPosition = this._getDefaultLabelPositionProperty(); + + for (var idx = 0, n = labels.length; idx < n; idx++) { + var labelNode = this._labelCache[idx]; + if (!labelNode) continue; + var label = labels[idx]; + var labelPosition = this._normalizeLabelPosition(label.position); + var position = this._mergeLabelPositionProperty(labelPosition, defaultLabelPosition); + var transformationMatrix = this._getLabelTransformationMatrix(position); + labelNode.setAttribute('transform', src_V.matrixToTransformString(transformationMatrix)); + this._cleanLabelMatrices(idx); + } + + return this; + }, + + _cleanLabelMatrices: function(index) { + // Clean magnetMatrix for all nodes of the label. + // Cached BoundingRect does not need to updated when the position changes + // TODO: this doesn't work for labels with XML String markups. + const { metrics, _labelSelectors } = this; + const selectors = _labelSelectors[index]; + if (!selectors) return; + for (let selector in selectors) { + const { id } = selectors[selector]; + if (id && (id in metrics)) delete metrics[id].magnetMatrix; + } + }, + + updateToolsPosition: function() { + + if (!this._V.linkTools) return this; + + // Move the tools a bit to the target position but don't cover the `sourceArrowhead` marker. + // Note that the offset is hardcoded here. The offset should be always + // more than the `this.$('.marker-arrowhead[end="source"]')[0].bbox().width` but looking + // this up all the time would be slow. + + var scale = ''; + var offset = this.options.linkToolsOffset; + var connectionLength = this.getConnectionLength(); + + // Firefox returns connectionLength=NaN in odd cases (for bezier curves). + // In that case we won't update tools position at all. + if (!Number.isNaN(connectionLength)) { + + // If the link is too short, make the tools half the size and the offset twice as low. + if (connectionLength < this.options.shortLinkLength) { + scale = 'scale(.5)'; + offset /= 2; + } + + var toolPosition = this.getPointAtLength(offset); + + this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + + if (this.options.doubleLinkTools && connectionLength >= this.options.longLinkLength) { + + var doubleLinkToolsOffset = this.options.doubleLinkToolsOffset || offset; + + toolPosition = this.getPointAtLength(connectionLength - doubleLinkToolsOffset); + this._tool2Cache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + this._tool2Cache.attr('visibility', 'visible'); + + } else if (this.options.doubleLinkTools) { + + this._tool2Cache.attr('visibility', 'hidden'); + } + } + + return this; + }, + + updateArrowheadMarkers: function() { + + if (!this._V.markerArrowheads) return this; + + // getting bbox of an element with `display="none"` in IE9 ends up with access violation + if (jquery.css(this._V.markerArrowheads.node, 'display') === 'none') return this; + + var sx = this.getConnectionLength() < this.options.shortLinkLength ? .5 : 1; + this._V.sourceArrowhead.scale(sx); + this._V.targetArrowhead.scale(sx); + + this._translateAndAutoOrientArrows(this._V.sourceArrowhead, this._V.targetArrowhead); + + return this; + }, + + updateEndProperties: function(endType) { + + const { model, paper } = this; + const endViewProperty = `${endType}View`; + const endDef = model.get(endType); + const endId = endDef && endDef.id; + + if (!endId) { + // the link end is a point ~ rect 0x0 + this[endViewProperty] = null; + this.updateEndMagnet(endType); + return true; + } + + const endModel = paper.getModelById(endId); + if (!endModel) throw new Error('LinkView: invalid ' + endType + ' cell.'); + + const endView = endModel.findView(paper); + if (!endView) { + // A view for a model should always exist + return false; + } + + this[endViewProperty] = endView; + this.updateEndMagnet(endType); + return true; + }, + + updateEndMagnet: function(endType) { + + const endMagnetProperty = `${endType}Magnet`; + const endView = this.getEndView(endType); + if (endView) { + let connectedMagnet = endView.getMagnetFromLinkEnd(this.model.get(endType)); + if (connectedMagnet === endView.el) connectedMagnet = null; + this[endMagnetProperty] = connectedMagnet; + } else { + this[endMagnetProperty] = null; + } + }, + + _translateAndAutoOrientArrows: function(sourceArrow, targetArrow) { + + // Make the markers "point" to their sticky points being auto-oriented towards + // `targetPosition`/`sourcePosition`. And do so only if there is a markup for them. + var route = toArray(this.route); + if (sourceArrow) { + sourceArrow.translateAndAutoOrient( + this.sourcePoint, + route[0] || this.targetPoint, + this.paper.cells + ); + } + + if (targetArrow) { + targetArrow.translateAndAutoOrient( + this.targetPoint, + route[route.length - 1] || this.sourcePoint, + this.paper.cells + ); + } + }, + + _getLabelPositionProperty: function(idx) { + + return (this.model.label(idx).position || {}); + }, + + _getLabelPositionAngle: function(idx) { + + var labelPosition = this._getLabelPositionProperty(idx); + return (labelPosition.angle || 0); + }, + + _getLabelPositionArgs: function(idx) { + + var labelPosition = this._getLabelPositionProperty(idx); + return labelPosition.args; + }, + + _getDefaultLabelPositionArgs: function() { + + var defaultLabel = this.model._getDefaultLabel(); + var defaultLabelPosition = defaultLabel.position || {}; + return defaultLabelPosition.args; + }, + + // merge default label position args into label position args + // keep `undefined` or `null` because `{}` means something else + _mergeLabelPositionArgs: function(labelPositionArgs, defaultLabelPositionArgs) { + + if (labelPositionArgs === null) return null; + if (labelPositionArgs === undefined) { + + if (defaultLabelPositionArgs === null) return null; + return defaultLabelPositionArgs; + } + + return merge({}, defaultLabelPositionArgs, labelPositionArgs); + }, + + // Add default label at given position at end of `labels` array. + // Four signatures: + // - obj, obj = point, opt + // - obj, num, obj = point, angle, opt + // - num, num, obj = x, y, opt + // - num, num, num, obj = x, y, angle, opt + // Assigns relative coordinates by default: + // `opt.absoluteDistance` forces absolute coordinates. + // `opt.reverseDistance` forces reverse absolute coordinates (if absoluteDistance = true). + // `opt.absoluteOffset` forces absolute coordinates for offset. + // Additional args: + // `opt.keepGradient` auto-adjusts the angle of the label to match path gradient at position. + // `opt.ensureLegibility` rotates labels so they are never upside-down. + addLabel: function(p1, p2, p3, p4) { + + // normalize data from the four possible signatures + var localX; + var localY; + var localAngle = 0; + var localOpt; + if (typeof p1 !== 'number') { + // {x, y} object provided as first parameter + localX = p1.x; + localY = p1.y; + if (typeof p2 === 'number') { + // angle and opt provided as second and third parameters + localAngle = p2; + localOpt = p3; + } else { + // opt provided as second parameter + localOpt = p2; + } + } else { + // x and y provided as first and second parameters + localX = p1; + localY = p2; + if (typeof p3 === 'number') { + // angle and opt provided as third and fourth parameters + localAngle = p3; + localOpt = p4; + } else { + // opt provided as third parameter + localOpt = p3; + } + } + + // merge label position arguments + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var labelPositionArgs = localOpt; + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + // append label to labels array + var label = { position: this.getLabelPosition(localX, localY, localAngle, positionArgs) }; + var idx = -1; + this.model.insertLabel(idx, label, localOpt); + return idx; + }, + + // Add a new vertex at calculated index to the `vertices` array. + addVertex: function(x, y, opt) { + + // accept input in form `{ x, y }, opt` or `x, y, opt` + var isPointProvided = (typeof x !== 'number'); + var localX = isPointProvided ? x.x : x; + var localY = isPointProvided ? x.y : y; + var localOpt = isPointProvided ? y : opt; + + var vertex = { x: localX, y: localY }; + var idx = this.getVertexIndex(localX, localY); + this.model.insertVertex(idx, vertex, localOpt); + return idx; + }, + + // Send a token (an SVG element, usually a circle) along the connection path. + // Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)` + // `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`. + // `opt.directon` is optional and it determines whether the token goes from source to target or other way round (`reverse`) + // `opt.connection` is an optional selector to the connection path. + // `callback` is optional and is a function to be called once the token reaches the target. + sendToken: function(token, opt, callback) { + + function onAnimationEnd(vToken, callback) { + return function() { + vToken.remove(); + if (typeof callback === 'function') { + callback(); + } + }; + } + + var duration, isReversed, selector; + if (isObject(opt)) { + duration = opt.duration; + isReversed = (opt.direction === 'reverse'); + selector = opt.connection; + } else { + // Backwards compatibility + duration = opt; + isReversed = false; + selector = null; + } + + duration = duration || 1000; + + var animationAttributes = { + dur: duration + 'ms', + repeatCount: 1, + calcMode: 'linear', + fill: 'freeze' + }; + + if (isReversed) { + animationAttributes.keyPoints = '1;0'; + animationAttributes.keyTimes = '0;1'; + } + + var vToken = src_V(token); + var connection; + if (typeof selector === 'string') { + // Use custom connection path. + connection = this.findBySelector(selector, this.el, this.selectors)[0]; + } else { + // Select connection path automatically. + var cache = this._V; + connection = (cache.connection) ? cache.connection.node : this.el.querySelector('path'); + } + + if (!(connection instanceof SVGPathElement)) { + throw new Error('dia.LinkView: token animation requires a valid connection path.'); + } + + vToken + .appendTo(this.paper.cells) + .animateAlongPath(animationAttributes, connection); + + setTimeout(onAnimationEnd(vToken, callback), duration); + }, + + findRoute: function(vertices) { + + vertices || (vertices = []); + + var namespace = this.paper.options.routerNamespace || routers_namespaceObject; + var router = this.model.router(); + var defaultRouter = this.paper.options.defaultRouter; + + if (!router) { + if (defaultRouter) router = defaultRouter; + else return vertices.map(Point); // no router specified + } + + var routerFn = isFunction(router) ? router : namespace[router.name]; + if (!isFunction(routerFn)) { + throw new Error('dia.LinkView: unknown router: "' + router.name + '".'); + } + + var args = router.args || {}; + + var route = routerFn.call( + this, // context + vertices, // vertices + args, // options + this // linkView + ); + + if (!route) return vertices.map(Point); + return route; + }, + + // Return the `d` attribute value of the `` element representing the link + // between `source` and `target`. + findPath: function(route, sourcePoint, targetPoint) { + + var namespace = this.paper.options.connectorNamespace || connectors_namespaceObject; + var connector = this.model.connector(); + var defaultConnector = this.paper.options.defaultConnector; + + if (!connector) { + connector = defaultConnector || {}; + } + + var connectorFn = isFunction(connector) ? connector : namespace[connector.name]; + if (!isFunction(connectorFn)) { + throw new Error('dia.LinkView: unknown connector: "' + connector.name + '".'); + } + + var args = clone(connector.args || {}); + args.raw = true; // Request raw g.Path as the result. + + var path = connectorFn.call( + this, // context + sourcePoint, // start point + targetPoint, // end point + route, // vertices + args, // options + this // linkView + ); + + if (typeof path === 'string') { + // Backwards compatibility for connectors not supporting `raw` option. + path = new Path(src_V.normalizePathData(path)); + } + + return path; + }, + + // Public API. + // ----------- + + getConnection: function() { + + var path = this.path; + if (!path) return null; + + return path.clone(); + }, + + getSerializedConnection: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('data')) return metrics.data; + var data = path.serialize(); + metrics.data = data; + return data; + }, + + getConnectionSubdivisions: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('segmentSubdivisions')) return metrics.segmentSubdivisions; + var subdivisions = path.getSegmentSubdivisions(); + metrics.segmentSubdivisions = subdivisions; + return subdivisions; + }, + + getConnectionLength: function() { + + var path = this.path; + if (!path) return 0; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('length')) return metrics.length; + var length = path.length({ segmentSubdivisions: this.getConnectionSubdivisions() }); + metrics.length = length; + return length; + }, + + getPointAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.pointAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getPointAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + if (isPercentage(ratio)) ratio = parseFloat(ratio) / 100; + return path.pointAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.tangentAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + + return path.tangentAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPoint: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPoint(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointLength: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointRatio: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointNormalizedLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + // Get label position object based on two provided coordinates, x and y. + // (Used behind the scenes when user moves labels around.) + // Two signatures: + // - num, num, obj = x, y, options + // - num, num, num, obj = x, y, angle, options + // Accepts distance/offset options = `absoluteDistance: boolean`, `reverseDistance: boolean`, `absoluteOffset: boolean` + // - `absoluteOffset` is necessary in order to move beyond connection endpoints + // Additional options = `keepGradient: boolean`, `ensureLegibility: boolean` + getLabelPosition: function(x, y, p3, p4) { + + var position = {}; + + // normalize data from the two possible signatures + var localAngle = 0; + var localOpt; + if (typeof p3 === 'number') { + // angle and opt provided as third and fourth argument + localAngle = p3; + localOpt = p4; + } else { + // opt provided as third argument + localOpt = p3; + } + + // save localOpt as `args` of the position object that is passed along + if (localOpt) position.args = localOpt; + + // identify distance/offset settings + var isDistanceRelative = !(localOpt && localOpt.absoluteDistance); // relative by default + var isDistanceAbsoluteReverse = (localOpt && localOpt.absoluteDistance && localOpt.reverseDistance); // non-reverse by default + var isOffsetAbsolute = localOpt && localOpt.absoluteOffset; // offset is non-absolute by default + + // find closest point t + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + var labelPoint = new Point(x, y); + var t = path.closestPointT(labelPoint, pathOpt); + + // DISTANCE: + var labelDistance = path.lengthAtT(t, pathOpt); + if (isDistanceRelative) labelDistance = (labelDistance / this.getConnectionLength()) || 0; // fix to prevent NaN for 0 length + if (isDistanceAbsoluteReverse) labelDistance = (-1 * (this.getConnectionLength() - labelDistance)) || 1; // fix for end point (-0 => 1) + position.distance = labelDistance; + + // OFFSET: + // use absolute offset if: + // - opt.absoluteOffset is true, + // - opt.absoluteOffset is not true but there is no tangent + var tangent; + if (!isOffsetAbsolute) tangent = path.tangentAtT(t); + var labelOffset; + if (tangent) { + labelOffset = tangent.pointOffset(labelPoint); + } else { + var closestPoint = path.pointAtT(t); + var labelOffsetDiff = labelPoint.difference(closestPoint); + labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y }; + } + position.offset = labelOffset; + + // ANGLE: + position.angle = localAngle; + + return position; + }, + + _getLabelTransformationMatrix: function(labelPosition) { + + var labelDistance; + var labelAngle = 0; + var args = {}; + if (typeof labelPosition === 'number') { + labelDistance = labelPosition; + } else if (typeof labelPosition.distance === 'number') { + args = labelPosition.args || {}; + labelDistance = labelPosition.distance; + labelAngle = labelPosition.angle || 0; + } else { + throw new Error('dia.LinkView: invalid label position distance.'); + } + + var isDistanceRelative = ((labelDistance > 0) && (labelDistance <= 1)); + + var labelOffset = 0; + var labelOffsetCoordinates = { x: 0, y: 0 }; + if (labelPosition.offset) { + var positionOffset = labelPosition.offset; + if (typeof positionOffset === 'number') labelOffset = positionOffset; + if (positionOffset.x) labelOffsetCoordinates.x = positionOffset.x; + if (positionOffset.y) labelOffsetCoordinates.y = positionOffset.y; + } + + var isOffsetAbsolute = ((labelOffsetCoordinates.x !== 0) || (labelOffsetCoordinates.y !== 0) || labelOffset === 0); + + var isKeepGradient = args.keepGradient; + var isEnsureLegibility = args.ensureLegibility; + + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + + var distance = isDistanceRelative ? (labelDistance * this.getConnectionLength()) : labelDistance; + var tangent = path.tangentAtLength(distance, pathOpt); + + var translation; + var angle = labelAngle; + if (tangent) { + if (isOffsetAbsolute) { + translation = tangent.start.clone(); + translation.offset(labelOffsetCoordinates); + } else { + var normal = tangent.clone(); + normal.rotate(tangent.start, -90); + normal.setLength(labelOffset); + translation = normal.end; + } + + if (isKeepGradient) { + angle = (tangent.angle() + labelAngle); + if (isEnsureLegibility) { + angle = normalizeAngle(((angle + 90) % 180) - 90); + } + } + + } else { + // fallback - the connection has zero length + translation = path.start.clone(); + if (isOffsetAbsolute) translation.offset(labelOffsetCoordinates); + } + + return src_V.createSVGMatrix() + .translate(translation.x, translation.y) + .rotate(angle); + }, + + getLabelCoordinates: function(labelPosition) { + + var transformationMatrix = this._getLabelTransformationMatrix(labelPosition); + return new Point(transformationMatrix.e, transformationMatrix.f); + }, + + getVertexIndex: function(x, y) { + + var model = this.model; + var vertices = model.vertices(); + + var vertexLength = this.getClosestPointLength(new Point(x, y)); + + var idx = 0; + for (var n = vertices.length; idx < n; idx++) { + var currentVertex = vertices[idx]; + var currentVertexLength = this.getClosestPointLength(currentVertex); + if (vertexLength < currentVertexLength) break; + } + + return idx; + }, + + // Interaction. The controller part. + // --------------------------------- + + notifyPointerdown(evt, x, y) { + CellView.prototype.pointerdown.call(this, evt, x, y); + this.notify('link:pointerdown', evt, x, y); + }, + + notifyPointermove(evt, x, y) { + CellView.prototype.pointermove.call(this, evt, x, y); + this.notify('link:pointermove', evt, x, y); + }, + + notifyPointerup(evt, x, y) { + this.notify('link:pointerup', evt, x, y); + CellView.prototype.pointerup.call(this, evt, x, y); + }, + + pointerdblclick: function(evt, x, y) { + + CellView.prototype.pointerdblclick.apply(this, arguments); + this.notify('link:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + CellView.prototype.pointerclick.apply(this, arguments); + this.notify('link:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + CellView.prototype.contextmenu.apply(this, arguments); + this.notify('link:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + this.notifyPointerdown(evt, x, y); + + // Backwards compatibility for the default markup + var className = evt.target.getAttribute('class'); + switch (className) { + + case 'marker-vertex': + this.dragVertexStart(evt, x, y); + return; + + case 'marker-vertex-remove': + case 'marker-vertex-remove-area': + this.dragVertexRemoveStart(evt, x, y); + return; + + case 'marker-arrowhead': + this.dragArrowheadStart(evt, x, y); + return; + + case 'connection': + case 'connection-wrap': + this.dragConnectionStart(evt, x, y); + return; + + case 'marker-source': + case 'marker-target': + return; + } + + this.dragStart(evt, x, y); + }, + + pointermove: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) this.eventData(evt, dragData); + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertex(evt, x, y); + break; + + case 'label-move': + this.dragLabel(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowhead(evt, x, y); + break; + + case 'move': + this.drag(evt, x, y); + break; + } + + // Backwards compatibility + if (dragData) utilHelpers_assign(dragData, this.eventData(evt)); + + this.notifyPointermove(evt, x, y); + }, + + pointerup: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) { + this.eventData(evt, dragData); + this._dragData = null; + } + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertexEnd(evt, x, y); + break; + + case 'label-move': + this.dragLabelEnd(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowheadEnd(evt, x, y); + break; + + case 'move': + this.dragEnd(evt, x, y); + } + + this.notifyPointerup(evt, x, y); + this.checkMouseleave(evt); + }, + + mouseover: function(evt) { + + CellView.prototype.mouseover.apply(this, arguments); + this.notify('link:mouseover', evt); + }, + + mouseout: function(evt) { + + CellView.prototype.mouseout.apply(this, arguments); + this.notify('link:mouseout', evt); + }, + + mouseenter: function(evt) { + + CellView.prototype.mouseenter.apply(this, arguments); + this.notify('link:mouseenter', evt); + }, + + mouseleave: function(evt) { + + CellView.prototype.mouseleave.apply(this, arguments); + this.notify('link:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + CellView.prototype.mousewheel.apply(this, arguments); + this.notify('link:mousewheel', evt, x, y, delta); + }, + + onevent: function(evt, eventName, x, y) { + + // Backwards compatibility + var linkTool = src_V(evt.target).findParentByClass('link-tool', this.el); + if (linkTool) { + // No further action to be executed + evt.stopPropagation(); + + // Allow `interactive.useLinkTools=false` + if (this.can('useLinkTools')) { + if (eventName === 'remove') { + // Built-in remove event + this.model.remove({ ui: true }); + // Do not trigger link pointerdown + return; + + } else { + // link:options and other custom events inside the link tools + this.notify(eventName, evt, x, y); + } + } + + this.notifyPointerdown(evt, x, y); + this.paper.delegateDragEvents(this, evt.data); + + } else { + CellView.prototype.onevent.apply(this, arguments); + } + }, + + onlabel: function(evt, x, y) { + + this.notifyPointerdown(evt, x, y); + + this.dragLabelStart(evt, x, y); + + var stopPropagation = this.eventData(evt).stopPropagation; + if (stopPropagation) evt.stopPropagation(); + }, + + // Drag Start Handlers + + dragConnectionStart: function(evt, x, y) { + + if (!this.can('vertexAdd')) return; + + // Store the index at which the new vertex has just been placed. + // We'll be update the very same vertex position in `pointermove()`. + var vertexIdx = this.addVertex({ x: x, y: y }, { ui: true }); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragLabelStart: function(evt, x, y) { + + if (this.can('labelMove')) { + + if (this.isDefaultInteractionPrevented(evt)) return; + + var labelNode = evt.currentTarget; + var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10); + + var defaultLabelPosition = this._getDefaultLabelPositionProperty(); + var initialLabelPosition = this._normalizeLabelPosition(this._getLabelPositionProperty(labelIdx)); + var position = this._mergeLabelPositionProperty(initialLabelPosition, defaultLabelPosition); + + var coords = this.getLabelCoordinates(position); + var dx = coords.x - x; // how much needs to be added to cursor x to get to label x + var dy = coords.y - y; // how much needs to be added to cursor y to get to label y + + var positionAngle = this._getLabelPositionAngle(labelIdx); + var labelPositionArgs = this._getLabelPositionArgs(labelIdx); + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + this.eventData(evt, { + action: 'label-move', + labelIdx: labelIdx, + dx: dx, + dy: dy, + positionAngle: positionAngle, + positionArgs: positionArgs, + stopPropagation: true + }); + + } else { + + // Backwards compatibility: + // If labels can't be dragged no default action is triggered. + this.eventData(evt, { stopPropagation: true }); + } + + this.paper.delegateDragEvents(this, evt.data); + }, + + dragVertexStart: function(evt, x, y) { + + if (!this.can('vertexMove')) return; + + var vertexNode = evt.target; + var vertexIdx = parseInt(vertexNode.getAttribute('idx'), 10); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragVertexRemoveStart: function(evt, x, y) { + + if (!this.can('vertexRemove')) return; + + var removeNode = evt.target; + var vertexIdx = parseInt(removeNode.getAttribute('idx'), 10); + this.model.removeVertex(vertexIdx); + }, + + dragArrowheadStart: function(evt, x, y) { + + if (!this.can('arrowheadMove')) return; + + var arrowheadNode = evt.target; + var arrowheadType = arrowheadNode.getAttribute('end'); + var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true }); + + this.eventData(evt, data); + }, + + dragStart: function(evt, x, y) { + + if (this.isDefaultInteractionPrevented(evt)) return; + + if (!this.can('linkMove')) return; + + this.eventData(evt, { + action: 'move', + dx: x, + dy: y + }); + }, + + // Drag Handlers + dragLabel: function(evt, x, y) { + + var data = this.eventData(evt); + var label = { position: this.getLabelPosition((x + data.dx), (y + data.dy), data.positionAngle, data.positionArgs) }; + if (this.paper.options.snapLabels) delete label.position.offset; + // The `touchmove' events are not fired + // when the original event target is removed from the DOM. + // The labels are currently re-rendered completely when only + // the position changes. This is why we need to make sure that + // the label is updated synchronously. + // TODO: replace `touchmove` with `pointermove` (breaking change). + const setOptions = { ui: true }; + if (this.paper.isAsync() && evt.type === 'touchmove') { + setOptions.async = false; + } + this.model.label(data.labelIdx, label, setOptions); + }, + + dragVertex: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.vertex(data.vertexIdx, { x: x, y: y }, { ui: true }); + }, + + dragArrowhead: function(evt, x, y) { + if (this.paper.options.snapLinks) { + const isSnapped = this._snapArrowhead(evt, x, y); + if (!isSnapped && this.paper.options.snapLinksSelf) { + this._snapArrowheadSelf(evt, x, y); + } + } else { + if (this.paper.options.snapLinksSelf) { + this._snapArrowheadSelf(evt, x, y); + } else { + this._connectArrowhead(this.getEventTarget(evt), x, y, this.eventData(evt)); + } + } + }, + + drag: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.translate(x - data.dx, y - data.dy, { ui: true }); + this.eventData(evt, { + dx: x, + dy: y + }); + }, + + // Drag End Handlers + + dragLabelEnd: function() { + // noop + }, + + dragVertexEnd: function() { + // noop + }, + + dragArrowheadEnd: function(evt, x, y) { + + var data = this.eventData(evt); + var paper = this.paper; + + if (paper.options.snapLinks) { + this._snapArrowheadEnd(data); + } else { + this._connectArrowheadEnd(data, x, y); + } + + if (!paper.linkAllowed(this)) { + // If the changed link is not allowed, revert to its previous state. + this._disallow(data); + } else { + this._finishEmbedding(data); + this._notifyConnectEvent(data, evt); + } + + this._afterArrowheadMove(data); + }, + + dragEnd: function() { + // noop + }, + + _disallow: function(data) { + + switch (data.whenNotAllowed) { + + case 'remove': + this.model.remove({ ui: true }); + break; + + case 'revert': + default: + this.model.set(data.arrowhead, data.initialEnd, { ui: true }); + break; + } + }, + + _finishEmbedding: function(data) { + + // Reparent the link if embedding is enabled + if (this.paper.options.embeddingMode && this.model.reparent()) { + // Make sure we don't reverse to the original 'z' index (see afterArrowheadMove()). + data.z = null; + } + }, + + _notifyConnectEvent: function(data, evt) { + + var arrowhead = data.arrowhead; + var initialEnd = data.initialEnd; + var currentEnd = this.model.prop(arrowhead); + var endChanged = currentEnd && !Link.endsEqual(initialEnd, currentEnd); + if (endChanged) { + var paper = this.paper; + if (initialEnd.id) { + this.notify('link:disconnect', evt, paper.findViewByModel(initialEnd.id), data.initialMagnet, arrowhead); + } + if (currentEnd.id) { + this.notify('link:connect', evt, paper.findViewByModel(currentEnd.id), data.magnetUnderPointer, arrowhead); + } + } + }, + + _snapToPoints: function(snapPoint, points, radius) { + let closestPointX = null; + let closestDistanceX = Infinity; + + let closestPointY = null; + let closestDistanceY = Infinity; + + let x = snapPoint.x; + let y = snapPoint.y; + + for (let i = 0; i < points.length; i++) { + const distX = Math.abs(points[i].x - snapPoint.x); + if (distX < closestDistanceX) { + closestDistanceX = distX; + closestPointX = points[i]; + } + + const distY = Math.abs(points[i].y - snapPoint.y); + if (distY < closestDistanceY) { + closestDistanceY = distY; + closestPointY = points[i]; + } + } + + if (closestDistanceX < radius) { + x = closestPointX.x; + } + if (closestDistanceY < radius) { + y = closestPointY.y; + } + + return { x, y }; + }, + + _snapArrowheadSelf: function(evt, x, y) { + + const { paper, model } = this; + const { snapLinksSelf } = paper.options; + const data = this.eventData(evt); + const radius = snapLinksSelf.radius || 20; + + const anchor = this.getEndAnchor(data.arrowhead === 'source' ? 'target' : 'source'); + const vertices = model.vertices(); + const points = [anchor, ...vertices]; + + const snapPoint = this._snapToPoints({ x: x, y: y }, points, radius); + + const point = paper.localToClientPoint(snapPoint); + this._connectArrowhead(document.elementFromPoint(point.x, point.y), snapPoint.x, snapPoint.y, this.eventData(evt)); + }, + + _snapArrowhead: function(evt, x, y) { + + const { paper } = this; + const { snapLinks, connectionStrategy } = paper.options; + const data = this.eventData(evt); + let isSnapped = false; + // checking view in close area of the pointer + + var r = snapLinks.radius || 50; + var viewsInArea = paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r }); + + var prevClosestView = data.closestView || null; + var prevClosestMagnet = data.closestMagnet || null; + var prevMagnetProxy = data.magnetProxy || null; + + data.closestView = data.closestMagnet = data.magnetProxy = null; + + var minDistance = Number.MAX_VALUE; + var pointer = new Point(x, y); + + viewsInArea.forEach(function(view) { + const candidates = []; + // skip connecting to the element in case '.': { magnet: false } attribute present + if (view.el.getAttribute('magnet') !== 'false') { + candidates.push({ + bbox: view.model.getBBox(), + magnet: view.el + }); + } + + view.$('[magnet]').toArray().forEach(magnet => { + candidates.push({ + bbox: view.getNodeBBox(magnet), + magnet + }); + }); + + candidates.forEach(candidate => { + const { magnet, bbox } = candidate; + // find distance from the center of the model to pointer coordinates + const distance = bbox.center().squaredDistance(pointer); + // the connection is looked up in a circle area by `distance < r` + if (distance < minDistance) { + const isAlreadyValidated = prevClosestMagnet === magnet; + if (isAlreadyValidated || paper.options.validateConnection.apply( + paper, data.validateConnectionArgs(view, (view.el === magnet) ? null : magnet) + )) { + minDistance = distance; + data.closestView = view; + data.closestMagnet = magnet; + } + } + }); + + }, this); + + var end; + var magnetProxy = null; + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + if (closestMagnet) { + magnetProxy = data.magnetProxy = closestView.findProxyNode(closestMagnet, 'highlighter'); + } + var endType = data.arrowhead; + var newClosestMagnet = (prevClosestMagnet !== closestMagnet); + if (prevClosestView && newClosestMagnet) { + prevClosestView.unhighlight(prevMagnetProxy, { + connecting: true, + snapping: true + }); + } + + if (closestView) { + const { prevEnd, prevX, prevY } = data; + data.prevX = x; + data.prevY = y; + isSnapped = true; + + if (!newClosestMagnet) { + if (typeof connectionStrategy !== 'function' || (prevX === x && prevY === y)) { + // the magnet has not changed and the link's end does not depend on the x and y + return isSnapped; + } + } + + end = closestView.getLinkEnd(closestMagnet, x, y, this.model, endType); + if (!newClosestMagnet && isEqual(prevEnd, end)) { + // the source/target json has not changed + return isSnapped; + } + + data.prevEnd = end; + + if (newClosestMagnet) { + closestView.highlight(magnetProxy, { + connecting: true, + snapping: true + }); + } + + } else { + + end = { x: x, y: y }; + } + + this.model.set(endType, end || { x: x, y: y }, { ui: true }); + + if (prevClosestView) { + this.notify('link:snap:disconnect', evt, prevClosestView, prevClosestMagnet, endType); + } + if (closestView) { + this.notify('link:snap:connect', evt, closestView, closestMagnet, endType); + } + + return isSnapped; + }, + + _snapArrowheadEnd: function(data) { + + // Finish off link snapping. + // Everything except view unhighlighting was already done on pointermove. + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + if (closestView && closestMagnet) { + + closestView.unhighlight(data.magnetProxy, { connecting: true, snapping: true }); + data.magnetUnderPointer = closestView.findMagnet(closestMagnet); + } + + data.closestView = data.closestMagnet = null; + }, + + _connectArrowhead: function(target, x, y, data) { + + // checking views right under the pointer + const { paper, model } = this; + + if (data.eventTarget !== target) { + // Unhighlight the previous view under pointer if there was one. + if (data.magnetProxy) { + data.viewUnderPointer.unhighlight(data.magnetProxy, { + connecting: true + }); + } + + const viewUnderPointer = data.viewUnderPointer = paper.findView(target); + if (viewUnderPointer) { + // If we found a view that is under the pointer, we need to find the closest + // magnet based on the real target element of the event. + const magnetUnderPointer = data.magnetUnderPointer = viewUnderPointer.findMagnet(target); + const magnetProxy = data.magnetProxy = viewUnderPointer.findProxyNode(magnetUnderPointer, 'highlighter'); + + if (magnetUnderPointer && this.paper.options.validateConnection.apply( + paper, + data.validateConnectionArgs(viewUnderPointer, magnetUnderPointer) + )) { + // If there was no magnet found, do not highlight anything and assume there + // is no view under pointer we're interested in reconnecting to. + // This can only happen if the overall element has the attribute `'.': { magnet: false }`. + if (magnetProxy) { + viewUnderPointer.highlight(magnetProxy, { + connecting: true + }); + } + } else { + // This type of connection is not valid. Disregard this magnet. + data.magnetUnderPointer = null; + data.magnetProxy = null; + } + } else { + // Make sure we'll unset previous magnet. + data.magnetUnderPointer = null; + data.magnetProxy = null; + } + } + + data.eventTarget = target; + + model.set(data.arrowhead, { x: x, y: y }, { ui: true }); + }, + + _connectArrowheadEnd: function(data = {}, x, y) { + + const { model } = this; + const { viewUnderPointer, magnetUnderPointer, magnetProxy, arrowhead } = data; + + if (!magnetUnderPointer || !magnetProxy || !viewUnderPointer) return; + + viewUnderPointer.unhighlight(magnetProxy, { connecting: true }); + + // The link end is taken from the magnet under the pointer, not the proxy. + const end = viewUnderPointer.getLinkEnd(magnetUnderPointer, x, y, model, arrowhead); + model.set(arrowhead, end, { ui: true }); + }, + + _beforeArrowheadMove: function(data) { + + data.z = this.model.get('z'); + this.model.toFront(); + + // Let the pointer propagate through the link view elements so that + // the `evt.target` is another element under the pointer, not the link itself. + var style = this.el.style; + data.pointerEvents = style.pointerEvents; + style.pointerEvents = 'none'; + + if (this.paper.options.markAvailable) { + this._markAvailableMagnets(data); + } + }, + + _afterArrowheadMove: function(data) { + + if (data.z !== null) { + this.model.set('z', data.z, { ui: true }); + data.z = null; + } + + // Put `pointer-events` back to its original value. See `_beforeArrowheadMove()` for explanation. + this.el.style.pointerEvents = data.pointerEvents; + + if (this.paper.options.markAvailable) { + this._unmarkAvailableMagnets(data); + } + }, + + _createValidateConnectionArgs: function(arrowhead) { + // It makes sure the arguments for validateConnection have the following form: + // (source view, source magnet, target view, target magnet and link view) + var args = []; + + args[4] = arrowhead; + args[5] = this; + + var oppositeArrowhead; + var i = 0; + var j = 0; + + if (arrowhead === 'source') { + i = 2; + oppositeArrowhead = 'target'; + } else { + j = 2; + oppositeArrowhead = 'source'; + } + + var end = this.model.get(oppositeArrowhead); + + if (end.id) { + var view = args[i] = this.paper.findViewByModel(end.id); + var magnet = view.getMagnetFromLinkEnd(end); + if (magnet === view.el) magnet = undefined; + args[i + 1] = magnet; + } + + function validateConnectionArgs(cellView, magnet) { + args[j] = cellView; + args[j + 1] = cellView.el === magnet ? undefined : magnet; + return args; + } + + return validateConnectionArgs; + }, + + _markAvailableMagnets: function(data) { + + function isMagnetAvailable(view, magnet) { + var paper = view.paper; + var validate = paper.options.validateConnection; + return validate.apply(paper, this.validateConnectionArgs(view, magnet)); + } + + var paper = this.paper; + var elements = paper.model.getCells(); + data.marked = {}; + + for (var i = 0, n = elements.length; i < n; i++) { + var view = elements[i].findView(paper); + + if (!view) { + continue; + } + + var magnets = Array.prototype.slice.call(view.el.querySelectorAll('[magnet]')); + if (view.el.getAttribute('magnet') !== 'false') { + // Element wrapping group is also a magnet + magnets.push(view.el); + } + + var availableMagnets = magnets.filter(isMagnetAvailable.bind(data, view)); + + if (availableMagnets.length > 0) { + // highlight all available magnets + for (var j = 0, m = availableMagnets.length; j < m; j++) { + view.highlight(availableMagnets[j], { magnetAvailability: true }); + } + // highlight the entire view + view.highlight(null, { elementAvailability: true }); + + data.marked[view.model.id] = availableMagnets; + } + } + }, + + _unmarkAvailableMagnets: function(data) { + + var markedKeys = Object.keys(data.marked); + var id; + var markedMagnets; + + for (var i = 0, n = markedKeys.length; i < n; i++) { + id = markedKeys[i]; + markedMagnets = data.marked[id]; + + var view = this.paper.findViewByModel(id); + if (view) { + for (var j = 0, m = markedMagnets.length; j < m; j++) { + view.unhighlight(markedMagnets[j], { magnetAvailability: true }); + } + view.unhighlight(null, { elementAvailability: true }); + } + } + + data.marked = null; + }, + + startArrowheadMove: function(end, opt) { + + opt || (opt = {}); + + // Allow to delegate events from an another view to this linkView in order to trigger arrowhead + // move without need to click on the actual arrowhead dom element. + var data = { + action: 'arrowhead-move', + arrowhead: end, + whenNotAllowed: opt.whenNotAllowed || 'revert', + initialMagnet: this[end + 'Magnet'] || (this[end + 'View'] ? this[end + 'View'].el : null), + initialEnd: clone(this.model.get(end)), + validateConnectionArgs: this._createValidateConnectionArgs(end) + }; + + this._beforeArrowheadMove(data); + + if (opt.ignoreBackwardsCompatibility !== true) { + this._dragData = data; + } + + return data; + }, + + // Lifecycle methods + + onMount: function() { + CellView.prototype.onMount.apply(this, arguments); + this.mountLabels(); + }, + + onDetach: function() { + CellView.prototype.onDetach.apply(this, arguments); + this.unmountLabels(); + }, + + onRemove: function() { + CellView.prototype.onRemove.apply(this, arguments); + this.unmountLabels(); + } + +}, { + + Flags: LinkView_Flags, +}); + +Object.defineProperty(LinkView.prototype, 'sourceBBox', { + + enumerable: true, + + get: function() { + var sourceView = this.sourceView; + if (!sourceView) { + var sourceDef = this.model.source(); + return new Rect(sourceDef.x, sourceDef.y); + } + var sourceMagnet = this.sourceMagnet; + if (sourceView.isNodeConnection(sourceMagnet)) { + return new Rect(this.sourceAnchor); + } + return sourceView.getNodeBBox(sourceMagnet || sourceView.el); + } + +}); + +Object.defineProperty(LinkView.prototype, 'targetBBox', { + + enumerable: true, + + get: function() { + var targetView = this.targetView; + if (!targetView) { + var targetDef = this.model.target(); + return new Rect(targetDef.x, targetDef.y); + } + var targetMagnet = this.targetMagnet; + if (targetView.isNodeConnection(targetMagnet)) { + return new Rect(this.targetAnchor); + } + return targetView.getNodeBBox(targetMagnet || targetView.el); + } +}); + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/stroke.mjs + + + + +const stroke_stroke = HighlighterView.extend({ + + tagName: 'path', + className: 'highlight-stroke', + attributes: { + 'pointer-events': 'none', + 'vector-effect': 'non-scaling-stroke', + 'fill': 'none' + }, + + options: { + padding: 3, + rx: 0, + ry: 0, + useFirstSubpath: false, + attrs: { + 'stroke-width': 3, + 'stroke': '#FEB663' + } + }, + + getPathData(cellView, node) { + const { options } = this; + const { useFirstSubpath } = options; + let d; + try { + const vNode = src_V(node); + d = vNode.convertToPathData().trim(); + if (vNode.tagName() === 'PATH' && useFirstSubpath) { + const secondSubpathIndex = d.search(/.M/i) + 1; + if (secondSubpathIndex > 0) { + d = d.substr(0, secondSubpathIndex); + } + } + } catch (error) { + // Failed to get path data from magnet element. + // Draw a rectangle around the node instead. + const nodeBBox = cellView.getNodeBoundingRect(node); + d = src_V.rectToPath(utilHelpers_assign({}, options, nodeBBox.toJSON())); + } + return d; + }, + + highlightConnection(cellView) { + this.vel.attr('d', cellView.getSerializedConnection()); + }, + + highlightNode(cellView, node) { + const { vel, options } = this; + const { padding, layer } = options; + let highlightMatrix = this.getNodeMatrix(cellView, node); + // Add padding to the highlight element. + if (padding) { + if (!layer && node === cellView.el) { + // If the highlighter is appended to the cellView + // and we measure the size of the cellView wrapping group + // it's necessary to remove the highlighter first + vel.remove(); + } + let nodeBBox = cellView.getNodeBoundingRect(node); + const cx = nodeBBox.x + (nodeBBox.width / 2); + const cy = nodeBBox.y + (nodeBBox.height / 2); + nodeBBox = src_V.transformRect(nodeBBox, highlightMatrix); + const width = Math.max(nodeBBox.width, 1); + const height = Math.max(nodeBBox.height, 1); + const sx = (width + padding) / width; + const sy = (height + padding) / height; + const paddingMatrix = src_V.createSVGMatrix({ + a: sx, + b: 0, + c: 0, + d: sy, + e: cx - sx * cx, + f: cy - sy * cy + }); + highlightMatrix = highlightMatrix.multiply(paddingMatrix); + } + vel.attr({ + 'd': this.getPathData(cellView, node), + 'transform': src_V.matrixToTransformString(highlightMatrix) + }); + }, + + highlight(cellView, node) { + const { vel, options } = this; + vel.attr(options.attrs); + if (cellView.isNodeConnection(node)) { + this.highlightConnection(cellView); + } else { + this.highlightNode(cellView, node); + } + } + +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/mask.mjs + + + +const MASK_CLIP = 20; + +function forEachDescendant(vel, fn) { + const descendants = vel.children(); + while (descendants.length > 0) { + const descendant = descendants.shift(); + if (fn(descendant)) { + descendants.push(...descendant.children()); + } + } +} + +const mask = HighlighterView.extend({ + + tagName: 'rect', + className: 'highlight-mask', + attributes: { + 'pointer-events': 'none' + }, + + options: { + padding: 3, + maskClip: MASK_CLIP, + deep: false, + attrs: { + 'stroke': '#FEB663', + 'stroke-width': 3, + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'miter', + } + }, + + VISIBLE: 'white', + INVISIBLE: 'black', + + MASK_ROOT_ATTRIBUTE_BLACKLIST: [ + 'marker-start', + 'marker-end', + 'marker-mid', + 'transform', + 'stroke-dasharray', + 'class', + ], + + MASK_CHILD_ATTRIBUTE_BLACKLIST: [ + 'stroke', + 'fill', + 'stroke-width', + 'stroke-opacity', + 'stroke-dasharray', + 'fill-opacity', + 'marker-start', + 'marker-end', + 'marker-mid', + 'class', + ], + + // TODO: change the list to a function callback + MASK_REPLACE_TAGS: [ + 'FOREIGNOBJECT', + 'IMAGE', + 'USE', + 'TEXT', + 'TSPAN', + 'TEXTPATH' + ], + + // TODO: change the list to a function callback + MASK_REMOVE_TAGS: [ + 'TEXT', + 'TSPAN', + 'TEXTPATH' + ], + + transformMaskChild(cellView, childEl) { + const { + MASK_CHILD_ATTRIBUTE_BLACKLIST, + MASK_REPLACE_TAGS, + MASK_REMOVE_TAGS + } = this; + const childTagName = childEl.tagName(); + // Do not include the element in the mask's image + if (!src_V.isSVGGraphicsElement(childEl) || MASK_REMOVE_TAGS.includes(childTagName)) { + childEl.remove(); + return false; + } + // Replace the element with a rectangle + if (MASK_REPLACE_TAGS.includes(childTagName)) { + // Note: clone() method does not change the children ids + const originalChild = cellView.vel.findOne(`#${childEl.id}`); + if (originalChild) { + const { node: originalNode } = originalChild; + let childBBox = cellView.getNodeBoundingRect(originalNode); + if (cellView.model.isElement()) { + childBBox = src_V.transformRect(childBBox, cellView.getNodeMatrix(originalNode)); + } + const replacement = src_V('rect', childBBox.toJSON()); + const { x: ox, y: oy } = childBBox.center(); + const { angle, cx = ox, cy = oy } = originalChild.rotate(); + if (angle) replacement.rotate(angle, cx, cy); + // Note: it's not important to keep the same sibling index since all subnodes are filled + childEl.parent().append(replacement); + } + childEl.remove(); + return false; + } + // Keep the element, but clean it from certain attributes + MASK_CHILD_ATTRIBUTE_BLACKLIST.forEach(attrName => { + if (attrName === 'fill' && childEl.attr('fill') === 'none') return; + childEl.removeAttr(attrName); + }); + return true; + }, + + transformMaskRoot(_cellView, rootEl) { + const { MASK_ROOT_ATTRIBUTE_BLACKLIST } = this; + MASK_ROOT_ATTRIBUTE_BLACKLIST.forEach(attrName => { + rootEl.removeAttr(attrName); + }); + }, + + getMaskShape(cellView, vel) { + const { options, MASK_REPLACE_TAGS } = this; + const { deep } = options; + const tagName = vel.tagName(); + let maskRoot; + if (tagName === 'G') { + if (!deep) return null; + maskRoot = vel.clone(); + forEachDescendant(maskRoot, maskChild => this.transformMaskChild(cellView, maskChild)); + } else { + if (MASK_REPLACE_TAGS.includes(tagName)) return null; + maskRoot = vel.clone(); + } + this.transformMaskRoot(cellView, maskRoot); + return maskRoot; + }, + + getMaskId() { + return `highlight-mask-${this.cid}`; + }, + + getMask(cellView, vNode) { + + const { VISIBLE, INVISIBLE, options } = this; + const { padding, attrs } = options; + + const strokeWidth = ('stroke-width' in attrs) ? attrs['stroke-width'] : 1; + const hasNodeFill = vNode.attr('fill') !== 'none'; + let magnetStrokeWidth = parseFloat(vNode.attr('stroke-width')); + if (isNaN(magnetStrokeWidth)) magnetStrokeWidth = 1; + // stroke of the invisible shape + const minStrokeWidth = magnetStrokeWidth + padding * 2; + // stroke of the visible shape + const maxStrokeWidth = minStrokeWidth + strokeWidth * 2; + let maskEl = this.getMaskShape(cellView, vNode); + if (!maskEl) { + const nodeBBox = cellView.getNodeBoundingRect(vNode.node); + // Make sure the rect is visible + nodeBBox.inflate(nodeBBox.width ? 0 : 0.5, nodeBBox.height ? 0 : 0.5); + maskEl = src_V('rect', nodeBBox.toJSON()); + } + maskEl.attr(attrs); + return src_V('mask', { + 'id': this.getMaskId() + }).append([ + maskEl.clone().attr({ + 'fill': hasNodeFill ? VISIBLE : 'none', + 'stroke': VISIBLE, + 'stroke-width': maxStrokeWidth + }), + maskEl.clone().attr({ + 'fill': hasNodeFill ? INVISIBLE : 'none', + 'stroke': INVISIBLE, + 'stroke-width': minStrokeWidth + }) + ]); + }, + + removeMask(paper) { + const maskNode = paper.svg.getElementById(this.getMaskId()); + if (maskNode) { + paper.defs.removeChild(maskNode); + } + }, + + addMask(paper, maskEl) { + paper.defs.appendChild(maskEl.node); + }, + + highlight(cellView, node) { + const { options, vel } = this; + const { padding, attrs, maskClip = MASK_CLIP, layer } = options; + const color = ('stroke' in attrs) ? attrs['stroke'] : '#000000'; + if (!layer && node === cellView.el) { + // If the highlighter is appended to the cellView + // and we measure the size of the cellView wrapping group + // it's necessary to remove the highlighter first + vel.remove(); + } + const highlighterBBox = cellView.getNodeBoundingRect(node).inflate(padding + maskClip); + const highlightMatrix = this.getNodeMatrix(cellView, node); + const maskEl = this.getMask(cellView, src_V(node)); + this.addMask(cellView.paper, maskEl); + vel.attr(highlighterBBox.toJSON()); + vel.attr({ + 'transform': src_V.matrixToTransformString(highlightMatrix), + 'mask': `url(#${maskEl.id})`, + 'fill': color + }); + }, + + unhighlight(cellView) { + this.removeMask(cellView.paper); + } + +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/opacity.mjs + + + + +const opacity = HighlighterView.extend({ + + UPDATABLE: false, + MOUNTABLE: false, + + opacityClassName: addClassNamePrefix('highlight-opacity'), + + highlight: function(_cellView, node) { + src_V(node).addClass(this.opacityClassName); + }, + + unhighlight: function(_cellView, node) { + src_V(node).removeClass(this.opacityClassName); + } + +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/addClass.mjs + + + + +const className = addClassNamePrefix('highlighted'); + +const addClass = HighlighterView.extend({ + + UPDATABLE: false, + MOUNTABLE: false, + + options: { + className + }, + + highlight: function(_cellView, node) { + src_V(node).addClass(this.options.className); + }, + + unhighlight: function(_cellView, node) { + src_V(node).removeClass(this.options.className); + } + +}, { + // Backwards Compatibility + className +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/list.mjs + + + + + +const list_Directions = { + ROW: 'row', + COLUMN: 'column' +}; + +const list = HighlighterView.extend({ + + tagName: 'g', + MOUNTABLE: true, + UPDATE_ATTRIBUTES: function() { + return [this.options.attribute]; + }, + + _prevItems: null, + + highlight(elementView, node) { + const element = elementView.model; + const { attribute, size = 20, gap = 5, direction = list_Directions.ROW } = this.options; + if (!attribute) throw new Error('List: attribute is required'); + const normalizedSize = (typeof size === 'number') ? { width: size, height: size } : size; + const isRowDirection = (direction === list_Directions.ROW); + const itemWidth = isRowDirection ? normalizedSize.width : normalizedSize.height; + let items = element.get(attribute); + if (!Array.isArray(items)) items = []; + const prevItems = this._prevItems || []; + const comparison = items.map((item, index) => isEqual(prevItems[index], items[index])); + if (prevItems.length !== items.length || comparison.some(unchanged => !unchanged)) { + const prevEls = this.vel.children(); + const itemsEls = items.map((item, index) => { + const prevEl = (index in prevEls) ? prevEls[index].node : null; + if (comparison[index]) return prevEl; + const itemEl = this.createListItem(item, normalizedSize, prevEl); + if (!itemEl) return null; + if (!(itemEl instanceof SVGElement)) throw new Error('List: item must be an SVGElement'); + itemEl.dataset.index = index; + itemEl.dataset.attribute = attribute; + const offset = index * (itemWidth + gap); + itemEl.setAttribute('transform', (isRowDirection) + ? `translate(${offset}, 0)` + : `translate(0, ${offset})` + ); + return itemEl; + }); + this.vel.empty().append(itemsEls); + this._prevItems = items; + } + const itemsCount = items.length; + const length = (itemsCount === 0) + ? 0 + : (itemsCount * itemWidth + (itemsCount - 1) * gap); + const listSize = (isRowDirection) + ? { width: length, height: normalizedSize.height } + : { width: normalizedSize.width, height: length }; + + this.position(element, listSize); + }, + + position(element, listSize) { + const { vel, options } = this; + const { margin = 5, position = 'top-left' } = options; + const { width, height } = element.size(); + const { left, right, top, bottom } = normalizeSides(margin); + const bbox = new Rect(left, top, width - (left + right), height - (top + bottom)); + let { x, y } = getRectPoint(bbox, position); + // x + switch (position) { + case Positions.CENTER: + case Positions.TOP: + case Positions.BOTTOM: { + x -= listSize.width / 2; + break; + } + case Positions.RIGHT: + case Positions.BOTTOM_RIGHT: + case Positions.TOP_RIGHT: { + x -= listSize.width; + break; + } + } + // y + switch (position) { + case Positions.CENTER: + case Positions.RIGHT: + case Positions.LEFT: { + y -= listSize.height / 2; + break; + } + case Positions.BOTTOM: + case Positions.BOTTOM_RIGHT: + case Positions.BOTTOM_LEFT: { + y -= listSize.height; + break; + } + } + vel.attr('transform', `translate(${x}, ${y})`); + } +}, { + Directions: list_Directions, + Positions: Positions +}); + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/highlighters/index.mjs + + + + + + +;// CONCATENATED MODULE: ./node_modules/jointjs/src/dia/Paper.mjs + + + + + + + + + + + + + + + + + + + +const sortingTypes = { + NONE: 'sorting-none', + APPROX: 'sorting-approximate', + EXACT: 'sorting-exact' +}; + +const WHEEL_CAP = 50; +const WHEEL_WAIT_MS = 20; +const MOUNT_BATCH_SIZE = 1000; +const UPDATE_BATCH_SIZE = Infinity; +const MIN_PRIORITY = 9007199254740991; // Number.MAX_SAFE_INTEGER + +const Paper_HighlightingTypes = CellView.Highlighting; + +const defaultHighlighting = { + [Paper_HighlightingTypes.DEFAULT]: { + name: 'stroke', + options: { + padding: 3 + } + }, + [Paper_HighlightingTypes.MAGNET_AVAILABILITY]: { + name: 'addClass', + options: { + className: 'available-magnet' + } + }, + [Paper_HighlightingTypes.ELEMENT_AVAILABILITY]: { + name: 'addClass', + options: { + className: 'available-cell' + } + } +}; + +const defaultLayers = [{ + name: LayersNames.BACK, +}, { + name: LayersNames.CELLS, +}, { + name: LayersNames.LABELS, +}, { + name: LayersNames.FRONT +}, { + name: LayersNames.TOOLS +}]; + +const Paper = View.extend({ + + className: 'paper', + + options: { + + width: 800, + height: 600, + origin: { x: 0, y: 0 }, // x,y coordinates in top-left corner + gridSize: 1, + // Whether or not to draw the grid lines on the paper's DOM element. + // e.g drawGrid: true, drawGrid: { color: 'red', thickness: 2 } + drawGrid: false, + // If not set, the size of the visual grid is the same as the `gridSize`. + drawGridSize: null, + + // Whether or not to draw the background on the paper's DOM element. + // e.g. background: { color: 'lightblue', image: '/paper-background.png', repeat: 'flip-xy' } + background: false, + + perpendicularLinks: false, + elementView: ElementView, + linkView: LinkView, + snapLabels: false, // false, true + snapLinks: false, // false, true, { radius: value } + snapLinksSelf: false, // false, true, { radius: value } + + // Should the link labels be rendered into its own layer? + // `false` - the labels are part of the links + // `true` - the labels are appended to LayersName.LABELS + // [LayersName] - the labels are appended to the layer specified + labelsLayer: false, + + // When set to FALSE, an element may not have more than 1 link with the same source and target element. + multiLinks: true, + + // For adding custom guard logic. + guard: function(evt, view) { + + // FALSE means the event isn't guarded. + return false; + }, + + highlighting: defaultHighlighting, + + // Prevent the default context menu from being displayed. + preventContextMenu: true, + + // Prevent the default action for blank:pointer. + preventDefaultBlankAction: true, + + // Prevent the default action for cell:pointer. + preventDefaultViewAction: true, + + // Restrict the translation of elements by given bounding box. + // Option accepts a boolean: + // true - the translation is restricted to the paper area + // false - no restrictions + // A method: + // restrictTranslate: function(elementView) { + // var parentId = elementView.model.get('parent'); + // return parentId && this.model.getCell(parentId).getBBox(); + // }, + // Or a bounding box: + // restrictTranslate: { x: 10, y: 10, width: 790, height: 590 } + restrictTranslate: false, + + // Marks all available magnets with 'available-magnet' class name and all available cells with + // 'available-cell' class name. Marks them when dragging a link is started and unmark + // when the dragging is stopped. + markAvailable: false, + + // Defines what link model is added to the graph after an user clicks on an active magnet. + // Value could be the Backbone.model or a function returning the Backbone.model + // defaultLink: function(elementView, magnet) { return condition ? new customLink1() : new customLink2() } + defaultLink: new Link, + + // A connector that is used by links with no connector defined on the model. + // e.g. { name: 'rounded', args: { radius: 5 }} or a function + defaultConnector: { name: 'normal' }, + + // A router that is used by links with no router defined on the model. + // e.g. { name: 'oneSide', args: { padding: 10 }} or a function + defaultRouter: { name: 'normal' }, + + defaultAnchor: { name: 'center' }, + + defaultLinkAnchor: { name: 'connectionRatio' }, + + defaultConnectionPoint: { name: 'bbox' }, + + /* CONNECTING */ + + connectionStrategy: null, + + // Check whether to add a new link to the graph when user clicks on an a magnet. + validateMagnet: function(_cellView, magnet, _evt) { + return magnet.getAttribute('magnet') !== 'passive'; + }, + + // Check whether to allow or disallow the link connection while an arrowhead end (source/target) + // being changed. + validateConnection: function(cellViewS, _magnetS, cellViewT, _magnetT, end, _linkView) { + return (end === 'target' ? cellViewT : cellViewS) instanceof ElementView; + }, + + /* EMBEDDING */ + + // Enables embedding. Re-parent the dragged element with elements under it and makes sure that + // all links and elements are visible taken the level of embedding into account. + embeddingMode: false, + + // Check whether to allow or disallow the element embedding while an element being translated. + validateEmbedding: function(childView, parentView) { + // by default all elements can be in relation child-parent + return true; + }, + + // Check whether to allow or disallow an embedded element to be unembedded / to become a root. + validateUnembedding: function(childView) { + // by default all elements can become roots + return true; + }, + + // Determines the way how a cell finds a suitable parent when it's dragged over the paper. + // The cell with the highest z-index (visually on the top) will be chosen. + findParentBy: 'bbox', // 'bbox'|'center'|'origin'|'corner'|'topRight'|'bottomLeft' + + // If enabled only the element on the very front is taken into account for the embedding. + // If disabled the elements under the dragged view are tested one by one + // (from front to back) until a valid parent found. + frontParentOnly: true, + + // Interactive flags. See online docs for the complete list of interactive flags. + interactive: { + labelMove: false + }, + + // When set to true the links can be pinned to the paper. + // i.e. link source/target can be a point e.g. link.get('source') ==> { x: 100, y: 100 }; + linkPinning: true, + + // Custom validation after an interaction with a link ends. + // Recognizes a function. If `false` is returned, the link is disallowed (removed or reverted) + // (linkView, paper) => boolean + allowLink: null, + + // Allowed number of mousemove events after which the pointerclick event will be still triggered. + clickThreshold: 0, + + // Number of required mousemove events before the first pointermove event will be triggered. + moveThreshold: 0, + + // Number of required mousemove events before a link is created out of the magnet. + // Or string `onleave` so the link is created when the pointer leaves the magnet + magnetThreshold: 0, + + // Rendering Options + + sorting: sortingTypes.EXACT, + + frozen: false, + + autoFreeze: false, + + // no docs yet + onViewUpdate: function(view, flag, priority, opt, paper) { + // Do not update connected links when: + // 1. the view was just inserted (added to the graph and rendered) + // 2. the view was just mounted (added back to the paper by viewport function) + // 3. the change was marked as `isolate`. + // 4. the view model was just removed from the graph + if ((flag & (view.FLAG_INSERT | view.FLAG_REMOVE)) || opt.mounting || opt.isolate) return; + paper.requestConnectedLinksUpdate(view, priority, opt); + }, + + // no docs yet + onViewPostponed: function(view, flag, paper) { + return paper.forcePostponedViewUpdate(view, flag); + }, + + beforeRender: null, // function(opt, paper) { }, + + afterRender: null, // function(stats, opt, paper) { + + viewport: null, + + // Default namespaces + + cellViewNamespace: null, + + routerNamespace: null, + + connectorNamespace: null, + + highlighterNamespace: highlighters_namespaceObject, + + anchorNamespace: anchors_namespaceObject, + + linkAnchorNamespace: linkAnchors_namespaceObject, + + connectionPointNamespace: connectionPoints_namespaceObject, + + overflow: false + }, + + events: { + 'dblclick': 'pointerdblclick', + 'dbltap': 'pointerdblclick', + 'contextmenu': 'contextmenu', + 'mousedown': 'pointerdown', + 'touchstart': 'pointerdown', + 'mouseover': 'mouseover', + 'mouseout': 'mouseout', + 'mouseenter': 'mouseenter', + 'mouseleave': 'mouseleave', + 'wheel': 'mousewheel', + 'mouseenter .joint-cell': 'mouseenter', + 'mouseleave .joint-cell': 'mouseleave', + 'mouseenter .joint-tools': 'mouseenter', + 'mouseleave .joint-tools': 'mouseleave', + 'dblclick .joint-cell [magnet]': 'magnetpointerdblclick', + 'contextmenu .joint-cell [magnet]': 'magnetcontextmenu', + 'mousedown .joint-link .label': 'onlabel', // interaction with link label + 'touchstart .joint-link .label': 'onlabel', + 'dragstart .joint-cell image': 'onImageDragStart' // firefox fix + }, + + documentEvents: { + 'mousemove': 'pointermove', + 'touchmove': 'pointermove', + 'mouseup': 'pointerup', + 'touchend': 'pointerup', + 'touchcancel': 'pointerup' + }, + + svg: null, + viewport: null, + defs: null, + tools: null, + $background: null, + layers: null, + $grid: null, + $document: null, + + // For storing the current transformation matrix (CTM) of the paper's viewport. + _viewportMatrix: null, + // For verifying whether the CTM is up-to-date. The viewport transform attribute + // could have been manipulated directly. + _viewportTransformString: null, + // Updates data (priorities, unmounted views etc.) + _updates: null, + // Paper Layers + _layers: null, + + SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'], + UPDATE_DELAYING_BATCHES: ['translate'], + // If you interact with these elements, + // the default interaction such as `element move` is prevented. + FORM_CONTROL_TAG_NAMES: ['TEXTAREA', 'INPUT', 'BUTTON', 'SELECT', 'OPTION'] , + // If you interact with these elements, the events are not propagated to the paper + // i.e. paper events such as `element:pointerdown` are not triggered. + GUARDED_TAG_NAMES: [ + // Guard \" +\n\t\t\t\t\"\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( el.querySelectorAll( \"[msallowcapture^='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !el.querySelectorAll( \"[selected]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+\n\t\t\tif ( !el.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"~=\" );\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 15 - 18+\n\t\t\t// IE 11/Edge don't find elements on a `[name='']` query in some cases.\n\t\t\t// Adding a temporary attribute to the document before the selection works\n\t\t\t// around the issue.\n\t\t\t// Interestingly, IE 10 & older don't seem to have the issue.\n\t\t\tinput = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"name\", \"\" );\n\t\t\tel.appendChild( input );\n\t\t\tif ( !el.querySelectorAll( \"[name='']\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*name\" + whitespace + \"*=\" +\n\t\t\t\t\twhitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !el.querySelectorAll( \":checked\" ).length ) {\n\t\t\t\trbuggyQSA.push( \":checked\" );\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibling-combinator selector` fails\n\t\t\tif ( !el.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push( \".#.+[+~]\" );\n\t\t\t}\n\n\t\t\t// Support: Firefox <=3.6 - 5 only\n\t\t\t// Old Firefox doesn't throw on a badly-escaped identifier.\n\t\t\tel.querySelectorAll( \"\\\\\\f\" );\n\t\t\trbuggyQSA.push( \"[\\\\r\\\\n\\\\f]\" );\n\t\t} );\n\n\t\tassert( function( el ) {\n\t\t\tel.innerHTML = \"\" +\n\t\t\t\t\"\";\n\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = document.createElement( \"input\" );\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tel.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( el.querySelectorAll( \"[name=d]\" ).length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( el.querySelectorAll( \":enabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// IE's :disabled selector does not pick up the children of disabled fieldsets\n\t\t\tdocElem.appendChild( el ).disabled = true;\n\t\t\tif ( el.querySelectorAll( \":disabled\" ).length !== 2 ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Support: Opera 10 - 11 only\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tel.querySelectorAll( \"*,:x\" );\n\t\t\trbuggyQSA.push( \",.*:\" );\n\t\t} );\n\t}\n\n\tif ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector ) ) ) ) {\n\n\t\tassert( function( el ) {\n\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( el, \"*\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( el, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t} );\n\t}\n\n\tif ( !support.cssHas ) {\n\n\t\t// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+\n\t\t// Our regular `try-catch` mechanism fails to detect natively-unsupported\n\t\t// pseudo-classes inside `:has()` (such as `:has(:contains(\"Foo\"))`)\n\t\t// in browsers that parse the `:has()` argument as a forgiving selector list.\n\t\t// https://drafts.csswg.org/selectors/#relational now requires the argument\n\t\t// to be parsed unforgivingly, but browsers have not yet fully adjusted.\n\t\trbuggyQSA.push( \":has\" );\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( \"|\" ) );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( \"|\" ) );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully self-exclusive\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\n\t\t\t// Support: IE <9 only\n\t\t\t// IE doesn't have `contains` on `document` so we need to check for\n\t\t\t// `documentElement` presence.\n\t\t\t// We need to fall back to `a` when `documentElement` is missing\n\t\t\t// as `ownerDocument` of elements within `