diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js
index 7bdf119..3298d59 100755
--- a/dist/ueblueprint.js
+++ b/dist/ueblueprint.js
@@ -129,28 +129,28 @@ class Configuration {
*/
const html = String.raw;
-/**
- * @typedef {import("../element/IElement").default} IElement
- */
-class ITemplate {
-
- /**
- * Computes the html content of the target element.
- * @param {IElement} entity Element of the graph
- * @returns The result html
- */
- render(entity) {
- return ""
- }
-
- /**
- * Applies the style to the element.
- * @param {IElement} element Element of the graph
- */
- apply(element) {
- // TODO replace with the safer element.setHTML(...) when it will be available
- element.innerHTML = this.render(element);
- }
+/**
+ * @typedef {import("../element/IElement").default} IElement
+ */
+class ITemplate {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {IElement} entity Element of the graph
+ * @returns The result html
+ */
+ render(entity) {
+ return ""
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {IElement} element Element of the graph
+ */
+ apply(element) {
+ // TODO replace with the safer element.setHTML(...) when it will be available
+ element.innerHTML = this.render(element);
+ }
}
document.createElement("div");
@@ -474,50 +474,50 @@ class FastSelectionModel {
}
-/**
- * @typedef {import("../Blueprint").default} Blueprint
- * @typedef {import("../entity/IEntity").default} IEntity
- * @typedef {import("../input/IContext").default} IContext
- * @typedef {import("../template/ITemplate").default} ITemplate
- */
-
-class IElement extends HTMLElement {
-
- static tagName = ""
-
- /**
- * @param {IEntity} entity The entity containing blueprint related data for this graph element
- * @param {ITemplate} template The template to render this node
- */
- constructor(entity, template) {
- super();
- /** @type {Blueprint} */
- this.blueprint = null;
- /** @type {IEntity} */
- this.entity = entity;
- /** @type {ITemplate} */
- this.template = template;
- /** @type {IContext[]} */
- this.inputObjects = [];
- }
-
- getTemplate() {
- return this.template
- }
-
- connectedCallback() {
- this.blueprint = this.closest("ueb-blueprint");
- this.template.apply(this);
- this.inputObjects = this.createInputObjects();
- }
-
- disconnectedCallback() {
- this.inputObjects.forEach(v => v.unlistenDOMElement());
- }
-
- createInputObjects() {
- return []
- }
+/**
+ * @typedef {import("../Blueprint").default} Blueprint
+ * @typedef {import("../entity/IEntity").default} IEntity
+ * @typedef {import("../input/IContext").default} IContext
+ * @typedef {import("../template/ITemplate").default} ITemplate
+ */
+
+class IElement extends HTMLElement {
+
+ static tagName = ""
+
+ /**
+ * @param {IEntity} entity The entity containing blueprint related data for this graph element
+ * @param {ITemplate} template The template to render this node
+ */
+ constructor(entity, template) {
+ super();
+ /** @type {Blueprint} */
+ this.blueprint = null;
+ /** @type {IEntity} */
+ this.entity = entity;
+ /** @type {ITemplate} */
+ this.template = template;
+ /** @type {IContext[]} */
+ this.inputObjects = [];
+ }
+
+ getTemplate() {
+ return this.template
+ }
+
+ connectedCallback() {
+ this.blueprint = this.closest("ueb-blueprint");
+ this.template.apply(this);
+ this.inputObjects = this.createInputObjects();
+ }
+
+ disconnectedCallback() {
+ this.inputObjects.forEach(v => v.unlistenDOMElement());
+ }
+
+ createInputObjects() {
+ return []
+ }
}
/**
@@ -567,41 +567,41 @@ class SelectorTemplate extends ITemplate {
}
}
-class SelectorElement extends IElement {
-
- static tagName = "ueb-selector"
-
- constructor() {
- super({}, new SelectorTemplate());
- this.selectionModel = null;
- /** @type {SelectorTemplate} */
- this.template;
- }
-
- /**
- * Create a selection rectangle starting from the specified position
- * @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element)
- */
- startSelecting(initialPosition) {
- this.template.applyStartSelecting(this, initialPosition);
- this.selectionModel = new FastSelectionModel(initialPosition, this.blueprint.getNodes(), this.blueprint.nodeBoundariesSupplier, this.blueprint.nodeSelectToggleFunction);
- }
-
- /**
- * Move selection rectagle to the specified final position. The initial position was specified by startSelecting()
- * @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element)
- */
- doSelecting(finalPosition) {
- this.template.applyDoSelecting(this, finalPosition);
- this.selectionModel.selectTo(finalPosition);
- }
-
- finishSelecting() {
- this.template.applyFinishSelecting(this);
- this.selectionModel = null;
- }
-}
-
+class SelectorElement extends IElement {
+
+ static tagName = "ueb-selector"
+
+ constructor() {
+ super({}, new SelectorTemplate());
+ this.selectionModel = null;
+ /** @type {SelectorTemplate} */
+ this.template;
+ }
+
+ /**
+ * Create a selection rectangle starting from the specified position
+ * @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element)
+ */
+ startSelecting(initialPosition) {
+ this.template.applyStartSelecting(this, initialPosition);
+ this.selectionModel = new FastSelectionModel(initialPosition, this.blueprint.getNodes(), this.blueprint.nodeBoundariesSupplier, this.blueprint.nodeSelectToggleFunction);
+ }
+
+ /**
+ * Move selection rectagle to the specified final position. The initial position was specified by startSelecting()
+ * @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element)
+ */
+ doSelecting(finalPosition) {
+ this.template.applyDoSelecting(this, finalPosition);
+ this.selectionModel.selectTo(finalPosition);
+ }
+
+ finishSelecting() {
+ this.template.applyFinishSelecting(this);
+ this.selectionModel = null;
+ }
+}
+
customElements.define(SelectorElement.tagName, SelectorElement);
/** @typedef {import("../Blueprint").default} Blueprint */
@@ -725,35 +725,35 @@ class BlueprintTemplate extends ITemplate {
}
}
-class IContext {
-
- constructor(target, blueprint, options) {
- /** @type {HTMLElement} */
- this.target = target;
- /** @type {import("../Blueprint").default}" */
- this.blueprint = blueprint;
- this.options = options;
- let self = this;
- this.blueprintFocusHandler = _ => self.listenEvents();
- this.blueprintUnfocusHandler = _ => self.unlistenEvents();
- if (options?.wantsFocusCallback ?? false) {
- this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler);
- this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
- }
- }
-
- unlistenDOMElement() {
- this.unlistenEvents();
- this.blueprint.removeEventListener("blueprint-focus", this.blueprintFocusHandler);
- this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
- }
-
- /* Subclasses will probabily override the following methods */
- listenEvents() {
- }
-
- unlistenEvents() {
- }
+class IContext {
+
+ constructor(target, blueprint, options) {
+ /** @type {HTMLElement} */
+ this.target = target;
+ /** @type {import("../Blueprint").default}" */
+ this.blueprint = blueprint;
+ this.options = options;
+ let self = this;
+ this.blueprintFocusHandler = _ => self.listenEvents();
+ this.blueprintUnfocusHandler = _ => self.unlistenEvents();
+ if (options?.wantsFocusCallback ?? false) {
+ this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler);
+ this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
+ }
+ }
+
+ unlistenDOMElement() {
+ this.unlistenEvents();
+ this.blueprint.removeEventListener("blueprint-focus", this.blueprintFocusHandler);
+ this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
+ }
+
+ /* Subclasses will probabily override the following methods */
+ listenEvents() {
+ }
+
+ unlistenEvents() {
+ }
}
class TypeInitialization {
@@ -899,56 +899,56 @@ class Utility {
}
}
-class IEntity {
-
- constructor(options = {}) {
- /**
- * @param {String[]} prefix
- * @param {Object} target
- * @param {Object} properties
- */
- const defineAllAttributes = (prefix, target, properties) => {
- let fullKey = prefix.concat("");
- const last = fullKey.length - 1;
- for (let property in properties) {
- fullKey[last] = property;
- // Not instanceof because all objects are instenceof Object, exact match needed
- if (properties[property]?.constructor === Object) {
- target[property] = {};
- defineAllAttributes(fullKey, target[property], properties[property]);
- continue
- }
- /*
- * The value can either be:
- * - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World")
- * - TypeInitialization: contains the maximum amount of information about the attribute.
- * - A type: the default value will be default constructed object without arguments.
- * - A proper value.
- */
- const value = Utility.objectGet(options, fullKey);
- if (value !== null) {
- target[property] = value;
- continue
- }
- let defaultValue = properties[property];
- if (defaultValue instanceof TypeInitialization) {
- if (!defaultValue.showDefault) {
- continue
- }
- defaultValue = defaultValue.value;
- }
- if (defaultValue instanceof Array) {
- target[property] = [];
- continue
- }
- if (defaultValue instanceof Function) {
- defaultValue = TypeInitialization.sanitize(new defaultValue());
- }
- target[property] = TypeInitialization.sanitize(defaultValue);
- }
- };
- defineAllAttributes([], this, this.constructor.attributes);
- }
+class IEntity {
+
+ constructor(options = {}) {
+ /**
+ * @param {String[]} prefix
+ * @param {Object} target
+ * @param {Object} properties
+ */
+ const defineAllAttributes = (prefix, target, properties) => {
+ let fullKey = prefix.concat("");
+ const last = fullKey.length - 1;
+ for (let property in properties) {
+ fullKey[last] = property;
+ // Not instanceof because all objects are instenceof Object, exact match needed
+ if (properties[property]?.constructor === Object) {
+ target[property] = {};
+ defineAllAttributes(fullKey, target[property], properties[property]);
+ continue
+ }
+ /*
+ * The value can either be:
+ * - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World")
+ * - TypeInitialization: contains the maximum amount of information about the attribute.
+ * - A type: the default value will be default constructed object without arguments.
+ * - A proper value.
+ */
+ const value = Utility.objectGet(options, fullKey);
+ if (value !== null) {
+ target[property] = value;
+ continue
+ }
+ let defaultValue = properties[property];
+ if (defaultValue instanceof TypeInitialization) {
+ if (!defaultValue.showDefault) {
+ continue
+ }
+ defaultValue = defaultValue.value;
+ }
+ if (defaultValue instanceof Array) {
+ target[property] = [];
+ continue
+ }
+ if (defaultValue instanceof Function) {
+ defaultValue = TypeInitialization.sanitize(new defaultValue());
+ }
+ target[property] = TypeInitialization.sanitize(defaultValue);
+ }
+ };
+ defineAllAttributes([], this, this.constructor.attributes);
+ }
}
class ObjectReferenceEntity extends IEntity {
@@ -1113,13 +1113,13 @@ class PinEntity extends IEntity {
linkTo(targetObjectName, targetPinEntity) {
/** @type {PinReferenceEntity[]} */
this.LinkedTo;
- const pinExists = !this.LinkedTo.find(
+ const linkExists = this.LinkedTo.find(
/** @type {PinReferenceEntity} */
pinReferenceEntity => {
return pinReferenceEntity.objectName == targetObjectName
&& pinReferenceEntity.pinGuid == targetPinEntity.PinId
});
- if (pinExists) {
+ if (!linkExists) {
this.LinkedTo.push(new PinReferenceEntity({
objectName: targetObjectName,
pinGuid: targetPinEntity.PinId
@@ -1381,82 +1381,82 @@ class SerializerFactory {
}
}
-class ISerializer {
-
- static grammar = Parsimmon.createLanguage(new Grammar())
-
- constructor(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) {
- this.entityType = entityType;
- this.prefix = prefix ?? "";
- this.separator = separator ?? ",";
- this.trailingSeparator = trailingSeparator ?? false;
- this.attributeValueConjunctionSign = attributeValueConjunctionSign ?? "=";
- this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join("."));
- }
-
- writeValue(value) {
- if (value === null) {
- return "()"
- }
- const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v);
- // This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically)
- switch (value?.constructor) {
- case Function:
- return this.writeValue(value())
- case Boolean:
- return Utility.FirstCapital(value.toString())
- case Number:
- return value.toString()
- case String:
- return `"${value}"`
- }
- if (value instanceof Array) {
- return `(${value.map(v => serialize(v) + ",")})`
- }
- if (value instanceof IEntity) {
- return serialize(value)
- }
- }
-
- /**
- * @param {String[]} key
- * @param {Object} object
- * @returns {String}
- */
- subWrite(key, object) {
- let result = "";
- let fullKey = key.concat("");
- const last = fullKey.length - 1;
- for (const property in object) {
- fullKey[last] = property;
- const value = object[property];
- if (object[property]?.constructor === Object) {
- // Recursive call when finding an object
- result += (result.length ? this.separator : "")
- + this.subWrite(fullKey, value);
- } else if (this.showProperty(fullKey, value)) {
- result += (result.length ? this.separator : "")
- + this.prefix
- + this.attributeKeyPrinter(fullKey)
- + this.attributeValueConjunctionSign
- + this.writeValue(value);
- }
- }
- if (this.trailingSeparator && result.length && fullKey.length === 0) {
- // append separator at the end if asked and there was printed content
- result += this.separator;
- }
- return result
- }
-
- showProperty(attributeKey, attributeValue) {
- const attributes = this.entityType.attributes;
- const attribute = Utility.objectGet(attributes, attributeKey);
- if (attribute instanceof TypeInitialization) {
- return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault
- }
- return true
- }
+class ISerializer {
+
+ static grammar = Parsimmon.createLanguage(new Grammar())
+
+ constructor(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) {
+ this.entityType = entityType;
+ this.prefix = prefix ?? "";
+ this.separator = separator ?? ",";
+ this.trailingSeparator = trailingSeparator ?? false;
+ this.attributeValueConjunctionSign = attributeValueConjunctionSign ?? "=";
+ this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join("."));
+ }
+
+ writeValue(value) {
+ if (value === null) {
+ return "()"
+ }
+ const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v);
+ // This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically)
+ switch (value?.constructor) {
+ case Function:
+ return this.writeValue(value())
+ case Boolean:
+ return Utility.FirstCapital(value.toString())
+ case Number:
+ return value.toString()
+ case String:
+ return `"${value}"`
+ }
+ if (value instanceof Array) {
+ return `(${value.map(v => serialize(v) + ",").join("")})`
+ }
+ if (value instanceof IEntity) {
+ return serialize(value)
+ }
+ }
+
+ /**
+ * @param {String[]} key
+ * @param {Object} object
+ * @returns {String}
+ */
+ subWrite(key, object) {
+ let result = "";
+ let fullKey = key.concat("");
+ const last = fullKey.length - 1;
+ for (const property in object) {
+ fullKey[last] = property;
+ const value = object[property];
+ if (object[property]?.constructor === Object) {
+ // Recursive call when finding an object
+ result += (result.length ? this.separator : "")
+ + this.subWrite(fullKey, value);
+ } else if (this.showProperty(fullKey, value)) {
+ result += (result.length ? this.separator : "")
+ + this.prefix
+ + this.attributeKeyPrinter(fullKey)
+ + this.attributeValueConjunctionSign
+ + this.writeValue(value);
+ }
+ }
+ if (this.trailingSeparator && result.length && fullKey.length === 0) {
+ // append separator at the end if asked and there was printed content
+ result += this.separator;
+ }
+ return result
+ }
+
+ showProperty(attributeKey, attributeValue) {
+ const attributes = this.entityType.attributes;
+ const attribute = Utility.objectGet(attributes, attributeKey);
+ if (attribute instanceof TypeInitialization) {
+ return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault
+ }
+ return true
+ }
}
class ObjectSerializer extends ISerializer {
@@ -1538,87 +1538,87 @@ class Copy extends IContext {
}
}
-let P = Parsimmon;
-
-class KeyGrammar {
-
- // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use
- ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i)))
- Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v])
- KeyboardShortcut = r => P.alt(
- P.seqMap(
- P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v)
- .atLeast(1)
- .map(v => v.reduce((acc, cur) => acc | cur)),
- P.optWhitespace,
- r.Key,
- (modifierKeysFlag, _, key) => ({
- key: key,
- ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))),
- shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))),
- altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))),
- metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta")))
- })
- ),
- r.Key.map(v => ({ key: v }))
- )
- .trim(P.optWhitespace)
-}
-
-class IKeyboardShortcut extends IContext {
-
- static keyGrammar = P.createLanguage(new KeyGrammar())
-
- constructor(target, blueprint, options = {}) {
- options.wantsFocusCallback = true;
- super(target, blueprint, options);
-
- /** @type {String[]} */
- this.key = this.options.key;
- this.ctrlKey = options.ctrlKey ?? false;
- this.shiftKey = options.shiftKey ?? false;
- this.altKey = options.altKey ?? false;
- this.metaKey = options.metaKey ?? false;
-
- let self = this;
- this.keyDownHandler = e => {
- if (
- e.code == self.key
- && e.ctrlKey === self.ctrlKey
- && e.shiftKey === self.shiftKey
- && e.altKey === self.altKey
- && e.metaKey === self.metaKey
- ) {
- self.fire();
- e.preventDefault();
- return true
- }
- return false
- };
- }
-
- /**
- * @param {String} keyString
- * @returns {Object}
- */
- static keyOptionsParse(options, keyString) {
- options = {
- ...options,
- ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value
- };
- return options
- }
-
- listenEvents() {
- document.addEventListener("keydown", this.keyDownHandler);
- }
-
- unlistenEvents() {
- document.removeEventListener("keydown", this.keyDownHandler);
- }
-
- fire() {
- }
+let P = Parsimmon;
+
+class KeyGrammar {
+
+ // Creates a grammar where each alternative is the string from ModifierKey mapped to a number for bit or use
+ ModifierKey = r => P.alt(...Configuration.ModifierKeys.map((v, i) => P.string(v).map(_ => 1 << i)))
+ Key = r => P.alt(...Object.keys(Configuration.Keys).map(v => P.string(v))).map(v => Configuration.Keys[v])
+ KeyboardShortcut = r => P.alt(
+ P.seqMap(
+ P.seqMap(r.ModifierKey, P.optWhitespace, P.string(Configuration.keysSeparator), (v, _, __) => v)
+ .atLeast(1)
+ .map(v => v.reduce((acc, cur) => acc | cur)),
+ P.optWhitespace,
+ r.Key,
+ (modifierKeysFlag, _, key) => ({
+ key: key,
+ ctrlKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Ctrl"))),
+ shiftKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Shift"))),
+ altKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Alt"))),
+ metaKey: Boolean(modifierKeysFlag & (1 << Configuration.ModifierKeys.indexOf("Meta")))
+ })
+ ),
+ r.Key.map(v => ({ key: v }))
+ )
+ .trim(P.optWhitespace)
+}
+
+class IKeyboardShortcut extends IContext {
+
+ static keyGrammar = P.createLanguage(new KeyGrammar())
+
+ constructor(target, blueprint, options = {}) {
+ options.wantsFocusCallback = true;
+ super(target, blueprint, options);
+
+ /** @type {String[]} */
+ this.key = this.options.key;
+ this.ctrlKey = options.ctrlKey ?? false;
+ this.shiftKey = options.shiftKey ?? false;
+ this.altKey = options.altKey ?? false;
+ this.metaKey = options.metaKey ?? false;
+
+ let self = this;
+ this.keyDownHandler = e => {
+ if (
+ e.code == self.key
+ && e.ctrlKey === self.ctrlKey
+ && e.shiftKey === self.shiftKey
+ && e.altKey === self.altKey
+ && e.metaKey === self.metaKey
+ ) {
+ self.fire();
+ e.preventDefault();
+ return true
+ }
+ return false
+ };
+ }
+
+ /**
+ * @param {String} keyString
+ * @returns {Object}
+ */
+ static keyOptionsParse(options, keyString) {
+ options = {
+ ...options,
+ ...IKeyboardShortcut.keyGrammar.KeyboardShortcut.parse(keyString).value
+ };
+ return options
+ }
+
+ listenEvents() {
+ document.addEventListener("keydown", this.keyDownHandler);
+ }
+
+ unlistenEvents() {
+ document.removeEventListener("keydown", this.keyDownHandler);
+ }
+
+ fire() {
+ }
}
class KeyvoardCanc extends IKeyboardShortcut {
@@ -1818,367 +1818,374 @@ class LinkTemplate extends ITemplate {
}
}
-/**
- * @typedef {import("./PinElement").default} PinElement
- * @typedef {import("./LinkMessageElement").default} LinkMessageElement
- */
-class LinkElement extends IElement {
-
- static tagName = "ueb-link"
- /** @type {PinElement} */
- #source
- /** @type {PinElement} */
- #destination
- #nodeDeleteHandler
- #nodeDragSourceHandler
- #nodeDragDestinatonHandler
- sourceLocation = [0, 0]
- /** @type {SVGPathElement} */
- pathElement
- /** @type {LinkMessageElement} */
- linkMessageElement
- originatesFromInput = false
- destinationLocation = [0, 0]
-
- /**
- * @param {?PinElement} source
- * @param {?PinElement} destination
- */
- constructor(source, destination) {
- super({}, new LinkTemplate());
- /** @type {import("../template/LinkTemplate").default} */
- this.template;
- const self = this;
- this.#nodeDeleteHandler = _ => self.remove();
- this.#nodeDragSourceHandler = e => self.addSourceLocation(e.detail.value);
- this.#nodeDragDestinatonHandler = e => self.addDestinationLocation(e.detail.value);
- if (source) {
- this.setSourcePin(source);
- }
- if (destination) {
- this.setDestinationPin(destination);
- }
- if (source && destination) {
- this.#linkPins();
- }
- }
-
- #linkPins() {
- this.#source.linkTo(this.#destination);
- this.#destination.linkTo(this.#source);
- }
-
- #unlinkPins() {
- this.#source.unlinkFrom(this.#destination);
- this.#destination.unlinkFrom(this.#source);
- }
-
- /**
- * @returns {Number[]}
- */
- getSourceLocation() {
- return this.sourceLocation
- }
-
- /**
- * @param {Number[]} offset
- */
- addSourceLocation(offset) {
- const location = [
- this.sourceLocation[0] + offset[0],
- this.sourceLocation[1] + offset[1]
- ];
- this.sourceLocation = location;
- this.template.applyFullLocation(this);
- }
-
- /**
- * @param {Number[]} location
- */
- setSourceLocation(location) {
- if (location == null) {
- location = this.#source.template.getLinkLocation(this.#source);
- }
- this.sourceLocation = location;
- this.template.applySourceLocation(this);
- }
-
- /**
- * @returns {Number[]}
- */
- getDestinationLocation() {
- return this.destinationLocation
- }
-
- /**
- * @param {Number[]} offset
- */
- addDestinationLocation(offset) {
- const location = [
- this.destinationLocation[0] + offset[0],
- this.destinationLocation[1] + offset[1]
- ];
- this.setDestinationLocation(location);
- }
-
- /**
- * @param {Number[]} location
- */
- setDestinationLocation(location) {
- if (location == null) {
- location = this.#destination.template.getLinkLocation(this.#destination);
- }
- this.destinationLocation = location;
- this.template.applyFullLocation(this);
- }
-
- /**
- * @returns {PinElement}
- */
- getSourcePin() {
- return this.#source
- }
-
- /**
- * @param {PinElement} pin
- */
- setSourcePin(pin) {
- if (this.#source) {
- const nodeElement = this.#source.getNodeElement();
- nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
- nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragSourceHandler);
- if (this.#destination) {
- this.#unlinkPins();
- }
- }
- this.#source = pin;
- if (this.#source) {
- const nodeElement = this.#source.getNodeElement();
- this.originatesFromInput = pin.isInput();
- nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
- nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragSourceHandler);
- this.setSourceLocation();
- if (this.#destination) {
- this.#linkPins();
- }
- }
- }
-
- /**
- * @returns {PinElement}
- */
- getDestinationPin() {
- return this.#destination
- }
-
- /**
- * @param {PinElement} pin
- */
- setDestinationPin(pin) {
- if (this.#destination) {
- const nodeElement = this.#destination.getNodeElement();
- nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
- nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
- if (this.#source) {
- this.#unlinkPins();
- }
- }
- this.#destination = pin;
- if (this.#destination) {
- const nodeElement = this.#destination.getNodeElement();
- nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
- nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
- this.setDestinationLocation();
- if (this.#source) {
- this.#linkPins();
- }
- }
- }
-
- /**
- * @param {LinkMessageElement} linkMessage
- */
- setLinkMessage(linkMessage) {
- if (linkMessage) {
- this.template.applyLinkMessage(this, linkMessage);
- } else if (this.linkMessageElement) {
- this.linkMessageElement.remove();
- this.linkMessageElement = null;
- }
- }
-
- startDragging() {
- this.template.applyStartDragging(this);
- }
-
- finishDragging() {
- this.template.applyFinishDragging(this);
- }
-}
-
+/**
+ * @typedef {import("./PinElement").default} PinElement
+ * @typedef {import("./LinkMessageElement").default} LinkMessageElement
+ */
+class LinkElement extends IElement {
+
+ static tagName = "ueb-link"
+ /** @type {PinElement} */
+ #source
+ /** @type {PinElement} */
+ #destination
+ #nodeDeleteHandler
+ #nodeDragSourceHandler
+ #nodeDragDestinatonHandler
+ sourceLocation = [0, 0]
+ /** @type {SVGPathElement} */
+ pathElement
+ /** @type {LinkMessageElement} */
+ linkMessageElement
+ originatesFromInput = false
+ destinationLocation = [0, 0]
+
+ /**
+ * @param {?PinElement} source
+ * @param {?PinElement} destination
+ */
+ constructor(source, destination) {
+ super({}, new LinkTemplate());
+ /** @type {import("../template/LinkTemplate").default} */
+ this.template;
+ const self = this;
+ this.#nodeDeleteHandler = _ => self.remove();
+ this.#nodeDragSourceHandler = e => self.addSourceLocation(e.detail.value);
+ this.#nodeDragDestinatonHandler = e => self.addDestinationLocation(e.detail.value);
+ if (source) {
+ this.setSourcePin(source);
+ }
+ if (destination) {
+ this.setDestinationPin(destination);
+ }
+ if (source && destination) {
+ this.#linkPins();
+ }
+ }
+
+ #linkPins() {
+ this.#source.linkTo(this.#destination);
+ this.#destination.linkTo(this.#source);
+ }
+
+ #unlinkPins() {
+ if (this.#source && this.#destination) {
+ this.#source.unlinkFrom(this.#destination);
+ this.#destination.unlinkFrom(this.#source);
+ }
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.#unlinkPins();
+ }
+
+ /**
+ * @returns {Number[]}
+ */
+ getSourceLocation() {
+ return this.sourceLocation
+ }
+
+ /**
+ * @param {Number[]} offset
+ */
+ addSourceLocation(offset) {
+ const location = [
+ this.sourceLocation[0] + offset[0],
+ this.sourceLocation[1] + offset[1]
+ ];
+ this.sourceLocation = location;
+ this.template.applyFullLocation(this);
+ }
+
+ /**
+ * @param {Number[]} location
+ */
+ setSourceLocation(location) {
+ if (location == null) {
+ location = this.#source.template.getLinkLocation(this.#source);
+ }
+ this.sourceLocation = location;
+ this.template.applySourceLocation(this);
+ }
+
+ /**
+ * @returns {Number[]}
+ */
+ getDestinationLocation() {
+ return this.destinationLocation
+ }
+
+ /**
+ * @param {Number[]} offset
+ */
+ addDestinationLocation(offset) {
+ const location = [
+ this.destinationLocation[0] + offset[0],
+ this.destinationLocation[1] + offset[1]
+ ];
+ this.setDestinationLocation(location);
+ }
+
+ /**
+ * @param {Number[]} location
+ */
+ setDestinationLocation(location) {
+ if (location == null) {
+ location = this.#destination.template.getLinkLocation(this.#destination);
+ }
+ this.destinationLocation = location;
+ this.template.applyFullLocation(this);
+ }
+
+ /**
+ * @returns {PinElement}
+ */
+ getSourcePin() {
+ return this.#source
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ setSourcePin(pin) {
+ if (this.#source) {
+ const nodeElement = this.#source.getNodeElement();
+ nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
+ nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragSourceHandler);
+ if (this.#destination) {
+ this.#unlinkPins();
+ }
+ }
+ this.#source = pin;
+ if (this.#source) {
+ const nodeElement = this.#source.getNodeElement();
+ this.originatesFromInput = pin.isInput();
+ nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
+ nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragSourceHandler);
+ this.setSourceLocation();
+ if (this.#destination) {
+ this.#linkPins();
+ }
+ }
+ }
+
+ /**
+ * @returns {PinElement}
+ */
+ getDestinationPin() {
+ return this.#destination
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ setDestinationPin(pin) {
+ if (this.#destination) {
+ const nodeElement = this.#destination.getNodeElement();
+ nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
+ nodeElement.removeEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
+ if (this.#source) {
+ this.#unlinkPins();
+ }
+ }
+ this.#destination = pin;
+ if (this.#destination) {
+ const nodeElement = this.#destination.getNodeElement();
+ nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
+ nodeElement.addEventListener(Configuration.nodeDragLocalEventName, this.#nodeDragDestinatonHandler);
+ this.setDestinationLocation();
+ if (this.#source) {
+ this.#linkPins();
+ }
+ }
+ }
+
+ /**
+ * @param {LinkMessageElement} linkMessage
+ */
+ setLinkMessage(linkMessage) {
+ if (linkMessage) {
+ this.template.applyLinkMessage(this, linkMessage);
+ } else if (this.linkMessageElement) {
+ this.linkMessageElement.remove();
+ this.linkMessageElement = null;
+ }
+ }
+
+ startDragging() {
+ this.template.applyStartDragging(this);
+ }
+
+ finishDragging() {
+ this.template.applyFinishDragging(this);
+ }
+}
+
customElements.define(LinkElement.tagName, LinkElement);
-class IPointing extends IContext {
-
- constructor(target, blueprint, options) {
- super(target, blueprint, options);
- this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement;
- }
-
- /**
- * @param {MouseEvent} mouseEvent
- */
- locationFromEvent(mouseEvent) {
- return this.blueprint.compensateTranslation(
- Utility.convertLocation(
- [mouseEvent.clientX, mouseEvent.clientY],
- this.movementSpace))
- }
+class IPointing extends IContext {
+
+ constructor(target, blueprint, options) {
+ super(target, blueprint, options);
+ this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement;
+ }
+
+ /**
+ * @param {MouseEvent} mouseEvent
+ */
+ locationFromEvent(mouseEvent) {
+ return this.blueprint.compensateTranslation(
+ Utility.convertLocation(
+ [mouseEvent.clientX, mouseEvent.clientY],
+ this.movementSpace))
+ }
}
-/**
- * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
- */
-class IMouseClickDrag extends IPointing {
-
- /** @type {(e: MouseEvent) => void} */
- #mouseDownHandler
-
- /** @type {(e: MouseEvent) => void} */
- #mouseStartedMovingHandler
-
- /** @type {(e: MouseEvent) => void} */
- #mouseMoveHandler
-
- /** @type {(e: MouseEvent) => void} */
- #mouseUpHandler
-
- #trackingMouse = false
-
- started = false
-
- constructor(target, blueprint, options) {
- super(target, blueprint, options);
- this.clickButton = options?.clickButton ?? 0;
- this.exitAnyButton = options?.exitAnyButton ?? true;
- this.moveEverywhere = options?.moveEverywhere ?? false;
- this.looseTarget = options?.looseTarget ?? false;
- this.consumeClickEvent = options?.consumeClickEvent ?? true;
- this.clickedPosition = [0, 0];
-
- const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace;
- let self = this;
-
- this.#mouseDownHandler = e => {
- this.blueprint.setFocused(true);
- switch (e.button) {
- case self.clickButton:
- // Either doesn't matter or consider the click only when clicking on the parent, not descandants
- if (self.looseTarget || e.target == e.currentTarget) {
- e.preventDefault();
- if (this.consumeClickEvent) {
- e.stopImmediatePropagation(); // Captured, don't call anyone else
- }
- // Attach the listeners
- movementListenedElement.addEventListener("mousemove", self.#mouseStartedMovingHandler);
- self.clickedPosition = self.locationFromEvent(e);
- self.clicked(self.clickedPosition);
- }
- break
- default:
- if (!self.exitAnyButton) {
- self.#mouseUpHandler(e);
- }
- break
- }
- };
-
- this.#mouseStartedMovingHandler = e => {
- e.preventDefault();
- if (this.consumeClickEvent) {
- e.stopImmediatePropagation(); // Captured, don't call anyone else
- }
- // Delegate from now on to self.#mouseMoveHandler
- movementListenedElement.removeEventListener("mousemove", self.#mouseStartedMovingHandler);
- movementListenedElement.addEventListener("mousemove", self.#mouseMoveHandler);
- document.addEventListener("mouseup", self.#mouseUpHandler);
- // Handler calls e.preventDefault() when it receives the event, this means dispatchEvent returns false
- const dragEvent = self.getEvent(Configuration.trackingMouseEventName.begin);
- self.#trackingMouse = this.target.dispatchEvent(dragEvent) == false;
- // Do actual actions
- self.startDrag();
- self.started = true;
- };
-
- this.#mouseMoveHandler = e => {
- e.preventDefault();
- if (this.consumeClickEvent) {
- e.stopImmediatePropagation(); // Captured, don't call anyone else
- }
- const location = self.locationFromEvent(e);
- const movement = [e.movementX, e.movementY];
- self.dragTo(location, movement);
- if (self.#trackingMouse) {
- self.blueprint.entity.mousePosition = self.locationFromEvent(e);
- }
- };
-
- this.#mouseUpHandler = e => {
- if (!self.exitAnyButton || e.button == self.clickButton) {
- e.preventDefault();
- if (this.consumeClickEvent) {
- e.stopImmediatePropagation(); // Captured, don't call anyone else
- }
- // Remove the handlers of "mousemove" and "mouseup"
- movementListenedElement.removeEventListener("mousemove", self.#mouseStartedMovingHandler);
- movementListenedElement.removeEventListener("mousemove", self.#mouseMoveHandler);
- document.removeEventListener("mouseup", self.#mouseUpHandler);
- self.endDrag();
- if (self.#trackingMouse) {
- const dragEvent = self.getEvent(Configuration.trackingMouseEventName.end);
- this.target.dispatchEvent(dragEvent);
- self.#trackingMouse = false;
- }
- self.started = false;
- }
- };
-
- this.target.addEventListener("mousedown", this.#mouseDownHandler);
- if (this.clickButton == 2) {
- this.target.addEventListener("contextmenu", e => e.preventDefault());
- }
- }
-
- getEvent(eventName) {
- return new CustomEvent(eventName, {
- detail: {
- tracker: this
- },
- bubbles: true,
- cancelable: true
- })
- }
-
- unlistenDOMElement() {
- super.unlistenDOMElement();
- this.target.removeEventListener("mousedown", this.#mouseDownHandler);
- if (this.clickButton == 2) {
- this.target.removeEventListener("contextmenu", e => e.preventDefault());
- }
- }
-
- /* Subclasses will override the following methods */
- clicked(location) {
- }
-
- startDrag(location) {
- }
-
- dragTo(location, movement) {
- }
-
- endDrag() {
- }
+/**
+ * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
+ */
+class IMouseClickDrag extends IPointing {
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseDownHandler
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseStartedMovingHandler
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseMoveHandler
+
+ /** @type {(e: MouseEvent) => void} */
+ #mouseUpHandler
+
+ #trackingMouse = false
+
+ started = false
+
+ constructor(target, blueprint, options) {
+ super(target, blueprint, options);
+ this.clickButton = options?.clickButton ?? 0;
+ this.exitAnyButton = options?.exitAnyButton ?? true;
+ this.moveEverywhere = options?.moveEverywhere ?? false;
+ this.looseTarget = options?.looseTarget ?? false;
+ this.consumeClickEvent = options?.consumeClickEvent ?? true;
+ this.clickedPosition = [0, 0];
+
+ const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace;
+ let self = this;
+
+ this.#mouseDownHandler = e => {
+ this.blueprint.setFocused(true);
+ switch (e.button) {
+ case self.clickButton:
+ // Either doesn't matter or consider the click only when clicking on the parent, not descandants
+ if (self.looseTarget || e.target == e.currentTarget) {
+ e.preventDefault();
+ if (this.consumeClickEvent) {
+ e.stopImmediatePropagation(); // Captured, don't call anyone else
+ }
+ // Attach the listeners
+ movementListenedElement.addEventListener("mousemove", self.#mouseStartedMovingHandler);
+ self.clickedPosition = self.locationFromEvent(e);
+ self.clicked(self.clickedPosition);
+ }
+ break
+ default:
+ if (!self.exitAnyButton) {
+ self.#mouseUpHandler(e);
+ }
+ break
+ }
+ };
+
+ this.#mouseStartedMovingHandler = e => {
+ e.preventDefault();
+ if (this.consumeClickEvent) {
+ e.stopImmediatePropagation(); // Captured, don't call anyone else
+ }
+ // Delegate from now on to self.#mouseMoveHandler
+ movementListenedElement.removeEventListener("mousemove", self.#mouseStartedMovingHandler);
+ movementListenedElement.addEventListener("mousemove", self.#mouseMoveHandler);
+ document.addEventListener("mouseup", self.#mouseUpHandler);
+ // Handler calls e.preventDefault() when it receives the event, this means dispatchEvent returns false
+ const dragEvent = self.getEvent(Configuration.trackingMouseEventName.begin);
+ self.#trackingMouse = this.target.dispatchEvent(dragEvent) == false;
+ // Do actual actions
+ self.startDrag();
+ self.started = true;
+ };
+
+ this.#mouseMoveHandler = e => {
+ e.preventDefault();
+ if (this.consumeClickEvent) {
+ e.stopImmediatePropagation(); // Captured, don't call anyone else
+ }
+ const location = self.locationFromEvent(e);
+ const movement = [e.movementX, e.movementY];
+ self.dragTo(location, movement);
+ if (self.#trackingMouse) {
+ self.blueprint.entity.mousePosition = self.locationFromEvent(e);
+ }
+ };
+
+ this.#mouseUpHandler = e => {
+ if (!self.exitAnyButton || e.button == self.clickButton) {
+ e.preventDefault();
+ if (this.consumeClickEvent) {
+ e.stopImmediatePropagation(); // Captured, don't call anyone else
+ }
+ // Remove the handlers of "mousemove" and "mouseup"
+ movementListenedElement.removeEventListener("mousemove", self.#mouseStartedMovingHandler);
+ movementListenedElement.removeEventListener("mousemove", self.#mouseMoveHandler);
+ document.removeEventListener("mouseup", self.#mouseUpHandler);
+ self.endDrag();
+ if (self.#trackingMouse) {
+ const dragEvent = self.getEvent(Configuration.trackingMouseEventName.end);
+ this.target.dispatchEvent(dragEvent);
+ self.#trackingMouse = false;
+ }
+ self.started = false;
+ }
+ };
+
+ this.target.addEventListener("mousedown", this.#mouseDownHandler);
+ if (this.clickButton == 2) {
+ this.target.addEventListener("contextmenu", e => e.preventDefault());
+ }
+ }
+
+ getEvent(eventName) {
+ return new CustomEvent(eventName, {
+ detail: {
+ tracker: this
+ },
+ bubbles: true,
+ cancelable: true
+ })
+ }
+
+ unlistenDOMElement() {
+ super.unlistenDOMElement();
+ this.target.removeEventListener("mousedown", this.#mouseDownHandler);
+ if (this.clickButton == 2) {
+ this.target.removeEventListener("contextmenu", e => e.preventDefault());
+ }
+ }
+
+ /* Subclasses will override the following methods */
+ clicked(location) {
+ }
+
+ startDrag(location) {
+ }
+
+ dragTo(location, movement) {
+ }
+
+ endDrag() {
+ }
}
class MouseScrollGraph extends IMouseClickDrag {
@@ -2305,181 +2312,181 @@ class MouseMoveNodes extends IMouseClickDrag {
}
}
-/** @typedef {import("../template/SelectableDraggableTemplate").default} SelectableDraggableTemplate */
-
-class ISelectableDraggableElement extends IElement {
-
- constructor(...args) {
- super(...args);
- this.dragObject = null;
- this.location = [0, 0];
- this.selected = false;
- /** @type {SelectableDraggableTemplate} */
- this.template;
-
- let self = this;
- this.dragHandler = (e) => {
- self.addLocation(e.detail.value);
- };
- }
-
- createInputObjects() {
- return [
- new MouseMoveNodes(this, this.blueprint, {
- looseTarget: true
- }),
- ]
- }
-
- setLocation(value = [0, 0]) {
- const d = [value[0] - this.location[0], value[1] - this.location[1]];
- const dragLocalEvent = new CustomEvent(Configuration.nodeDragLocalEventName, {
- detail: {
- value: d
- },
- bubbles: false,
- cancelable: true
- });
- this.location = value;
- this.template.applyLocation(this);
- this.dispatchEvent(dragLocalEvent);
- }
-
- addLocation(value) {
- this.setLocation([this.location[0] + value[0], this.location[1] + value[1]]);
- }
-
- setSelected(value = true) {
- if (this.selected == value) {
- return
- }
- this.selected = value;
- if (this.selected) {
- this.blueprint.addEventListener(Configuration.nodeDragEventName, this.dragHandler);
- } else {
- this.blueprint.removeEventListener(Configuration.nodeDragEventName, this.dragHandler);
- }
- this.template.applySelected(this);
- }
-
- dispatchDragEvent(value) {
- if (!this.selected) {
- this.blueprint.unselectAll();
- this.setSelected(true);
- }
- const dragEvent = new CustomEvent(Configuration.nodeDragEventName, {
- detail: {
- value: value
- },
- bubbles: true,
- cancelable: true
- });
- this.dispatchEvent(dragEvent);
- }
-
- snapToGrid() {
- let snappedLocation = this.blueprint.snapToGrid(this.location);
- if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) {
- this.setLocation(snappedLocation);
- }
- }
+/** @typedef {import("../template/SelectableDraggableTemplate").default} SelectableDraggableTemplate */
+
+class ISelectableDraggableElement extends IElement {
+
+ constructor(...args) {
+ super(...args);
+ this.dragObject = null;
+ this.location = [0, 0];
+ this.selected = false;
+ /** @type {SelectableDraggableTemplate} */
+ this.template;
+
+ let self = this;
+ this.dragHandler = (e) => {
+ self.addLocation(e.detail.value);
+ };
+ }
+
+ createInputObjects() {
+ return [
+ new MouseMoveNodes(this, this.blueprint, {
+ looseTarget: true
+ }),
+ ]
+ }
+
+ setLocation(value = [0, 0]) {
+ const d = [value[0] - this.location[0], value[1] - this.location[1]];
+ const dragLocalEvent = new CustomEvent(Configuration.nodeDragLocalEventName, {
+ detail: {
+ value: d
+ },
+ bubbles: false,
+ cancelable: true
+ });
+ this.location = value;
+ this.template.applyLocation(this);
+ this.dispatchEvent(dragLocalEvent);
+ }
+
+ addLocation(value) {
+ this.setLocation([this.location[0] + value[0], this.location[1] + value[1]]);
+ }
+
+ setSelected(value = true) {
+ if (this.selected == value) {
+ return
+ }
+ this.selected = value;
+ if (this.selected) {
+ this.blueprint.addEventListener(Configuration.nodeDragEventName, this.dragHandler);
+ } else {
+ this.blueprint.removeEventListener(Configuration.nodeDragEventName, this.dragHandler);
+ }
+ this.template.applySelected(this);
+ }
+
+ dispatchDragEvent(value) {
+ if (!this.selected) {
+ this.blueprint.unselectAll();
+ this.setSelected(true);
+ }
+ const dragEvent = new CustomEvent(Configuration.nodeDragEventName, {
+ detail: {
+ value: value
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ this.dispatchEvent(dragEvent);
+ }
+
+ snapToGrid() {
+ let snappedLocation = this.blueprint.snapToGrid(this.location);
+ if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) {
+ this.setLocation(snappedLocation);
+ }
+ }
}
-/**
- * @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
- */
-class LinkMessageTemplate extends ITemplate {
-
- /**
- * Computes the html content of the target element.
- * @param {LinkMessageElement} linkMessage
- */
- render(linkMessage) {
- return html`
-
-
- `
- }
-
- /**
- * Applies the style to the element.
- * @param {LinkMessageElement} linkMessage
- */
- apply(linkMessage) {
- super.apply(linkMessage);
- const linkMessageSetup = _ => linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
- linkMessage.linkElement.getSourcePin(),
- linkMessage.linkElement.getDestinationPin()
- );
- linkMessage.linkElement = linkMessage.closest(LinkElement.tagName);
- if (linkMessage.linkElement) {
- linkMessageSetup();
- } else {
- window.customElements.whenDefined(linkMessage.constructor.tagName).then(linkMessage);
- }
- }
-
-}
-
-/**
- * @typedef {import("./PinElement").default} PinElement
- * @typedef {import("./LinkElement").default} LinkElement
- * @typedef {(sourcePin: PinElement, sourcePin: PinElement) => String} LinkRetrieval
- */
-class LinkMessageElement extends IElement {
-
- static tagName = "ueb-link-message"
- static convertType = _ => new LinkMessageElement(
- "ueb-icon-conver-type",
- /** @type {LinkRetrieval} */
- (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
- )
- static correct = _ => new LinkMessageElement(
- "ueb-icon-correct",
- /** @type {LinkRetrieval} */
- (s, d) => ""
- )
- static directionsIncompatible = _ => new LinkMessageElement(
- "ueb-icon-directions-incompatible",
- /** @type {LinkRetrieval} */
- (s, d) => "Directions are not compatbile."
- )
- static placeNode = _ => new LinkMessageElement(
- "ueb-icon-place-node",
- /** @type {LinkRetrieval} */
- (s, d) => "Place a new node."
- )
- static replaceLink = _ => new LinkMessageElement(
- "ueb-icon-replace-link",
- /** @type {LinkRetrieval} */
- (s, d) => "Replace existing input connections."
- )
- static sameNode = _ => new LinkMessageElement(
- "ueb-icon-same-node",
- /** @type {LinkRetrieval} */
- (s, d) => "Both are on the same node."
- )
- static typesIncompatible = _ => new LinkMessageElement(
- "ueb-icon-types-incompatible",
- /** @type {LinkRetrieval} */
- (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
- )
-
- /** @type {String} */
- icon
- /** @type {String} */
- message
- /** @type {LinkElement} */
- linkElement
-
- constructor(icon, message) {
- super({}, new LinkMessageTemplate());
- this.icon = icon;
- this.message = message;
- }
-
+/**
+ * @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
+ */
+class LinkMessageTemplate extends ITemplate {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {LinkMessageElement} linkMessage
+ */
+ render(linkMessage) {
+ return html`
+
+
+ `
+ }
+
+ /**
+ * Applies the style to the element.
+ * @param {LinkMessageElement} linkMessage
+ */
+ apply(linkMessage) {
+ super.apply(linkMessage);
+ const linkMessageSetup = _ => linkMessage.querySelector(".ueb-link-message").innerText = linkMessage.message(
+ linkMessage.linkElement.getSourcePin(),
+ linkMessage.linkElement.getDestinationPin()
+ );
+ linkMessage.linkElement = linkMessage.closest(LinkElement.tagName);
+ if (linkMessage.linkElement) {
+ linkMessageSetup();
+ } else {
+ window.customElements.whenDefined(linkMessage.constructor.tagName).then(linkMessage);
+ }
+ }
+
}
+/**
+ * @typedef {import("./PinElement").default} PinElement
+ * @typedef {import("./LinkElement").default} LinkElement
+ * @typedef {(sourcePin: PinElement, sourcePin: PinElement) => String} LinkRetrieval
+ */
+class LinkMessageElement extends IElement {
+
+ static tagName = "ueb-link-message"
+ static convertType = _ => new LinkMessageElement(
+ "ueb-icon-conver-type",
+ /** @type {LinkRetrieval} */
+ (s, d) => `Convert ${s.getType()} to ${d.getType()}.`
+ )
+ static correct = _ => new LinkMessageElement(
+ "ueb-icon-correct",
+ /** @type {LinkRetrieval} */
+ (s, d) => ""
+ )
+ static directionsIncompatible = _ => new LinkMessageElement(
+ "ueb-icon-directions-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Directions are not compatbile."
+ )
+ static placeNode = _ => new LinkMessageElement(
+ "ueb-icon-place-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Place a new node."
+ )
+ static replaceLink = _ => new LinkMessageElement(
+ "ueb-icon-replace-link",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Replace existing input connections."
+ )
+ static sameNode = _ => new LinkMessageElement(
+ "ueb-icon-same-node",
+ /** @type {LinkRetrieval} */
+ (s, d) => "Both are on the same node."
+ )
+ static typesIncompatible = _ => new LinkMessageElement(
+ "ueb-icon-types-incompatible",
+ /** @type {LinkRetrieval} */
+ (s, d) => `${s.getType()} is not compatible with ${d.getType()}.`
+ )
+
+ /** @type {String} */
+ icon
+ /** @type {String} */
+ message
+ /** @type {LinkElement} */
+ linkElement
+
+ constructor(icon, message) {
+ super({}, new LinkMessageTemplate());
+ this.icon = icon;
+ this.message = message;
+ }
+
+}
+
customElements.define(LinkMessageElement.tagName, LinkMessageElement);
/**
@@ -2544,6 +2551,7 @@ class MouseCreateLink extends IMouseClickDrag {
startDrag() {
this.link = new LinkElement(this.target, null);
+ this.blueprint.nodesContainerElement.prepend(this.link);
this.setLinkMessage(LinkMessageElement.placeNode());
this.#listenedPins = this.blueprint.querySelectorAll(this.target.constructor.tagName);
this.#listenedPins.forEach(pin => {
@@ -2581,7 +2589,6 @@ class MouseCreateLink extends IMouseClickDrag {
setLinkMessage(linkMessage) {
this.link.setLinkMessage(linkMessage);
- this.blueprint.nodesContainerElement.prepend(this.link);
}
}
@@ -2621,10 +2628,15 @@ class PinTemplate extends ITemplate {
"ueb-pin-" + sanitizeText(pin.getType())
);
pin.clickableElement = pin;
- pin.nodeElement = pin.closest(NodeElement.tagName);
- if (!pin.nodeElement) {
- window.customElements.whenDefined(linkMessage.constructor.tagName).then(linkMessage);
- }
+ window.customElements.whenDefined(NodeElement.tagName).then(pin.nodeElement = pin.closest(NodeElement.tagName));
+ pin.getLinks().forEach(pinReference => {
+ const targetPin = pin.blueprint.getPin(pinReference.pinGuid);
+ if (linkedToPin) {
+ const [sourcePin, destinationPin] = pin.isOutput() ? [pin, targetPin] : [targetPin, pin];
+ pin.blueprint.addGraphElement(new LinkElement(sourcePin, destinationPin));
+ }
+ });
+
}
/**
@@ -2650,109 +2662,123 @@ class PinTemplate extends ITemplate {
}
}
-/**
- * @typedef {import("./NodeElement").default} NodeElement
- */
-class PinElement extends IElement {
-
- static tagName = "ueb-pin"
-
- /** @type {NodeElement} */
- nodeElement
-
- /** @type {HTMLElement} */
- clickableElement
-
- /** @type {String} */
- #color
-
- constructor(entity) {
- super(entity, new PinTemplate());
- /** @type {import("../entity/PinEntity").default} */
- this.entity;
- /** @type {PinTemplate} */
- this.template;
- }
-
- connectedCallback() {
- super.connectedCallback();
- this.#color = window.getComputedStyle(this).getPropertyValue("--ueb-pin-color");
- }
-
- createInputObjects() {
- return [
- new MouseCreateLink(this.clickableElement, this.blueprint, {
- moveEverywhere: true,
- looseTarget: true
- }),
- ]
- }
-
- /**
- * @returns {String}
- */
- getPinName() {
- return this.entity.PinName
- }
-
- /**
- * @returns {String}
- */
- getPinDisplayName() {
- return this.entity.PinName
- }
-
- isInput() {
- return this.entity.isInput()
- }
-
- isOutput() {
- return this.entity.isOutput()
- }
-
- isConnected() {
- return this.entity.isConnected()
- }
-
- getType() {
- return this.entity.getType()
- }
-
- getClickableElement() {
- return this.clickableElement
- }
-
- getColor() {
- return this.#color
- }
-
- /**
- * Returns The exact location where the link originates from or arrives at.
- * @returns {Number[]} The location array
- */
- getLinkLocation() {
- return this.template.getLinkLocation(this)
- }
-
- getNodeElement() {
- return this.closest("ueb-node")
- }
-
- /**
- * @param {PinElement} targetPinElement
- */
- linkTo(targetPinElement) {
- this.entity.linkTo(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity);
- }
-
- /**
- * @param {PinElement} targetPinElement
- */
- unlinkFrom(targetPinElement) {
- this.entity.unlinkFrom(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity);
- }
-}
-
+/**
+ * @typedef {import("./NodeElement").default} NodeElement
+ * @typedef {import("../entity/GuidEntity").default} GuidEntity
+ */
+class PinElement extends IElement {
+
+ static tagName = "ueb-pin"
+
+ /** @type {NodeElement} */
+ nodeElement
+
+ /** @type {HTMLElement} */
+ clickableElement
+
+ /** @type {String} */
+ #color
+
+ constructor(entity) {
+ super(entity, new PinTemplate());
+ /** @type {import("../entity/PinEntity").default} */
+ this.entity;
+ /** @type {PinTemplate} */
+ this.template;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.#color = window.getComputedStyle(this).getPropertyValue("--ueb-pin-color");
+ }
+
+ createInputObjects() {
+ return [
+ new MouseCreateLink(this.clickableElement, this.blueprint, {
+ moveEverywhere: true,
+ looseTarget: true
+ }),
+ ]
+ }
+
+ /** @type {GuidEntity} */
+ GetPinId() {
+ return this.entity.PinId
+ }
+
+ /**
+ * @returns {String}
+ */
+ getPinName() {
+ return this.entity.PinName
+ }
+
+ /**
+ * @returns {String}
+ */
+ getPinDisplayName() {
+ return this.entity.PinName
+ }
+
+ isInput() {
+ return this.entity.isInput()
+ }
+
+ isOutput() {
+ return this.entity.isOutput()
+ }
+
+ isConnected() {
+ return this.entity.isConnected()
+ }
+
+ getType() {
+ return this.entity.getType()
+ }
+
+ getClickableElement() {
+ return this.clickableElement
+ }
+
+ getColor() {
+ return this.#color
+ }
+
+ /**
+ * Returns The exact location where the link originates from or arrives at.
+ * @returns {Number[]} The location array
+ */
+ getLinkLocation() {
+ return this.template.getLinkLocation(this)
+ }
+
+ getNodeElement() {
+ return this.closest("ueb-node")
+ }
+
+ getLinks() {
+ return this.entity.LinkedTo.map(pinReference =>
+ pinReference
+ )
+ }
+
+ /**
+ * @param {PinElement} targetPinElement
+ */
+ linkTo(targetPinElement) {
+ this.entity.linkTo(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity);
+ this.template.applyConnected(this);
+ }
+
+ /**
+ * @param {PinElement} targetPinElement
+ */
+ unlinkFrom(targetPinElement) {
+ this.entity.unlinkFrom(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity);
+ this.template.applyConnected(this);
+ }
+}
+
customElements.define(PinElement.tagName, PinElement);
/**
@@ -2830,65 +2856,79 @@ class NodeTemplate extends SelectableDraggableTemplate {
pins.filter(v => v.isInput()).forEach(v => inputContainer.appendChild(new PinElement(v)));
pins.filter(v => v.isOutput()).forEach(v => outputContainer.appendChild(new PinElement(v)));
}
+
+ /**
+ * @param {NodeElement} node
+ * @returns {NodeListOf}
+ */
+ getPinElements(node) {
+ return node.querySelectorAll(PinElement.tagName)
+ }
}
-class NodeElement extends ISelectableDraggableElement {
-
- static tagName = "ueb-node"
-
- /**
- * @param {ObjectEntity} entity
- */
- constructor(entity) {
- super(entity, new NodeTemplate());
- /** @type {ObjectEntity} */
- this.entity;
- this.dragLinkObjects = [];
- super.setLocation([this.entity.NodePosX, this.entity.NodePosY]);
- }
-
- static fromSerializedObject(str) {
- let entity = SerializerFactory.getSerializer(ObjectEntity).read(str);
- return new NodeElement(entity)
- }
-
- disconnectedCallback() {
- super.disconnectedCallback();
- this.dispatchDeleteEvent();
- }
-
- getNodeName() {
- return this.entity.getName()
- }
-
- /**
- * @returns {PinEntity[]}
- */
- getPinEntities() {
- return this.entity.CustomProperties.filter(v => v instanceof PinEntity)
- }
-
- connectedCallback() {
- this.getAttribute("type")?.trim();
- super.connectedCallback();
- }
-
- setLocation(value = [0, 0]) {
- let nodeType = this.entity.NodePosX.constructor;
- this.entity.NodePosX = new nodeType(value[0]);
- this.entity.NodePosY = new nodeType(value[1]);
- super.setLocation(value);
- }
-
- dispatchDeleteEvent(value) {
- let deleteEvent = new CustomEvent(Configuration.nodeDeleteEventName, {
- bubbles: true,
- cancelable: true,
- });
- this.dispatchEvent(deleteEvent);
- }
-}
-
+class NodeElement extends ISelectableDraggableElement {
+
+ static tagName = "ueb-node"
+
+ /**
+ * @param {ObjectEntity} entity
+ */
+ constructor(entity) {
+ super(entity, new NodeTemplate());
+ /** @type {ObjectEntity} */
+ this.entity;
+ /** @type {NodeTemplate} */
+ this.template;
+ this.dragLinkObjects = [];
+ super.setLocation([this.entity.NodePosX, this.entity.NodePosY]);
+ }
+
+ static fromSerializedObject(str) {
+ let entity = SerializerFactory.getSerializer(ObjectEntity).read(str);
+ return new NodeElement(entity)
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.dispatchDeleteEvent();
+ }
+
+ getNodeName() {
+ return this.entity.getName()
+ }
+
+ getPinElements() {
+ return this.template.getPinElements(this)
+ }
+
+ /**
+ * @returns {PinEntity[]}
+ */
+ getPinEntities() {
+ return this.entity.CustomProperties.filter(v => v instanceof PinEntity)
+ }
+
+ connectedCallback() {
+ this.getAttribute("type")?.trim();
+ super.connectedCallback();
+ }
+
+ setLocation(value = [0, 0]) {
+ let nodeType = this.entity.NodePosX.constructor;
+ this.entity.NodePosX = new nodeType(value[0]);
+ this.entity.NodePosY = new nodeType(value[1]);
+ super.setLocation(value);
+ }
+
+ dispatchDeleteEvent(value) {
+ let deleteEvent = new CustomEvent(Configuration.nodeDeleteEventName, {
+ bubbles: true,
+ cancelable: true,
+ });
+ this.dispatchEvent(deleteEvent);
+ }
+}
+
customElements.define(NodeElement.tagName, NodeElement);
class Paste extends IContext {
@@ -3001,50 +3041,50 @@ class Unfocus extends IContext {
}
}
-class IMouseWheel extends IPointing {
-
- /** @type {(e: WheelEvent) => void} */
- #mouseWheelHandler
-
- /** @type {(e: WheelEvent) => void} */
- #mouseParentWheelHandler
-
- /**
- * @param {HTMLElement} target
- * @param {import("../../Blueprint").default} blueprint
- * @param {Object} options
- */
- constructor(target, blueprint, options) {
- options.wantsFocusCallback = true;
- super(target, blueprint, options);
- this.looseTarget = options?.looseTarget ?? true;
- let self = this;
-
- this.#mouseWheelHandler = e => {
- e.preventDefault();
- const location = self.locationFromEvent(e);
- self.wheel(Math.sign(e.deltaY), location);
- };
- this.#mouseParentWheelHandler = e => e.preventDefault();
-
- if (this.blueprint.focused) {
- this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false);
- }
- }
-
- listenEvents() {
- this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false);
- this.movementSpace.parentElement?.addEventListener("wheel", this.#mouseParentWheelHandler);
- }
-
- unlistenEvents() {
- this.movementSpace.removeEventListener("wheel", this.#mouseWheelHandler, false);
- this.movementSpace.parentElement?.removeEventListener("wheel", this.#mouseParentWheelHandler);
- }
-
- /* Subclasses will override the following method */
- wheel(variation, location) {
- }
+class IMouseWheel extends IPointing {
+
+ /** @type {(e: WheelEvent) => void} */
+ #mouseWheelHandler
+
+ /** @type {(e: WheelEvent) => void} */
+ #mouseParentWheelHandler
+
+ /**
+ * @param {HTMLElement} target
+ * @param {import("../../Blueprint").default} blueprint
+ * @param {Object} options
+ */
+ constructor(target, blueprint, options) {
+ options.wantsFocusCallback = true;
+ super(target, blueprint, options);
+ this.looseTarget = options?.looseTarget ?? true;
+ let self = this;
+
+ this.#mouseWheelHandler = e => {
+ e.preventDefault();
+ const location = self.locationFromEvent(e);
+ self.wheel(Math.sign(e.deltaY), location);
+ };
+ this.#mouseParentWheelHandler = e => e.preventDefault();
+
+ if (this.blueprint.focused) {
+ this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false);
+ }
+ }
+
+ listenEvents() {
+ this.movementSpace.addEventListener("wheel", this.#mouseWheelHandler, false);
+ this.movementSpace.parentElement?.addEventListener("wheel", this.#mouseParentWheelHandler);
+ }
+
+ unlistenEvents() {
+ this.movementSpace.removeEventListener("wheel", this.#mouseWheelHandler, false);
+ this.movementSpace.parentElement?.removeEventListener("wheel", this.#mouseParentWheelHandler);
+ }
+
+ /* Subclasses will override the following method */
+ wheel(variation, location) {
+ }
}
class Zoom extends IMouseWheel {
@@ -3056,9 +3096,15 @@ class Zoom extends IMouseWheel {
}
}
+/**
+ * @typedef {import("./entity/GuidEntity").default} GuidEntity
+ * @typedef {import("./element/PinElement").default} PinElement
+ */
class Blueprint extends IElement {
static tagName = "ueb-blueprint"
+ /** @type {WeakMap} */
+ #pinGuidMap = new WeakMap()
/** @type {number} */
gridSize = Configuration.gridSize
/** @type {NodeElement[]}" */
@@ -3328,7 +3374,6 @@ class Blueprint extends IElement {
/**
* Returns the list of nodes in this blueprint. It can filter the list providing just the selected ones.
- * @returns {NodeElement[]} Nodes
*/
getNodes(selected = false) {
if (selected) {
@@ -3340,6 +3385,13 @@ class Blueprint extends IElement {
}
}
+ /**
+ * @param {GuidEntity} guid
+ */
+ getPin(guid) {
+ return this.#pinGuidMap[guid]
+ }
+
/**
* Returns the list of links in this blueprint.
* @returns {LinkElement[]} Nodes
@@ -3375,22 +3427,35 @@ class Blueprint extends IElement {
* @param {...IElement} graphElements
*/
addGraphElement(...graphElements) {
+ const intoArray = element => {
+ if (element instanceof NodeElement) {
+ this.nodes.push(element);
+ element.getPinElements().forEach(
+ pinElement => this.#pinGuidMap[pinElement.GetPinId()] = pinElement
+ );
+ } else if (element instanceof LinkElement) {
+ this.links.push(element);
+ }
+ };
if (this.nodesContainerElement) {
graphElements.forEach(element => {
if (element.closest(Blueprint.tagName) != this) {
+ // If not already the in target DOM position
this.nodesContainerElement.appendChild(element);
+ intoArray(element);
}
- this.nodes = [...this.querySelectorAll(NodeElement.tagName)];
- this.links = [...this.querySelectorAll(LinkElement.tagName)];
});
} else {
- graphElements.forEach(element => {
- if (element instanceof NodeElement) {
- this.nodes.push(element);
- } else if (element instanceof LinkElement) {
- this.links.push(element);
- }
- });
+ graphElements
+ .filter(element => {
+ if (element instanceof NodeElement) {
+ return !this.nodes.includes(element)
+ } else if (element instanceof LinkElement) {
+ return !this.links.includes(element)
+ }
+ return false
+ })
+ .forEach(intoArray);
}
}
diff --git a/js/Blueprint.js b/js/Blueprint.js
index ce67d5b..3bb1848 100755
--- a/js/Blueprint.js
+++ b/js/Blueprint.js
@@ -15,10 +15,15 @@ import Unfocus from "./input/mouse/Unfocus"
import Utility from "./Utility"
import Zoom from "./input/mouse/Zoom"
+/**
+ * @typedef {import("./entity/GuidEntity").default} GuidEntity
+ * @typedef {import("./element/PinElement").default} PinElement
+ */
export default class Blueprint extends IElement {
static tagName = "ueb-blueprint"
- #pinGuidMap = new Map()
+ /** @type {WeakMap} */
+ #pinGuidMap = new WeakMap()
/** @type {number} */
gridSize = Configuration.gridSize
/** @type {NodeElement[]}" */
@@ -288,7 +293,6 @@ export default class Blueprint extends IElement {
/**
* Returns the list of nodes in this blueprint. It can filter the list providing just the selected ones.
- * @returns {NodeElement[]} Nodes
*/
getNodes(selected = false) {
if (selected) {
@@ -300,6 +304,13 @@ export default class Blueprint extends IElement {
}
}
+ /**
+ * @param {GuidEntity} guid
+ */
+ getPin(guid) {
+ return this.#pinGuidMap[guid]
+ }
+
/**
* Returns the list of links in this blueprint.
* @returns {LinkElement[]} Nodes
@@ -339,8 +350,7 @@ export default class Blueprint extends IElement {
if (element instanceof NodeElement) {
this.nodes.push(element)
element.getPinElements().forEach(
- pinElement => this.#pinGuidMap[
- pinElement.]
+ pinElement => this.#pinGuidMap[pinElement.GetPinId()] = pinElement
)
} else if (element instanceof LinkElement) {
this.links.push(element)
diff --git a/js/element/LinkElement.js b/js/element/LinkElement.js
index 3e7c482..9a0d6aa 100644
--- a/js/element/LinkElement.js
+++ b/js/element/LinkElement.js
@@ -53,8 +53,15 @@ export default class LinkElement extends IElement {
}
#unlinkPins() {
- this.#source.unlinkFrom(this.#destination)
- this.#destination.unlinkFrom(this.#source)
+ if (this.#source && this.#destination) {
+ this.#source.unlinkFrom(this.#destination)
+ this.#destination.unlinkFrom(this.#source)
+ }
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback()
+ this.#unlinkPins()
}
/**
diff --git a/js/element/PinElement.js b/js/element/PinElement.js
index 08e8fc6..59ca5cf 100644
--- a/js/element/PinElement.js
+++ b/js/element/PinElement.js
@@ -107,6 +107,7 @@ export default class PinElement extends IElement {
*/
linkTo(targetPinElement) {
this.entity.linkTo(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity)
+ this.template.applyConnected(this)
}
/**
@@ -114,6 +115,7 @@ export default class PinElement extends IElement {
*/
unlinkFrom(targetPinElement) {
this.entity.unlinkFrom(targetPinElement.nodeElement.getNodeName(), targetPinElement.entity)
+ this.template.applyConnected(this)
}
}
diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js
index 539c671..ebe5c4a 100755
--- a/js/entity/PinEntity.js
+++ b/js/entity/PinEntity.js
@@ -71,13 +71,13 @@ export default class PinEntity extends IEntity {
linkTo(targetObjectName, targetPinEntity) {
/** @type {PinReferenceEntity[]} */
this.LinkedTo
- const pinExists = !this.LinkedTo.find(
+ const linkExists = this.LinkedTo.find(
/** @type {PinReferenceEntity} */
pinReferenceEntity => {
return pinReferenceEntity.objectName == targetObjectName
&& pinReferenceEntity.pinGuid == targetPinEntity.PinId
})
- if (pinExists) {
+ if (!linkExists) {
this.LinkedTo.push(new PinReferenceEntity({
objectName: targetObjectName,
pinGuid: targetPinEntity.PinId
diff --git a/js/input/mouse/MouseCreateLink.js b/js/input/mouse/MouseCreateLink.js
index 7a6654f..7ad5abe 100755
--- a/js/input/mouse/MouseCreateLink.js
+++ b/js/input/mouse/MouseCreateLink.js
@@ -64,6 +64,7 @@ export default class MouseCreateLink extends IMouseClickDrag {
startDrag() {
this.link = new LinkElement(this.target, null)
+ this.blueprint.nodesContainerElement.prepend(this.link)
this.setLinkMessage(LinkMessageElement.placeNode())
this.#listenedPins = this.blueprint.querySelectorAll(this.target.constructor.tagName)
this.#listenedPins.forEach(pin => {
@@ -101,6 +102,5 @@ export default class MouseCreateLink extends IMouseClickDrag {
setLinkMessage(linkMessage) {
this.link.setLinkMessage(linkMessage)
- this.blueprint.nodesContainerElement.prepend(this.link)
}
}
diff --git a/js/serialization/ISerializer.js b/js/serialization/ISerializer.js
index 38519a2..83a350d 100644
--- a/js/serialization/ISerializer.js
+++ b/js/serialization/ISerializer.js
@@ -35,7 +35,7 @@ export default class ISerializer {
return `"${value}"`
}
if (value instanceof Array) {
- return `(${value.map(v => serialize(v) + ",")})`
+ return `(${value.map(v => serialize(v) + ",").join("")})`
}
if (value instanceof IEntity) {
return serialize(value)
diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js
index 6ab97ec..f4b6fda 100755
--- a/js/template/NodeTemplate.js
+++ b/js/template/NodeTemplate.js
@@ -54,6 +54,7 @@ export default class NodeTemplate extends SelectableDraggableTemplate {
/**
* @param {NodeElement} node
+ * @returns {NodeListOf}
*/
getPinElements(node) {
return node.querySelectorAll(PinElement.tagName)
diff --git a/js/template/PinTemplate.js b/js/template/PinTemplate.js
index 08f3f73..baf383b 100755
--- a/js/template/PinTemplate.js
+++ b/js/template/PinTemplate.js
@@ -1,8 +1,9 @@
import html from "./html"
import ITemplate from "./ITemplate"
+import LinkElement from "../element/LinkElement"
+import NodeElement from "../element/NodeElement"
import sanitizeText from "./sanitizeText"
import Utility from "../Utility"
-import NodeElement from "../element/NodeElement"
/**
* @typedef {import("../element/NodeElement").default} NodeElement
@@ -40,11 +41,15 @@ export default class PinTemplate extends ITemplate {
"ueb-pin-" + sanitizeText(pin.getType())
)
pin.clickableElement = pin
- pin.nodeElement = pin.closest(NodeElement.tagName)
- if (!pin.nodeElement) {
- window.customElements.whenDefined(linkMessage.constructor.tagName).then(linkMessage)
- }
- pin.getLin
+ window.customElements.whenDefined(NodeElement.tagName).then(pin.nodeElement = pin.closest(NodeElement.tagName))
+ pin.getLinks().forEach(pinReference => {
+ const targetPin = pin.blueprint.getPin(pinReference.pinGuid)
+ if (linkedToPin) {
+ const [sourcePin, destinationPin] = pin.isOutput() ? [pin, targetPin] : [targetPin, pin]
+ pin.blueprint.addGraphElement(new LinkElement(sourcePin, destinationPin))
+ }
+ })
+
}
/**