diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js
index 9b7e787..18fb6bb 100755
--- a/dist/ueblueprint.js
+++ b/dist/ueblueprint.js
@@ -896,12 +896,19 @@ class IContext {
*/
class TypeInitialization {
- static sanitize(value) {
- if (!(value instanceof Object)) {
- return value // Is already primitive
+ static sanitize(value, targetType) {
+ if (targetType === undefined) {
+ targetType = value?.constructor;
+ }
+ let wrongType = false;
+ if (targetType && value?.constructor !== targetType && !(value instanceof targetType)) {
+ wrongType = true;
}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
- return value.valueOf()
+ value = value.valueOf(); // Get the relative primitive value
+ }
+ if (wrongType) {
+ return new targetType(value)
}
return value
}
@@ -1044,7 +1051,15 @@ class IEntity {
static attributes = {}
- constructor(options = {}) {
+ constructor(options) {
+ // @ts-expect-error
+ const attributes = this.constructor.attributes;
+ if (options.constructor !== Object && Object.getOwnPropertyNames(attributes).length == 1) {
+ // Where there is just one attribute, option can be the value of that attribute
+ options = {
+ [Object.getOwnPropertyNames(attributes)[0]]: options
+ };
+ }
/**
* @param {String[]} prefix
* @param {Object} target
@@ -1055,8 +1070,10 @@ class IEntity {
const last = fullKey.length - 1;
for (let property of Object.getOwnPropertyNames(properties)) {
fullKey[last] = property;
+ let defaultValue = properties[property];
+ const defaultType = (defaultValue instanceof Function) ? defaultValue : defaultValue?.constructor;
// Not instanceof because all objects are instenceof Object, exact match needed
- if (properties[property]?.constructor === Object) {
+ if (defaultType === Object) {
target[property] = {};
defineAllAttributes(fullKey, target[property], properties[property]);
continue
@@ -1070,10 +1087,9 @@ class IEntity {
*/
const value = Utility.objectGet(options, fullKey);
if (value !== undefined) {
- target[property] = value;
+ target[property] = TypeInitialization.sanitize(value, defaultType);
continue
}
- let defaultValue = properties[property];
if (defaultValue instanceof TypeInitialization) {
if (!defaultValue.showDefault) {
target[property] = undefined; // to preserve the order
@@ -1086,13 +1102,12 @@ class IEntity {
continue
}
if (defaultValue instanceof Function) {
- defaultValue = TypeInitialization.sanitize(new defaultValue());
+ defaultValue = TypeInitialization.sanitize(new defaultValue(), defaultType);
}
- target[property] = TypeInitialization.sanitize(defaultValue);
+ target[property] = TypeInitialization.sanitize(defaultValue, defaultType);
}
};
- // @ts-expect-error
- defineAllAttributes([], this, this.constructor.attributes);
+ defineAllAttributes([], this, attributes);
}
empty() {
@@ -1380,11 +1395,11 @@ class PinEntity extends IEntity {
}
isInput() {
- return !this.bHidden && this.Direction !== "EGPD_Output"
+ return !this.bHidden && this.Direction != "EGPD_Output"
}
isOutput() {
- return !this.bHidden && this.Direction === "EGPD_Output"
+ return !this.bHidden && this.Direction == "EGPD_Output"
}
/**
@@ -1678,7 +1693,7 @@ class Grammar {
Identifier = r => P.regex(/\w+/).map(v => new IdentifierEntity(v))
- PathSymbol = r => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v }))
+ PathSymbol = r => P.regex(/[0-9\w]+/).map(v => new PathSymbolEntity({ value: v }))
Reference = r => P.alt(
r.None,
@@ -2293,9 +2308,7 @@ class LinkTemplate extends ITemplate {
static c2Clamped = LinkTemplate.clampedLine([0, 100], [200, 30])
/**
- * Computes the html content of the target element.
- * @param {LinkElement} link connecting two graph nodes
- * @returns The result html
+ * @param {LinkElement} link
*/
render(link) {
const uniqueId = crypto.randomUUID();
@@ -2310,8 +2323,7 @@ class LinkTemplate extends ITemplate {
}
/**
- * Applies the style to the element.
- * @param {LinkElement} link Element of the graph
+ * @param {LinkElement} link
*/
apply(link) {
super.apply(link);
@@ -2324,10 +2336,26 @@ class LinkTemplate extends ITemplate {
if (referencePin) {
link.style.setProperty("--ueb-pin-color", referencePin.getColor());
}
+ this.applyPins(link);
+ if (link.sourcePin && link.destinationPin) {
+ this.applyFullLocation(link);
+ }
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
+ */
+ applyPins(link) {
+ if (link.sourcePin) {
+ link.dataset.source = link.sourcePin.GetPinId().toString();
+ }
+ if (link.destinationPin) {
+ link.dataset.destination = link.destinationPin.GetPinId().toString();
+ }
+ }
+
+ /**
+ * @param {LinkElement} link
*/
applyStartDragging(link) {
link.blueprint.dataset.creatingLink = "true";
@@ -2335,7 +2363,7 @@ class LinkTemplate extends ITemplate {
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
*/
applyFinishDragging(link) {
link.blueprint.dataset.creatingLink = "false";
@@ -2343,8 +2371,7 @@ class LinkTemplate extends ITemplate {
}
/**
- * Applies the style relative to the source pin location.
- * @param {LinkElement} link element
+ * @param {LinkElement} link
*/
applySourceLocation(link) {
link.style.setProperty("--ueb-from-input", link.originatesFromInput ? "1" : "0");
@@ -2353,8 +2380,7 @@ class LinkTemplate extends ITemplate {
}
/**
- * Applies the style relative to the destination pin location.
- * @param {LinkElement} link Link element
+ * @param {LinkElement} link
*/
applyFullLocation(link) {
const dx = Math.max(Math.abs(link.sourceLocation[0] - link.destinationLocation[0]), 1);
@@ -2392,7 +2418,7 @@ class LinkTemplate extends ITemplate {
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
* @param {LinkMessageElement} linkMessage
*/
applyLinkMessage(link, linkMessage) {
@@ -2423,6 +2449,9 @@ class LinkElement extends IElement {
return this.#source
}
set sourcePin(pin) {
+ if (this.#source == pin) {
+ return
+ }
if (this.#source) {
const nodeElement = this.#source.getNodeElement();
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
@@ -2442,6 +2471,7 @@ class LinkElement extends IElement {
this.#linkPins();
}
}
+ this.template.applyPins(this);
}
/** @type {PinElement} */
@@ -2450,6 +2480,9 @@ class LinkElement extends IElement {
return this.#destination
}
set destinationPin(pin) {
+ if (this.#destination == pin) {
+ return
+ }
if (this.#destination) {
const nodeElement = this.#destination.getNodeElement();
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler);
@@ -2468,6 +2501,7 @@ class LinkElement extends IElement {
this.#linkPins();
}
}
+ this.template.applyPins(this);
}
#nodeDeleteHandler
@@ -2549,9 +2583,6 @@ class LinkElement extends IElement {
this.template.applySourceLocation(this);
}
- /**
- * @returns {Number[]}
- */
getDestinationLocation() {
return this.destinationLocation
}
@@ -2934,6 +2965,23 @@ class ISelectableDraggableElement extends IElement {
};
}
+ #setSelected(value = true) {
+ this.selected = value;
+ if (this.blueprint) {
+ if (this.selected) {
+ this.blueprint.addEventListener(Configuration.nodeDragEventName, this.dragHandler);
+ } else {
+ this.blueprint.removeEventListener(Configuration.nodeDragEventName, this.dragHandler);
+ }
+ }
+ this.template.applySelected(this);
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.#setSelected(this.selected);
+ }
+
createInputObjects() {
return [
new MouseMoveNodes(this, this.blueprint, {
@@ -2966,16 +3014,9 @@ class ISelectableDraggableElement extends IElement {
}
setSelected(value = true) {
- if (this.selected == value) {
- return
+ if (this.selected != value) {
+ this.#setSelected(value);
}
- 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) {
@@ -2990,7 +3031,7 @@ class ISelectableDraggableElement extends IElement {
}
snapToGrid() {
- let snappedLocation = this.blueprint.snapToGrid(this.location);
+ let snappedLocation = Utility.snapToGrid(this.location, Configuration.gridSize);
if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) {
this.setLocation(snappedLocation);
}
@@ -2999,6 +3040,114 @@ class ISelectableDraggableElement extends IElement {
// @ts-check
+/**
+ * @typedef {import("../element/NodeElement").default} NodeElement
+ * @typedef {import("../element/PinElement").default} PinElement
+ */
+class PinTemplate extends ITemplate {
+
+ hasInput() {
+ return false
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ render(pin) {
+ if (pin.isInput()) {
+ return html`
+
+ ${this.renderIcon(pin)}
+
+
+ ${sanitizeText(pin.getPinDisplayName())}
+ ${this.renderInput(pin)}
+
+ `
+ } else {
+ return html`
+ ${sanitizeText(pin.getPinDisplayName())}
+
+ ${this.renderIcon(pin)}
+
+ `
+ }
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ renderIcon(pin) {
+ return ''
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ renderInput(pin) {
+ return ""
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ apply(pin) {
+ super.apply(pin);
+ pin.classList.add(
+ "ueb-node-" + (pin.isInput() ? "input" : pin.isOutput() ? "output" : "hidden"),
+ "ueb-pin-" + sanitizeText(pin.getType())
+ );
+ pin.dataset.id = pin.GetPinIdValue();
+ if (pin.entity.bAdvancedView) {
+ pin.dataset.advancedView = "true";
+ }
+ pin.clickableElement = pin;
+ pin.nodeElement = pin.closest("ueb-node");
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ applyConnected(pin) {
+ if (pin.isLinked()) {
+ pin.classList.add("ueb-pin-fill");
+ } else {
+ pin.classList.remove("ueb-pin-fill");
+ }
+ }
+
+ /**
+ * @param {PinElement} pin
+ */
+ getLinkLocation(pin) {
+ const rect = pin.querySelector(".ueb-pin-icon").getBoundingClientRect();
+ return pin.blueprint.compensateTranslation(Utility.convertLocation(
+ [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2],
+ pin.blueprint.gridElement))
+ }
+}
+
+// @ts-check
+
+/**
+ * @typedef {import("../element/PinElement").default} PinElement
+ */
+class ExecPinTemplate extends PinTemplate {
+
+ /**
+ * @param {PinElement} pin
+ */
+ renderIcon(pin) {
+ return html`
+
+ `
+ }
+}
+
+// @ts-check
+
/**
* @typedef {import("../element/LinkMessageElement").default} LinkMessageElement
*/
@@ -3208,123 +3357,6 @@ class MouseCreateLink extends IMouseClickDrag {
// @ts-check
-/**
- * @typedef {import("../element/NodeElement").default} NodeElement
- * @typedef {import("../element/PinElement").default} PinElement
- */
-class PinTemplate extends ITemplate {
-
- hasInput() {
- return false
- }
-
- /**
- * @param {PinElement} pin
- */
- render(pin) {
- if (pin.isInput()) {
- return html`
-
- ${this.renderIcon(pin)}
-
-
- ${sanitizeText(pin.getPinDisplayName())}
- ${this.renderInput(pin)}
-
- `
- } else {
- return html`
- ${sanitizeText(pin.getPinDisplayName())}
-
- ${this.renderIcon(pin)}
-
- `
- }
- }
-
- /**
- * @param {PinElement} pin
- */
- renderIcon(pin) {
- return ''
- }
-
- /**
- * @param {PinElement} pin
- */
- renderInput(pin) {
- return ""
- }
-
- /**
- * @param {PinElement} pin
- */
- apply(pin) {
- super.apply(pin);
- pin.classList.add(
- "ueb-node-" + (pin.isInput() ? "input" : pin.isOutput() ? "output" : "hidden"),
- "ueb-pin-" + sanitizeText(pin.getType())
- );
- pin.dataset.id = pin.GetPinIdValue();
- if (pin.entity.bAdvancedView) {
- pin.dataset.advancedView = "true";
- }
- pin.clickableElement = pin;
- pin.nodeElement = pin.closest("ueb-node");
- pin.getLinks().forEach(pinReference => {
- const targetPin = pin.blueprint.getPin(pinReference);
- if (targetPin) {
- const [sourcePin, destinationPin] = pin.isOutput() ? [pin, targetPin] : [targetPin, pin];
- pin.blueprint.addGraphElement(
- new LinkElement(/** @type {PinElement} */(sourcePin), /** @type {PinElement} */(destinationPin))
- );
- }
- });
- }
-
- /**
- * @param {PinElement} pin
- */
- applyConnected(pin) {
- if (pin.isLinked()) {
- pin.classList.add("ueb-pin-fill");
- } else {
- pin.classList.remove("ueb-pin-fill");
- }
- }
-
- /**
- * @param {PinElement} pin
- */
- getLinkLocation(pin) {
- const rect = pin.querySelector(".ueb-pin-icon").getBoundingClientRect();
- return pin.blueprint.compensateTranslation(Utility.convertLocation(
- [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2],
- pin.blueprint.gridElement))
- }
-}
-
-// @ts-check
-
-/**
- * @typedef {import("../element/PinElement").default} PinElement
- */
-class ExecPinTemplate extends PinTemplate {
-
- /**
- * @param {PinElement} pin
- */
- renderIcon(pin) {
- return html`
-
- `
- }
-}
-
-// @ts-check
-
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
@@ -3467,10 +3499,17 @@ class PinElement extends IElement {
return this.entity.LinkedTo ?? []
}
- cleanLinks() {
- this.entity.LinkedTo = this.getLinks().filter(
- pinReference => this.blueprint.getPin(pinReference)
- );
+ sanitizeLinks() {
+ this.entity.LinkedTo = this.getLinks().filter(pinReference => {
+ let pin = this.blueprint.getPin(pinReference);
+ if (pin) {
+ let link = this.blueprint.getLink(this, pin, true);
+ if (!link) {
+ this.blueprint.addGraphElement(new LinkElement(this, pin));
+ }
+ }
+ return pin
+ });
}
/**
@@ -3656,8 +3695,8 @@ class NodeElement extends ISelectableDraggableElement {
return this.entity.getFullName()
}
- cleanLinks() {
- this.getPinElements().forEach(pin => pin.cleanLinks());
+ sanitizeLinks() {
+ this.getPinElements().forEach(pin => pin.sanitizeLinks());
}
/**
@@ -3750,7 +3789,6 @@ class Paste extends IContext {
this.blueprint.unselectAll();
}
let mousePosition = this.blueprint.mousePosition;
- this.blueprint.addGraphElement(...nodes);
nodes.forEach(node => {
const locationOffset = [
mousePosition[0] - left,
@@ -3760,6 +3798,7 @@ class Paste extends IContext {
node.setSelected(true);
node.snapToGrid();
});
+ this.blueprint.addGraphElement(...nodes);
return true
}
}
@@ -3922,7 +3961,6 @@ class Blueprint extends IElement {
* @param {Number} y
*/
#expand(x, y) {
- // TODO remove
x = Math.round(x);
y = Math.round(y);
this.additional[0] += x;
@@ -4195,6 +4233,18 @@ class Blueprint extends IElement {
return this.links
}
+ /**
+ * @param {PinElement} sourcePin
+ * @param {PinElement} destinationPin
+ * @returns
+ */
+ getLink(sourcePin, destinationPin, ignoreDirection = false) {
+ return this.links.find(link =>
+ link.sourcePin == sourcePin && link.destinationPin == destinationPin
+ || ignoreDirection && link.sourcePin == destinationPin && link.destinationPin == sourcePin
+ )
+ }
+
/**
* Select all nodes
*/
@@ -4213,7 +4263,6 @@ class Blueprint extends IElement {
* @param {...IElement} graphElements
*/
addGraphElement(...graphElements) {
- let nodeElements = [];
for (let element of graphElements) {
if (element instanceof NodeElement && !this.nodes.includes(element)) {
const nodeName = element.entity.getFullName();
@@ -4230,16 +4279,15 @@ class Blueprint extends IElement {
homonymNode.rename(Configuration.nodeName(name, this.#nodeNameCounter[name]));
}
this.nodes.push(element);
- nodeElements.push(element);
this.nodesContainerElement?.appendChild(element);
} else if (element instanceof LinkElement && !this.links.includes(element)) {
this.links.push(element);
+ this.nodesContainerElement?.appendChild(element);
}
}
- // Keep separated for linking purpose
- if (this.nodesContainerElement) {
- nodeElements.forEach(node => node.cleanLinks());
- }
+ graphElements.filter(element => element instanceof NodeElement).forEach(
+ node => /** @type {NodeElement} */(node).sanitizeLinks()
+ );
}
/**
@@ -4255,7 +4303,7 @@ class Blueprint extends IElement {
? this.links
: null;
elementsArray?.splice(
- elementsArray.findIndex(v => v == element),
+ elementsArray.findIndex(v => v === element),
1
);
}
diff --git a/js/Blueprint.js b/js/Blueprint.js
index 039ba98..15f2427 100755
--- a/js/Blueprint.js
+++ b/js/Blueprint.js
@@ -107,7 +107,6 @@ export default class Blueprint extends IElement {
* @param {Number} y
*/
#expand(x, y) {
- // TODO remove
x = Math.round(x)
y = Math.round(y)
this.additional[0] += x
@@ -380,6 +379,18 @@ export default class Blueprint extends IElement {
return this.links
}
+ /**
+ * @param {PinElement} sourcePin
+ * @param {PinElement} destinationPin
+ * @returns
+ */
+ getLink(sourcePin, destinationPin, ignoreDirection = false) {
+ return this.links.find(link =>
+ link.sourcePin == sourcePin && link.destinationPin == destinationPin
+ || ignoreDirection && link.sourcePin == destinationPin && link.destinationPin == sourcePin
+ )
+ }
+
/**
* Select all nodes
*/
@@ -419,12 +430,12 @@ export default class Blueprint extends IElement {
this.nodesContainerElement?.appendChild(element)
} else if (element instanceof LinkElement && !this.links.includes(element)) {
this.links.push(element)
+ this.nodesContainerElement?.appendChild(element)
}
}
- // Keep separated for linking purpose
- if (this.nodesContainerElement) {
- nodeElements.forEach(node => node.cleanLinks())
- }
+ graphElements.filter(element => element instanceof NodeElement).forEach(
+ node => /** @type {NodeElement} */(node).sanitizeLinks()
+ )
}
/**
diff --git a/js/element/ISelectableDraggableElement.js b/js/element/ISelectableDraggableElement.js
index 7bc6840..7fbcf32 100644
--- a/js/element/ISelectableDraggableElement.js
+++ b/js/element/ISelectableDraggableElement.js
@@ -3,6 +3,7 @@
import Configuration from "../Configuration"
import IElement from "./IElement"
import MouseMoveNodes from "../input/mouse/MouseMoveNodes"
+import Utility from "../Utility"
/**
* @typedef {import("../template/SelectableDraggableTemplate").default} SelectableDraggableTemplate
@@ -29,6 +30,23 @@ export default class ISelectableDraggableElement extends IElement {
}
}
+ #setSelected(value = true) {
+ this.selected = value
+ if (this.blueprint) {
+ if (this.selected) {
+ this.blueprint.addEventListener(Configuration.nodeDragEventName, this.dragHandler)
+ } else {
+ this.blueprint.removeEventListener(Configuration.nodeDragEventName, this.dragHandler)
+ }
+ }
+ this.template.applySelected(this)
+ }
+
+ connectedCallback() {
+ super.connectedCallback()
+ this.#setSelected(this.selected)
+ }
+
createInputObjects() {
return [
new MouseMoveNodes(this, this.blueprint, {
@@ -61,16 +79,9 @@ export default class ISelectableDraggableElement extends IElement {
}
setSelected(value = true) {
- if (this.selected == value) {
- return
+ if (this.selected != value) {
+ this.#setSelected(value)
}
- 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) {
@@ -85,7 +96,7 @@ export default class ISelectableDraggableElement extends IElement {
}
snapToGrid() {
- let snappedLocation = this.blueprint.snapToGrid(this.location)
+ let snappedLocation = Utility.snapToGrid(this.location, Configuration.gridSize)
if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) {
this.setLocation(snappedLocation)
}
diff --git a/js/element/LinkElement.js b/js/element/LinkElement.js
index 138e6e3..381b13e 100644
--- a/js/element/LinkElement.js
+++ b/js/element/LinkElement.js
@@ -23,6 +23,9 @@ export default class LinkElement extends IElement {
return this.#source
}
set sourcePin(pin) {
+ if (this.#source == pin) {
+ return
+ }
if (this.#source) {
const nodeElement = this.#source.getNodeElement()
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
@@ -42,6 +45,7 @@ export default class LinkElement extends IElement {
this.#linkPins()
}
}
+ this.template.applyPins(this)
}
/** @type {PinElement} */
@@ -50,6 +54,9 @@ export default class LinkElement extends IElement {
return this.#destination
}
set destinationPin(pin) {
+ if (this.#destination == pin) {
+ return
+ }
if (this.#destination) {
const nodeElement = this.#destination.getNodeElement()
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
@@ -68,6 +75,7 @@ export default class LinkElement extends IElement {
this.#linkPins()
}
}
+ this.template.applyPins(this)
}
#nodeDeleteHandler
@@ -149,9 +157,6 @@ export default class LinkElement extends IElement {
this.template.applySourceLocation(this)
}
- /**
- * @returns {Number[]}
- */
getDestinationLocation() {
return this.destinationLocation
}
diff --git a/js/element/NodeElement.js b/js/element/NodeElement.js
index a687d9f..69c44e0 100644
--- a/js/element/NodeElement.js
+++ b/js/element/NodeElement.js
@@ -43,8 +43,8 @@ export default class NodeElement extends ISelectableDraggableElement {
return this.entity.getFullName()
}
- cleanLinks() {
- this.getPinElements().forEach(pin => pin.cleanLinks())
+ sanitizeLinks() {
+ this.getPinElements().forEach(pin => pin.sanitizeLinks())
}
/**
diff --git a/js/element/PinElement.js b/js/element/PinElement.js
index 3a1e7af..566d516 100644
--- a/js/element/PinElement.js
+++ b/js/element/PinElement.js
@@ -1,9 +1,10 @@
// @ts-check
+import ExecPinTemplate from "../template/ExecPinTemplate"
import IElement from "./IElement"
+import LinkElement from "./LinkElement"
import MouseCreateLink from "../input/mouse/MouseCreateLink"
import PinTemplate from "../template/PinTemplate"
-import ExecPinTemplate from "../template/ExecPinTemplate"
import StringPinTemplate from "../template/StringPinTemplate"
/**
@@ -119,10 +120,17 @@ export default class PinElement extends IElement {
return this.entity.LinkedTo ?? []
}
- cleanLinks() {
- this.entity.LinkedTo = this.getLinks().filter(
- pinReference => this.blueprint.getPin(pinReference)
- )
+ sanitizeLinks() {
+ this.entity.LinkedTo = this.getLinks().filter(pinReference => {
+ let pin = this.blueprint.getPin(pinReference)
+ if (pin) {
+ let link = this.blueprint.getLink(this, pin, true)
+ if (!link) {
+ this.blueprint.addGraphElement(new LinkElement(this, pin))
+ }
+ }
+ return pin
+ })
}
/**
diff --git a/js/entity/IEntity.js b/js/entity/IEntity.js
index 7f23e0f..665aa80 100644
--- a/js/entity/IEntity.js
+++ b/js/entity/IEntity.js
@@ -7,7 +7,15 @@ export default class IEntity {
static attributes = {}
- constructor(options = {}) {
+ constructor(options) {
+ // @ts-expect-error
+ const attributes = this.constructor.attributes
+ if (options.constructor !== Object && Object.getOwnPropertyNames(attributes).length == 1) {
+ // Where there is just one attribute, option can be the value of that attribute
+ options = {
+ [Object.getOwnPropertyNames(attributes)[0]]: options
+ }
+ }
/**
* @param {String[]} prefix
* @param {Object} target
@@ -18,8 +26,10 @@ export default class IEntity {
const last = fullKey.length - 1
for (let property of Object.getOwnPropertyNames(properties)) {
fullKey[last] = property
+ let defaultValue = properties[property]
+ const defaultType = (defaultValue instanceof Function) ? defaultValue : defaultValue?.constructor
// Not instanceof because all objects are instenceof Object, exact match needed
- if (properties[property]?.constructor === Object) {
+ if (defaultType === Object) {
target[property] = {}
defineAllAttributes(fullKey, target[property], properties[property])
continue
@@ -33,10 +43,9 @@ export default class IEntity {
*/
const value = Utility.objectGet(options, fullKey)
if (value !== undefined) {
- target[property] = value
+ target[property] = TypeInitialization.sanitize(value, defaultType)
continue
}
- let defaultValue = properties[property]
if (defaultValue instanceof TypeInitialization) {
if (!defaultValue.showDefault) {
target[property] = undefined // to preserve the order
@@ -49,13 +58,12 @@ export default class IEntity {
continue
}
if (defaultValue instanceof Function) {
- defaultValue = TypeInitialization.sanitize(new defaultValue())
+ defaultValue = TypeInitialization.sanitize(new defaultValue(), defaultType)
}
- target[property] = TypeInitialization.sanitize(defaultValue)
+ target[property] = TypeInitialization.sanitize(defaultValue, defaultType)
}
}
- // @ts-expect-error
- defineAllAttributes([], this, this.constructor.attributes)
+ defineAllAttributes([], this, attributes)
}
empty() {
diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js
index d4ff586..99afe86 100755
--- a/js/entity/PinEntity.js
+++ b/js/entity/PinEntity.js
@@ -76,11 +76,11 @@ export default class PinEntity extends IEntity {
}
isInput() {
- return !this.bHidden && this.Direction !== "EGPD_Output"
+ return !this.bHidden && this.Direction != "EGPD_Output"
}
isOutput() {
- return !this.bHidden && this.Direction === "EGPD_Output"
+ return !this.bHidden && this.Direction == "EGPD_Output"
}
/**
diff --git a/js/entity/TypeInitialization.js b/js/entity/TypeInitialization.js
index 22880da..48dae52 100755
--- a/js/entity/TypeInitialization.js
+++ b/js/entity/TypeInitialization.js
@@ -5,12 +5,19 @@
*/
export default class TypeInitialization {
- static sanitize(value) {
- if (!(value instanceof Object)) {
- return value // Is already primitive
+ static sanitize(value, targetType) {
+ if (targetType === undefined) {
+ targetType = value?.constructor
+ }
+ let wrongType = false
+ if (targetType && value?.constructor !== targetType && !(value instanceof targetType)) {
+ wrongType = true
}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
- return value.valueOf()
+ value = value.valueOf() // Get the relative primitive value
+ }
+ if (wrongType) {
+ return new targetType(value)
}
return value
}
diff --git a/js/input/common/Paste.js b/js/input/common/Paste.js
index cb686dd..b05f56c 100755
--- a/js/input/common/Paste.js
+++ b/js/input/common/Paste.js
@@ -42,7 +42,6 @@ export default class Paste extends IContext {
this.blueprint.unselectAll()
}
let mousePosition = this.blueprint.mousePosition
- this.blueprint.addGraphElement(...nodes)
nodes.forEach(node => {
const locationOffset = [
mousePosition[0] - left,
@@ -52,6 +51,7 @@ export default class Paste extends IContext {
node.setSelected(true)
node.snapToGrid()
})
+ this.blueprint.addGraphElement(...nodes)
return true
}
}
diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js
index 72e29be..71953f0 100755
--- a/js/serialization/Grammar.js
+++ b/js/serialization/Grammar.js
@@ -149,7 +149,7 @@ export default class Grammar {
Identifier = r => P.regex(/\w+/).map(v => new IdentifierEntity(v))
- PathSymbol = r => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v }))
+ PathSymbol = r => P.regex(/[0-9\w]+/).map(v => new PathSymbolEntity({ value: v }))
Reference = r => P.alt(
r.None,
diff --git a/js/template/LinkTemplate.js b/js/template/LinkTemplate.js
index 44bdc88..6e5a09d 100755
--- a/js/template/LinkTemplate.js
+++ b/js/template/LinkTemplate.js
@@ -59,9 +59,7 @@ export default class LinkTemplate extends ITemplate {
static c2Clamped = LinkTemplate.clampedLine([0, 100], [200, 30])
/**
- * Computes the html content of the target element.
- * @param {LinkElement} link connecting two graph nodes
- * @returns The result html
+ * @param {LinkElement} link
*/
render(link) {
const uniqueId = crypto.randomUUID()
@@ -76,8 +74,7 @@ export default class LinkTemplate extends ITemplate {
}
/**
- * Applies the style to the element.
- * @param {LinkElement} link Element of the graph
+ * @param {LinkElement} link
*/
apply(link) {
super.apply(link)
@@ -90,10 +87,26 @@ export default class LinkTemplate extends ITemplate {
if (referencePin) {
link.style.setProperty("--ueb-pin-color", referencePin.getColor())
}
+ this.applyPins(link)
+ if (link.sourcePin && link.destinationPin) {
+ this.applyFullLocation(link)
+ }
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
+ */
+ applyPins(link) {
+ if (link.sourcePin) {
+ link.dataset.source = link.sourcePin.GetPinId().toString()
+ }
+ if (link.destinationPin) {
+ link.dataset.destination = link.destinationPin.GetPinId().toString()
+ }
+ }
+
+ /**
+ * @param {LinkElement} link
*/
applyStartDragging(link) {
link.blueprint.dataset.creatingLink = "true"
@@ -101,7 +114,7 @@ export default class LinkTemplate extends ITemplate {
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
*/
applyFinishDragging(link) {
link.blueprint.dataset.creatingLink = "false"
@@ -109,8 +122,7 @@ export default class LinkTemplate extends ITemplate {
}
/**
- * Applies the style relative to the source pin location.
- * @param {LinkElement} link element
+ * @param {LinkElement} link
*/
applySourceLocation(link) {
link.style.setProperty("--ueb-from-input", link.originatesFromInput ? "1" : "0")
@@ -119,8 +131,7 @@ export default class LinkTemplate extends ITemplate {
}
/**
- * Applies the style relative to the destination pin location.
- * @param {LinkElement} link Link element
+ * @param {LinkElement} link
*/
applyFullLocation(link) {
const dx = Math.max(Math.abs(link.sourceLocation[0] - link.destinationLocation[0]), 1)
@@ -159,7 +170,7 @@ export default class LinkTemplate extends ITemplate {
}
/**
- * @param {LinkElement} link element
+ * @param {LinkElement} link
* @param {LinkMessageElement} linkMessage
*/
applyLinkMessage(link, linkMessage) {
diff --git a/js/template/PinTemplate.js b/js/template/PinTemplate.js
index 0f3f844..1c5bdd4 100755
--- a/js/template/PinTemplate.js
+++ b/js/template/PinTemplate.js
@@ -2,7 +2,6 @@
import html from "./html"
import ITemplate from "./ITemplate"
-import LinkElement from "../element/LinkElement"
import sanitizeText from "./sanitizeText"
import Utility from "../Utility"
@@ -69,15 +68,6 @@ export default class PinTemplate extends ITemplate {
}
pin.clickableElement = pin
pin.nodeElement = pin.closest("ueb-node")
- pin.getLinks().forEach(pinReference => {
- const targetPin = pin.blueprint.getPin(pinReference)
- if (targetPin) {
- const [sourcePin, destinationPin] = pin.isOutput() ? [pin, targetPin] : [targetPin, pin]
- pin.blueprint.addGraphElement(
- new LinkElement(/** @type {PinElement} */(sourcePin), /** @type {PinElement} */(destinationPin))
- )
- }
- })
}
/**