diff --git a/css/ueblueprint-style.css b/css/ueblueprint-style.css
index 9ca2294..7d64267 100644
--- a/css/ueblueprint-style.css
+++ b/css/ueblueprint-style.css
@@ -252,7 +252,7 @@ u-blueprint {
}
.ueb-node-inputs {
- margin-right: 2em;
+ margin-right: auto;
}
.ueb-node-value-icon {
diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js
index 25d00a3..176ca24 100644
--- a/dist/ueblueprint.js
+++ b/dist/ueblueprint.js
@@ -323,6 +323,14 @@ class PinEntity extends Entity {
return PinEntity.attributes
}
+ /**
+ *
+ * @returns {String}
+ */
+ getPinDisplayName() {
+ return this.PinName
+ }
+
isOutput() {
if (this.Direction === "EGPD_Output") {
return true
@@ -361,6 +369,14 @@ class ObjectEntity extends Entity {
getAttributes() {
return ObjectEntity.attributes
}
+
+ /**
+ *
+ * @returns {String} The name of the node
+ */
+ getNodeDisplayName() {
+ return this.Name
+ }
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -681,6 +697,20 @@ class ObjectSerializer extends Serializer {
return parseResult.value
}
+ /**
+ *
+ * @param {String} value
+ * @returns {ObjectEntity[]}
+ */
+ readMultiple(value) {
+ const parseResult = Serializer.grammar.MultipleObject.parse(value);
+ if (!parseResult.status) {
+ console.error("Error when trying to parse the object.");
+ return parseResult
+ }
+ return parseResult.value
+ }
+
/**
*
* @param {ObjectEntity} object
@@ -708,7 +738,7 @@ class Template {
* @returns The computed html
*/
render(entity) {
- return ``
+ return ""
}
/**
@@ -804,7 +834,7 @@ class MouseClickDrag extends Pointing {
const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace;
let self = this;
- this.mouseDownHandler = function (e) {
+ this.mouseDownHandler = e => {
switch (e.button) {
case self.clickButton:
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
@@ -826,7 +856,7 @@ class MouseClickDrag extends Pointing {
}
};
- this.mouseStartedMovingHandler = function (e) {
+ this.mouseStartedMovingHandler = e => {
e.preventDefault();
e.stopPropagation();
@@ -839,7 +869,7 @@ class MouseClickDrag extends Pointing {
self.started = true;
};
- this.mouseMoveHandler = function (e) {
+ this.mouseMoveHandler = e => {
e.preventDefault();
e.stopPropagation();
const location = self.getLocation(e);
@@ -847,7 +877,7 @@ class MouseClickDrag extends Pointing {
self.dragTo(location, movement);
};
- this.mouseUpHandler = function (e) {
+ this.mouseUpHandler = e => {
if (!self.exitAnyButton || e.button == self.clickButton) {
// Remove the handlers of "mousemove" and "mouseup"
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler);
@@ -1322,7 +1352,7 @@ class MouseWheel extends Pointing {
this.looseTarget = options?.looseTarget ?? true;
let self = this;
- this.mouseWheelHandler = function (e) {
+ this.mouseWheelHandler = e => {
e.preventDefault();
const location = self.getLocation(e);
self.wheel(Math.sign(e.deltaY), location);
@@ -1361,6 +1391,239 @@ class BlueprintData {
}
}
+/**
+ * @typedef {import("../entity/ObjectEntity").default} ObjectEntity
+ */
+class NodeTemplate extends Template {
+
+ /**
+ * Computes the html content of the target element.
+ * @param {ObjectEntity} entity Entity representing the element
+ * @returns The computed html
+ */
+ header(entity) {
+ return `
+
+ `
+ }
+
+ /**
+ * Computes the html content of the target element.
+ * @param {ObjectEntity} entity Entity representing the element
+ * @returns The computed html
+ */
+ body(entity) {
+ /** @type {PinEntity[]} */
+ let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity);
+ let outputs = inputs.filter(v => v.isOutput());
+ inputs = inputs.filter(v => !v.isOutput());
+ return `
+
+
+
+ ${outputs.map((output, index) => `
+
+ ${output.getPinDisplayName()}
+
+
+ `).join("") ?? ''}
+
+
+ `
+ }
+
+ /**
+ * Computes the html content of the target element.
+ * @param {ObjectEntity} entity Entity representing the element
+ * @returns The computed html
+ */
+ render(entity) {
+ return `
+
+
+ ${this.header(entity)}
+ ${this.body(entity)}
+
+
+ `
+ }
+}
+
+class Drag extends MouseClickDrag {
+ constructor(target, blueprint, options) {
+ super(target, blueprint, options);
+ this.stepSize = parseInt(options?.stepSize);
+ this.mousePosition = [0, 0];
+ }
+
+ snapToGrid(location) {
+ return [
+ this.stepSize * Math.round(location[0] / this.stepSize),
+ this.stepSize * Math.round(location[1] / this.stepSize)
+ ]
+ }
+
+ startDrag() {
+ if (isNaN(this.stepSize) || this.stepSize <= 0) {
+ this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'));
+ if (isNaN(this.stepSize) || this.stepSize <= 0) {
+ this.stepSize = 1;
+ }
+ }
+ // Get the current mouse position
+ this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition;
+ }
+
+ dragTo(location, movement) {
+ const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location;
+ const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]];
+
+ if (d[0] == 0 && d[1] == 0) {
+ return
+ }
+
+ this.target.dragDispatch(d);
+
+ // Reassign the position of mouse
+ this.mousePosition = mousePosition;
+ }
+}
+
+class SelectableDraggable extends GraphElement {
+
+ constructor(...args) {
+ super(...args);
+ this.dragObject = null;
+ this.location = [0, 0];
+ this.selected = false;
+
+ let self = this;
+ this.dragHandler = (e) => {
+ self.addLocation(e.detail.value);
+ };
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.dragObject = new Drag(this, null, { // UDrag doesn't need blueprint
+ looseTarget: true
+ });
+ }
+
+ disconnectedCallback() {
+ this.dragObject.unlistenDOMElement();
+ }
+
+ setLocation(value = [0, 0]) {
+ this.location = value;
+ this.style.setProperty('--ueb-position-x', this.location[0]);
+ this.style.setProperty('--ueb-position-y', this.location[1]);
+ }
+
+ addLocation(value) {
+ this.setLocation([this.location[0] + value[0], this.location[1] + value[1]]);
+ }
+
+ dragDispatch(value) {
+ if (!this.selected) {
+ this.blueprint.unselectAll();
+ this.setSelected(true);
+ }
+ let dragEvent = new CustomEvent('uDragSelected', {
+ detail: {
+ instigator: this,
+ value: value
+ },
+ bubbles: false,
+ cancelable: true,
+ composed: false,
+ });
+ this.blueprint.dispatchEvent(dragEvent);
+ }
+
+ setSelected(value = true) {
+ if (this.selected == value) {
+ return
+ }
+ this.selected = value;
+ if (this.selected) {
+ this.classList.add('ueb-selected');
+ this.blueprint.addEventListener('uDragSelected', this.dragHandler);
+ } else {
+ this.classList.remove('ueb-selected');
+ this.blueprint.removeEventListener('uDragSelected', this.dragHandler);
+ }
+ }
+
+}
+
+class GraphNode extends SelectableDraggable {
+
+ static fromSerializedObject(str) {
+ let entity = SerializerFactory.getSerializer(ObjectEntity).read(str);
+ return new GraphNode(entity)
+ }
+
+ constructor(entity) {
+ super(entity, new NodeTemplate());
+ this.graphNodeName = 'n/a';
+ this.inputs = [];
+ this.outputs = [];
+ }
+
+ connectedCallback() {
+ this.getAttribute('type')?.trim();
+ super.connectedCallback();
+ this.classList.add('ueb-node');
+ if (this.selected) {
+ this.classList.add('ueb-selected');
+ }
+ this.style.setProperty('--ueb-position-x', this.location[0]);
+ this.style.setProperty('--ueb-position-y', this.location[1]);
+ }
+}
+
+customElements.define('u-node', GraphNode);
+
+class Paste {
+
+ constructor(target, blueprint, options) {
+ /** @type {HTMLElement} */
+ this.target = target;
+ /** @type {import("../Blueprint").default}" */
+ this.blueprint = blueprint;
+ this.serializer = new ObjectSerializer();
+
+ let self = this;
+ this.pasteHandler = e => {
+ self.pasted(e.clipboardData.getData("text"));
+ };
+ this.target.addEventListener("paste", this.pasteHandler);
+ }
+
+ pasted(value) {
+ let nodes = this.serializer.readMultiple(value).map(entity => new GraphNode(entity));
+ this.blueprint.addNode(...nodes);
+ }
+
+ unlistenDOMElement() {
+ this.target.removeEventListener("paste", this.pasteHandler);
+ }
+
+}
+
/** @typedef {import("./graph/GraphNode").default} GraphNode */
class Blueprint extends GraphElement {
@@ -1438,6 +1701,8 @@ class Blueprint extends GraphElement {
moveEverywhere: true,
exitAnyButton: true
});
+
+ this.pasteObject = new Paste(this.getGridDOMElement(), this);
}
getGridDOMElement() {
@@ -1448,6 +1713,7 @@ class Blueprint extends GraphElement {
super.disconnectedCallback();
this.dragObject.unlistenDOMElement();
this.selectObject.unlistenDOMElement();
+ this.pasteObject.unlistenDOMElement();
}
getScroll() {
@@ -1666,208 +1932,6 @@ class Blueprint extends GraphElement {
customElements.define('u-blueprint', Blueprint);
-class NodeTemplate extends Template {
-
- /**
- * Computes the html content of the target element.
- * @param {HTMLElement} entity Entity representing the element
- * @returns The computed html
- */
- header(entity) {
- return `
-
- `
- }
-
- /**
- * Computes the html content of the target element.
- * @param {import("../entity/ObjectEntity").default} entity Entity representing the element
- * @returns The computed html
- */
- body(entity) {
- let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity);
- let outputs = inputs.filter(v => v.isOutput());
- inputs = inputs.filter(v => !v.isOutput());
- return `
-
-
-
- ${outputs.map((output, index) => `
-
- ${output.name}
-
-
- `).join("") ?? ''}
-
-
- `
- }
-
- /**
- * Computes the html content of the target element.
- * @param {HTMLElement} entity Entity representing the element
- * @returns The computed html
- */
- render(entity) {
- return `
-
-
- ${this.header(entity)}
- ${this.body(entity)}
-
-
- `
- }
-}
-
-class Drag extends MouseClickDrag {
- constructor(target, blueprint, options) {
- super(target, blueprint, options);
- this.stepSize = parseInt(options?.stepSize);
- this.mousePosition = [0, 0];
- }
-
- snapToGrid(location) {
- return [
- this.stepSize * Math.round(location[0] / this.stepSize),
- this.stepSize * Math.round(location[1] / this.stepSize)
- ]
- }
-
- startDrag() {
- if (isNaN(this.stepSize) || this.stepSize <= 0) {
- this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'));
- if (isNaN(this.stepSize) || this.stepSize <= 0) {
- this.stepSize = 1;
- }
- }
- // Get the current mouse position
- this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition;
- }
-
- dragTo(location, movement) {
- const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location;
- const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]];
-
- if (d[0] == 0 && d[1] == 0) {
- return
- }
-
- this.target.dragDispatch(d);
-
- // Reassign the position of mouse
- this.mousePosition = mousePosition;
- }
-}
-
-class SelectableDraggable extends GraphElement {
-
- constructor(...args) {
- super(...args);
- this.dragObject = null;
- this.location = [0, 0];
- this.selected = false;
-
- let self = this;
- this.dragHandler = (e) => {
- self.addLocation(e.detail.value);
- };
- }
-
- connectedCallback() {
- super.connectedCallback();
- this.dragObject = new Drag(this, null, { // UDrag doesn't need blueprint
- looseTarget: true
- });
- }
-
- disconnectedCallback() {
- this.dragObject.unlistenDOMElement();
- }
-
- setLocation(value = [0, 0]) {
- this.location = value;
- this.style.setProperty('--ueb-position-x', this.location[0]);
- this.style.setProperty('--ueb-position-y', this.location[1]);
- }
-
- addLocation(value) {
- this.setLocation([this.location[0] + value[0], this.location[1] + value[1]]);
- }
-
- dragDispatch(value) {
- if (!this.selected) {
- this.blueprint.unselectAll();
- this.setSelected(true);
- }
- let dragEvent = new CustomEvent('uDragSelected', {
- detail: {
- instigator: this,
- value: value
- },
- bubbles: false,
- cancelable: true,
- composed: false,
- });
- this.blueprint.dispatchEvent(dragEvent);
- }
-
- setSelected(value = true) {
- if (this.selected == value) {
- return
- }
- this.selected = value;
- if (this.selected) {
- this.classList.add('ueb-selected');
- this.blueprint.addEventListener('uDragSelected', this.dragHandler);
- } else {
- this.classList.remove('ueb-selected');
- this.blueprint.removeEventListener('uDragSelected', this.dragHandler);
- }
- }
-
-}
-
-class GraphNode extends SelectableDraggable {
-
- static fromSerializedObject(str) {
- let entity = SerializerFactory.getSerializer(ObjectEntity).read(str);
- return new GraphNode(entity)
- }
-
- constructor(entity) {
- super(entity, new NodeTemplate());
- this.graphNodeName = 'n/a';
- this.inputs = [];
- this.outputs = [];
- }
-
- connectedCallback() {
- this.getAttribute('type')?.trim();
- super.connectedCallback();
- this.classList.add('ueb-node');
- if (this.selected) {
- this.classList.add('ueb-selected');
- }
- this.style.setProperty('--ueb-position-x', this.location[0]);
- this.style.setProperty('--ueb-position-y', this.location[1]);
- }
-}
-
-customElements.define('u-node', GraphNode);
-
class GraphLink extends GraphElement {
/**
diff --git a/js/Blueprint.js b/js/Blueprint.js
index 2ded613..b72f5ad 100755
--- a/js/Blueprint.js
+++ b/js/Blueprint.js
@@ -6,6 +6,7 @@ import Select from "./input/Select"
import Utility from "./Utility"
import Zoom from "./input/Zoom"
import BlueprintData from "./BlueprintData"
+import Paste from "./input/Paste"
/** @typedef {import("./graph/GraphNode").default} GraphNode */
export default class Blueprint extends GraphElement {
@@ -84,6 +85,8 @@ export default class Blueprint extends GraphElement {
moveEverywhere: true,
exitAnyButton: true
})
+
+ this.pasteObject = new Paste(this.getGridDOMElement(), this)
}
getGridDOMElement() {
@@ -94,6 +97,7 @@ export default class Blueprint extends GraphElement {
super.disconnectedCallback()
this.dragObject.unlistenDOMElement()
this.selectObject.unlistenDOMElement()
+ this.pasteObject.unlistenDOMElement()
}
getScroll() {
diff --git a/js/entity/ObjectEntity.js b/js/entity/ObjectEntity.js
index d0205c1..ba0a17e 100755
--- a/js/entity/ObjectEntity.js
+++ b/js/entity/ObjectEntity.js
@@ -24,4 +24,12 @@ export default class ObjectEntity extends Entity {
getAttributes() {
return ObjectEntity.attributes
}
+
+ /**
+ *
+ * @returns {String} The name of the node
+ */
+ getNodeDisplayName() {
+ return this.Name
+ }
}
diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js
index c51e8d3..542d9fc 100755
--- a/js/entity/PinEntity.js
+++ b/js/entity/PinEntity.js
@@ -41,6 +41,14 @@ export default class PinEntity extends Entity {
return PinEntity.attributes
}
+ /**
+ *
+ * @returns {String}
+ */
+ getPinDisplayName() {
+ return this.PinName
+ }
+
isOutput() {
if (this.Direction === "EGPD_Output") {
return true
diff --git a/js/input/MouseClickDrag.js b/js/input/MouseClickDrag.js
index b70f4f8..d42ee12 100755
--- a/js/input/MouseClickDrag.js
+++ b/js/input/MouseClickDrag.js
@@ -15,7 +15,7 @@ export default class MouseClickDrag extends Pointing {
const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace
let self = this
- this.mouseDownHandler = function (e) {
+ this.mouseDownHandler = e => {
switch (e.button) {
case self.clickButton:
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
@@ -37,7 +37,7 @@ export default class MouseClickDrag extends Pointing {
}
}
- this.mouseStartedMovingHandler = function (e) {
+ this.mouseStartedMovingHandler = e => {
e.preventDefault()
e.stopPropagation()
@@ -50,7 +50,7 @@ export default class MouseClickDrag extends Pointing {
self.started = true
}
- this.mouseMoveHandler = function (e) {
+ this.mouseMoveHandler = e => {
e.preventDefault()
e.stopPropagation()
const location = self.getLocation(e)
@@ -58,7 +58,7 @@ export default class MouseClickDrag extends Pointing {
self.dragTo(location, movement)
}
- this.mouseUpHandler = function (e) {
+ this.mouseUpHandler = e => {
if (!self.exitAnyButton || e.button == self.clickButton) {
// Remove the handlers of "mousemove" and "mouseup"
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler)
diff --git a/js/input/MouseWheel.js b/js/input/MouseWheel.js
index 7097365..ea95d83 100755
--- a/js/input/MouseWheel.js
+++ b/js/input/MouseWheel.js
@@ -13,7 +13,7 @@ export default class MouseWheel extends Pointing {
this.looseTarget = options?.looseTarget ?? true
let self = this
- this.mouseWheelHandler = function (e) {
+ this.mouseWheelHandler = e => {
e.preventDefault()
const location = self.getLocation(e)
self.wheel(Math.sign(e.deltaY), location)
diff --git a/js/input/Paste.js b/js/input/Paste.js
new file mode 100644
index 0000000..c9db9cd
--- /dev/null
+++ b/js/input/Paste.js
@@ -0,0 +1,29 @@
+import GraphNode from "../graph/GraphNode"
+import ObjectSerializer from "../serialization/ObjectSerializer"
+
+export default class Paste {
+
+ constructor(target, blueprint, options) {
+ /** @type {HTMLElement} */
+ this.target = target
+ /** @type {import("../Blueprint").default}" */
+ this.blueprint = blueprint
+ this.serializer = new ObjectSerializer()
+
+ let self = this
+ this.pasteHandler = e => {
+ self.pasted(e.clipboardData.getData("text"))
+ }
+ this.target.addEventListener("paste", this.pasteHandler)
+ }
+
+ pasted(value) {
+ let nodes = this.serializer.readMultiple(value).map(entity => new GraphNode(entity))
+ this.blueprint.addNode(...nodes)
+ }
+
+ unlistenDOMElement() {
+ this.target.removeEventListener("paste", this.pasteHandler)
+ }
+
+}
\ No newline at end of file
diff --git a/js/serialization/ObjectSerializer.js b/js/serialization/ObjectSerializer.js
index 2b3e082..231f84e 100755
--- a/js/serialization/ObjectSerializer.js
+++ b/js/serialization/ObjectSerializer.js
@@ -29,6 +29,20 @@ export default class ObjectSerializer extends Serializer {
return parseResult.value
}
+ /**
+ *
+ * @param {String} value
+ * @returns {ObjectEntity[]}
+ */
+ readMultiple(value) {
+ const parseResult = Serializer.grammar.MultipleObject.parse(value)
+ if (!parseResult.status) {
+ console.error("Error when trying to parse the object.")
+ return parseResult
+ }
+ return parseResult.value
+ }
+
/**
*
* @param {ObjectEntity} object
diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js
index a04b727..a9af786 100755
--- a/js/template/NodeTemplate.js
+++ b/js/template/NodeTemplate.js
@@ -1,11 +1,14 @@
import PinEntity from "../entity/PinEntity"
import Template from "./Template"
+/**
+ * @typedef {import("../entity/ObjectEntity").default} ObjectEntity
+ */
export default class NodeTemplate extends Template {
/**
* Computes the html content of the target element.
- * @param {HTMLElement} entity Entity representing the element
+ * @param {ObjectEntity} entity Entity representing the element
* @returns The computed html
*/
header(entity) {
@@ -13,7 +16,7 @@ export default class NodeTemplate extends Template {
`
@@ -21,10 +24,11 @@ export default class NodeTemplate extends Template {
/**
* Computes the html content of the target element.
- * @param {import("../entity/ObjectEntity").default} entity Entity representing the element
+ * @param {ObjectEntity} entity Entity representing the element
* @returns The computed html
*/
body(entity) {
+ /** @type {PinEntity[]} */
let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity)
let outputs = inputs.filter(v => v.isOutput())
inputs = inputs.filter(v => !v.isOutput())
@@ -34,14 +38,14 @@ export default class NodeTemplate extends Template {
${inputs.map((input, index) => `
- ${input.name}
+ ${input.getPinDisplayName()}
`).join("") ?? ""}
${outputs.map((output, index) => `
- ${output.name}
+ ${output.getPinDisplayName()}
`).join("") ?? ''}
@@ -52,7 +56,7 @@ export default class NodeTemplate extends Template {
/**
* Computes the html content of the target element.
- * @param {HTMLElement} entity Entity representing the element
+ * @param {ObjectEntity} entity Entity representing the element
* @returns The computed html
*/
render(entity) {
diff --git a/js/template/Template.js b/js/template/Template.js
index d0f89cc..ab6b88b 100755
--- a/js/template/Template.js
+++ b/js/template/Template.js
@@ -9,7 +9,7 @@ export default class Template {
* @returns The computed html
*/
render(entity) {
- return ``
+ return ""
}
/**