diff --git a/css/ueblueprint-style.css b/css/ueblueprint-style.css index 0747bc6..3481219 100644 --- a/css/ueblueprint-style.css +++ b/css/ueblueprint-style.css @@ -285,8 +285,8 @@ u-blueprint[data-focused="true"] .ueb-viewport-body { margin-right: auto; } -.ueb-node-input, -.ueb-node-output { +u-pin { + display: block; padding: 4px 8px; cursor: pointer; } diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 095fd64..de9b08a 100644 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -974,7 +974,7 @@ class PinReferenceEntity extends Entity { } } -class PinEntity extends Entity { +class PinEntity$1 extends Entity { static attributes = { PinId: GuidEntity, @@ -1007,23 +1007,7 @@ class PinEntity extends Entity { } getAttributes() { - return PinEntity.attributes - } - - /** - * - * @returns {String} - */ - getPinDisplayName() { - return this.PinName - } - - isConnected() { - return this.LinkedTo.length > 0 - } - - getType() { - return this.PinType.PinCategory ?? "object" + return PinEntity$1.attributes } isInput() { @@ -1037,6 +1021,14 @@ class PinEntity extends Entity { return true } } + + isConnected() { + return this.LinkedTo.length > 0 + } + + getType() { + return this.PinType.PinCategory ?? "object" + } } /** @@ -1066,6 +1058,83 @@ class SelectableDraggableTemplate extends Template { } } +/** + * @typedef {import("../graph/GraphPin").default} GraphPin + */ +class PinTemplate extends Template { + + /** + * Computes the html content of the pin. + * @param {GraphPin} pin Pin entity + * @returns The result html + */ + render(pin) { + if (pin.isInput()) { + return html` + + ${pin.getPinDisplayName()} + ` + } else { + return html` + ${pin.getPinDisplayName()} + + ` + } + } + + /** + * Applies the style to the element. + * @param {GraphPin} pin Element of the graph + */ + apply(pin) { + super.apply(pin); + pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + pin.getType()); + } +} + +class GraphPin extends GraphElement { + + constructor(entity) { + super(entity, new PinTemplate()); + /** @type {import("../entity/PinEntity").default} */ + this.entity; + } + + connectedCallback() { + super.connectedCallback(); + } + + /** + * + * @returns {String} + */ + getPinDisplayName() { + return this.entity.PinName + } + + getAttributes() { + return PinEntity.attributes + } + + isInput() { + return this.entity.isInput() + } + + isOutput() { + return this.entity.isOutput() + } + + isConnected() { + return this.entity.isConnected() + } + + getType() { + return this.entity.getType() + } +} + +customElements.define("u-pin", GraphPin); + /** * @typedef {import("../graph/GraphNode").default} GraphNode */ @@ -1078,12 +1147,6 @@ class NodeTemplate extends SelectableDraggableTemplate { */ header(node) { return html` -
- - - ${node.entity.getNodeDisplayName()} - -
` } @@ -1093,29 +1156,10 @@ class NodeTemplate extends SelectableDraggableTemplate { * @returns The result html */ body(node) { - /** @type {PinEntity[]} */ - let inputs = node.entity.CustomProperties.filter(v => v instanceof PinEntity); - let outputs = inputs.filter(v => v.isOutput()); + let inputs = node.entity.CustomProperties.filter(v => v instanceof PinEntity$1); + inputs.filter(v => v.isOutput()); inputs = inputs.filter(v => v.isInput()); return html` -
-
- ${inputs.map((input, index) => html` -
- - ${input.getPinDisplayName()} -
- `).join("") ?? ""} -
-
- ${outputs.map((output, index) => html` -
- ${output.getPinDisplayName()} - -
- `).join('') ?? ''} -
-
` } @@ -1128,8 +1172,16 @@ class NodeTemplate extends SelectableDraggableTemplate { return html`
- ${this.header(node)} - ${this.body(node)} +
+ + + ${node.entity.getNodeDisplayName()} + +
+
+
+
+
` @@ -1147,6 +1199,13 @@ class NodeTemplate extends SelectableDraggableTemplate { } node.style.setProperty("--ueb-position-x", node.location[0]); node.style.setProperty("--ueb-position-y", node.location[1]); + /** @type {HTMLElement} */ + let inputContainer = node.querySelector(".ueb-node-inputs"); + /** @type {HTMLElement} */ + let outputContainer = node.querySelector(".ueb-node-outputs"); + let pins = node.getPinEntities(); + pins.filter(v => v.isInput()).forEach(v => inputContainer.appendChild(new GraphPin(v))); + pins.filter(v => v.isOutput()).forEach(v => outputContainer.appendChild(new GraphPin(v))); } } @@ -1173,6 +1232,11 @@ class IntegerEntity extends Entity { } constructor(options = {}) { + if (options.constructor === Number || options.constructor === String) { + options = { + value: options + }; + } super(options); this.value = Math.round(this.value); } @@ -1214,7 +1278,7 @@ class ObjectEntity extends Entity { NodeGuid: GuidEntity, ErrorType: new TypeInitialization(IntegerEntity, false), ErrorMsg: new TypeInitialization(String, false, ""), - CustomProperties: [PinEntity] + CustomProperties: [PinEntity$1] } getAttributes() { @@ -1390,6 +1454,14 @@ class GraphNode extends SelectableDraggable { return new GraphNode(entity) } + /** + * + * @returns {PinEntity[]} + */ + getPinEntities() { + return this.entity.CustomProperties.filter(v => v instanceof PinEntity$1) + } + connectedCallback() { this.getAttribute("type")?.trim(); super.connectedCallback(); @@ -1406,8 +1478,9 @@ class GraphNode extends SelectableDraggable { } setLocation(value = [0, 0]) { - this.entity.NodePosX = value[0]; - this.entity.NodePosY = value[1]; + let nodeType = this.entity.NodePosX.constructor; + this.entity.NodePosX = new nodeType(value[0]); + this.entity.NodePosY = new nodeType(value[1]); super.setLocation(value); } } @@ -1439,7 +1512,7 @@ class Grammar { None = _ => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False") Number = _ => P.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") - Integer = _ => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity({ value: v })).desc("an integer") + Integer = _ => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") String = _ => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")') Word = _ => P.regex(/[a-zA-Z]+/).desc("a word") Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })).desc("32 digit hexadecimal (accepts all the letters for safety) value") @@ -1508,7 +1581,7 @@ class Grammar { return r.PinReference case FunctionReferenceEntity: return r.FunctionReference - case PinEntity: + case PinEntity$1: return r.Pin case Array: return P.seqMap( @@ -1568,8 +1641,8 @@ class Grammar { Pin = r => Grammar.CreateMultiAttributeGrammar( r, P.string("Pin"), - PinEntity, - attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey) + PinEntity$1, + attributeKey => Utility.objectGet(PinEntity$1.attributes, attributeKey) ) CustomProperties = r => P.string("CustomProperties") @@ -1722,7 +1795,7 @@ class ObjectSerializer extends Serializer { let result = `Begin Object Class=${this.writeValue(object.Class)} Name=${this.writeValue(object.Name)} ${this.subWrite([], object) + object - .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin)) + .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity$1).write(pin)) .join("")} End Object`; return result @@ -1764,9 +1837,9 @@ class Paste extends Context { nodes.forEach(node => { const locationOffset = [ mousePosition[0] - left, - mousePosition[1] - top + mousePosition[1] - top, ]; - node.addLocation(locationOffset); + node.addLocation(this.blueprint.compensateTranslation(locationOffset)); node.setSelected(true); }); } @@ -2319,8 +2392,8 @@ function initializeSerializerFactory() { new ObjectSerializer() ); SerializerFactory.registerSerializer( - PinEntity, - new GeneralSerializer(v => `Pin (${v})`, PinEntity, "", ",", true) + PinEntity$1, + new GeneralSerializer(v => `Pin (${v})`, PinEntity$1, "", ",", true) ); SerializerFactory.registerSerializer( FunctionReferenceEntity, diff --git a/js/entity/IntegerEntity.js b/js/entity/IntegerEntity.js index a16a5fe..8f94aaa 100755 --- a/js/entity/IntegerEntity.js +++ b/js/entity/IntegerEntity.js @@ -11,6 +11,11 @@ export default class IntegerEntity extends Entity { } constructor(options = {}) { + if (options.constructor === Number || options.constructor === String) { + options = { + value: options + } + } super(options) this.value = Math.round(this.value) } diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js index 8ed11d1..c0a75bb 100755 --- a/js/entity/PinEntity.js +++ b/js/entity/PinEntity.js @@ -41,22 +41,6 @@ export default class PinEntity extends Entity { return PinEntity.attributes } - /** - * - * @returns {String} - */ - getPinDisplayName() { - return this.PinName - } - - isConnected() { - return this.LinkedTo.length > 0 - } - - getType() { - return this.PinType.PinCategory ?? "object" - } - isInput() { if (!this.bHidden && this.Direction !== "EGPD_Output") { return true @@ -68,4 +52,12 @@ export default class PinEntity extends Entity { return true } } + + isConnected() { + return this.LinkedTo.length > 0 + } + + getType() { + return this.PinType.PinCategory ?? "object" + } } diff --git a/js/graph/GraphNode.js b/js/graph/GraphNode.js index 10ace76..2e5c40a 100755 --- a/js/graph/GraphNode.js +++ b/js/graph/GraphNode.js @@ -3,6 +3,7 @@ import ObjectEntity from "../entity/ObjectEntity" import SelectableDraggable from "./SelectableDraggable" import SerializerFactory from "../serialization/SerializerFactory" import DragLink from "../input/DragLink" +import PinEntity from "../entity/PinEntity" export default class GraphNode extends SelectableDraggable { @@ -24,6 +25,14 @@ export default class GraphNode extends SelectableDraggable { return new GraphNode(entity) } + /** + * + * @returns {PinEntity[]} + */ + getPinEntities() { + return this.entity.CustomProperties.filter(v => v instanceof PinEntity) + } + connectedCallback() { const type = this.getAttribute("type")?.trim() super.connectedCallback() @@ -40,8 +49,9 @@ export default class GraphNode extends SelectableDraggable { } setLocation(value = [0, 0]) { - this.entity.NodePosX = value[0] - this.entity.NodePosY = value[1] + let nodeType = this.entity.NodePosX.constructor + this.entity.NodePosX = new nodeType(value[0]) + this.entity.NodePosY = new nodeType(value[1]) super.setLocation(value) } } diff --git a/js/graph/GraphPin.js b/js/graph/GraphPin.js index 269339b..8c5c98d 100644 --- a/js/graph/GraphPin.js +++ b/js/graph/GraphPin.js @@ -3,13 +3,43 @@ import PinTemplate from "../template/PinTemplate" export default class GraphPin extends GraphElement { - constructor() { - super({}, new PinTemplate()) + constructor(entity) { + super(entity, new PinTemplate()) + /** @type {import("../entity/PinEntity").default} */ + this.entity } - /*connectedCallback() { + connectedCallback() { super.connectedCallback() - }*/ + } + + /** + * + * @returns {String} + */ + getPinDisplayName() { + return this.entity.PinName + } + + getAttributes() { + return PinEntity.attributes + } + + isInput() { + return this.entity.isInput() + } + + isOutput() { + return this.entity.isOutput() + } + + isConnected() { + return this.entity.isConnected() + } + + getType() { + return this.entity.getType() + } } customElements.define("u-pin", GraphPin) diff --git a/js/input/Paste.js b/js/input/Paste.js index da2376a..a2abfe5 100644 --- a/js/input/Paste.js +++ b/js/input/Paste.js @@ -37,9 +37,9 @@ export default class Paste extends Context { nodes.forEach(node => { const locationOffset = [ mousePosition[0] - left, - mousePosition[1] - top + mousePosition[1] - top, ] - node.addLocation(locationOffset) + node.addLocation(this.blueprint.compensateTranslation(locationOffset)) node.setSelected(true) }) } diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js index 9ddeb4f..abb5fff 100755 --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -21,7 +21,7 @@ export default class Grammar { None = _ => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False") Number = _ => P.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") - Integer = _ => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity({ value: v })).desc("an integer") + Integer = _ => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer") String = _ => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")') Word = _ => P.regex(/[a-zA-Z]+/).desc("a word") Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).map(v => new GuidEntity({ value: v })).desc("32 digit hexadecimal (accepts all the letters for safety) value") diff --git a/js/template/BlueprintTemplate.js b/js/template/BlueprintTemplate.js index 591ee12..b279019 100755 --- a/js/template/BlueprintTemplate.js +++ b/js/template/BlueprintTemplate.js @@ -1,4 +1,3 @@ -import Blueprint from "../Blueprint" import html from "./html" import sanitizeText from "./sanitizeText" import Template from "./Template" diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js index e541e7f..3e63619 100755 --- a/js/template/NodeTemplate.js +++ b/js/template/NodeTemplate.js @@ -1,6 +1,7 @@ import html from "./html" import PinEntity from "../entity/PinEntity" import SelectableDraggableTemplate from "./SelectableDraggableTemplate" +import GraphPin from "../graph/GraphPin" /** * @typedef {import("../graph/GraphNode").default} GraphNode @@ -14,12 +15,6 @@ export default class NodeTemplate extends SelectableDraggableTemplate { */ header(node) { return html` -
- - - ${node.entity.getNodeDisplayName()} - -
` } @@ -29,29 +24,10 @@ export default class NodeTemplate extends SelectableDraggableTemplate { * @returns The result html */ body(node) { - /** @type {PinEntity[]} */ let inputs = node.entity.CustomProperties.filter(v => v instanceof PinEntity) let outputs = inputs.filter(v => v.isOutput()) inputs = inputs.filter(v => v.isInput()) return html` -
-
- ${inputs.map((input, index) => html` -
- - ${input.getPinDisplayName()} -
- `).join("") ?? ""} -
-
- ${outputs.map((output, index) => html` -
- ${output.getPinDisplayName()} - -
- `).join('') ?? ''} -
-
` } @@ -64,8 +40,16 @@ export default class NodeTemplate extends SelectableDraggableTemplate { return html`
- ${this.header(node)} - ${this.body(node)} +
+ + + ${node.entity.getNodeDisplayName()} + +
+
+
+
+
` @@ -83,5 +67,12 @@ export default class NodeTemplate extends SelectableDraggableTemplate { } node.style.setProperty("--ueb-position-x", node.location[0]) node.style.setProperty("--ueb-position-y", node.location[1]) + /** @type {HTMLElement} */ + let inputContainer = node.querySelector(".ueb-node-inputs") + /** @type {HTMLElement} */ + let outputContainer = node.querySelector(".ueb-node-outputs") + let pins = node.getPinEntities() + pins.filter(v => v.isInput()).forEach(v => inputContainer.appendChild(new GraphPin(v))) + pins.filter(v => v.isOutput()).forEach(v => outputContainer.appendChild(new GraphPin(v))) } } diff --git a/js/template/PinTemplate.js b/js/template/PinTemplate.js index 3a3f73f..73b4201 100644 --- a/js/template/PinTemplate.js +++ b/js/template/PinTemplate.js @@ -2,50 +2,35 @@ import html from "./html" import Template from "./Template" /** - * @typedef {import("../entity/PinEntity").default} PinEntity + * @typedef {import("../graph/GraphPin").default} GraphPin */ export default class PinTemplate extends Template { - /** - * Computes the template for a pin in case it is an input. - * @param {PinEntity} pin Pin entity - * @returns The result html - */ - renderInputPin(pin) { - return html` -
- - ${pin.getPinDisplayName()} -
- ` - } - - /** - * Computes the template for a pin in case it is an output. - * @param {PinEntity} pin Pin entity - * @returns The result html - */ - renderOutputPin(pin) { - return html` -
- ${output.getPinDisplayName()} - -
- ` - } - /** * Computes the html content of the pin. - * @param {PinEntity} pin Pin entity + * @param {GraphPin} pin Pin entity * @returns The result html */ render(pin) { if (pin.isInput()) { - return this.renderInputPin(pin) + return html` + + ${pin.getPinDisplayName()} + ` + } else { + return html` + ${pin.getPinDisplayName()} + + ` } - if (pin.isOutput()) { - return this.renderOutputPin(pin) - } - return "" } -} + + /** + * Applies the style to the element. + * @param {GraphPin} pin Element of the graph + */ + apply(pin) { + super.apply(pin) + pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + pin.getType()) + } +} \ No newline at end of file