diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index f4cc044..f9188b3 100644 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -1,2225 +1 @@ -/** @typedef {import("./graph/GraphNode").default} GraphNode */ -class BlueprintData { - - constructor() { - /** @type {GraphNode[]}" */ - this.nodes = new Array(); - this.expandGridSize = 400; - /** @type {number[]} */ - this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]; - /** @type {number[]} */ - this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]; - /** @type {number[]} */ - this.mousePosition = [0, 0]; - } -} - -/** - * @typedef {import(""../entity/Entity"").default} Entity - */ -class Template { - - /** - * Computes the html content of the target element. - * @param {Entity} entity Entity representing the element - * @returns The computed html - */ - render(entity) { - return "" - } - - /** - * Returns the html elements rendered by this template. - * @param {Entity} entity Entity representing the element - * @returns The rendered elements - */ - getElements(entity) { - let aDiv = document.createElement("div"); - aDiv.innerHTML = this.render(entity); - return aDiv.childNodes - } -} - -class BlueprintTemplate extends Template { - header(element) { - return ` -
-
1:1
-
- ` - } - - overlay() { - return ` -
- ` - } - - /** - * - * @param {import("../Blueprint").Blueprint} element - * @returns - */ - viewport(element) { - return ` -
-
-
-
-
- ` - } - - /** - * Computes the html content of the target element. - * @param {HTMLElement} element Target element - * @returns The computed html - */ - render(element) { - return ` - ${this.header(element)} - ${this.overlay(element)} - ${this.viewport(element)} - ` - } -} - -class Context { - - constructor(target, blueprint, options) { - /** @type {HTMLElement} */ - this.target = target; - /** @type {import("../Blueprint").default}" */ - this.blueprint = blueprint; - this.options = options; - if (options?.wantsFocusCallback ?? false) { - let self = this; - this.blueprintfocusHandler = _ => self.blueprintFocused(); - this.blueprintunfocusHandler = _ => self.blueprintUnfocused(); - this.blueprint.addEventListener("blueprintfocus", this.blueprintfocusHandler); - this.blueprint.addEventListener("blueprintunfocus", this.blueprintunfocusHandler); - } - } - - unlistenDOMElement() { - this.blueprint.removeEventListener("blueprintfocus", this.blueprintfocusHandler); - this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler); - } - - blueprintFocused() { - } - - blueprintUnfocused() { - } -} - -class TypeInitialization { - - static sanitize(value) { - if (!(value instanceof Object)) { - return value // Is already primitive - } - if (value instanceof Boolean || value instanceof Number || value instanceof String) { - return value.valueOf() - } - return value - } - - /** - * - * @param {typeof Object} type - * @param {boolean} showDefault - * @param {*} value - */ - constructor(type, showDefault = true, value = undefined) { - if (value === undefined) { - value = TypeInitialization.sanitize(new type()); - } - this.value = value; - this.showDefault = showDefault; - this.type = type; - } -} - -class Utility { - static clamp(val, min, max) { - return Math.min(Math.max(val, min), max) - } - - static getScale(element) { - return getComputedStyle(element).getPropertyValue("--ueb-scale") - } - - /** - * Sets a value in an object - * @param {String[]} keys The chained keys to access from object in order to set the value - * @param {any} value Value to be set - * @param {Object} target Object holding the data - * @param {Boolean} create Whether to create or not the key in case it doesn't exist - * @returns {Boolean} Returns true on succes, false otherwise - */ - static objectSet(target, keys, value, create = false) { - if (keys.constructor != Array) { - console.error("Expected keys to be an array."); - } - if (keys.length == 1) { - if (create || keys[0] in target) { - target[keys[0]] = value; - return true - } - } else if (keys.length > 0) { - return Utility.objectSet(target[keys[0]], keys.slice(1), value, create) - } - return false - } - - /** - * Gets a value from an object, gives defaultValue in case of failure - * @param {Object} source Object holding the data - * @param {String[]} keys The chained keys to access from object in order to get the value - * @param {any} defaultValue Value to return in case from doesn't have it - * @returns {any} The value in from corresponding to the keys or defaultValue otherwise - */ - static objectGet(source, keys, defaultValue = null) { - if (keys.constructor != Array) { - console.error("Expected keys to be an array."); - } - if (keys.length == 0 || !(keys[0] in source)) { - return defaultValue - } - if (keys.length == 1) { - return source[keys[0]] - } - return Utility.objectGet(source[keys[0]], keys.slice(1), defaultValue) - } - - static equals(a, b) { - a = TypeInitialization.sanitize(a); - b = TypeInitialization.sanitize(b); - return a === b - } - - /** - * - * @param {String} value - */ - static FirstCapital(value) { - return value.charAt(0).toUpperCase() + value.substring(1) - } - - static getType(value) { - let constructor = value?.constructor; - switch (constructor) { - case TypeInitialization: - return value.type - case Function: - return value - default: - return constructor - } - } -} - -class Pointing extends Context { - - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement; - } - - /** - * - * @param {MouseEvent} mouseEvent - * @returns - */ - getLocation(mouseEvent) { - const scaleCorrection = 1 / Utility.getScale(this.target); - const targetOffset = this.movementSpace.getBoundingClientRect(); - let location = [ - (mouseEvent.clientX - targetOffset.x) * scaleCorrection, - (mouseEvent.clientY - targetOffset.y) * scaleCorrection - ]; - return location - } -} - -/** - * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses. - */ -class MouseClickDrag extends Pointing { - - 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.started = false; - 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.stopPropagation(); - self.started = false; - // Attach the listeners - movementListenedElement.addEventListener("mousemove", self.mouseStartedMovingHandler); - document.addEventListener("mouseup", self.mouseUpHandler); - self.clickedPosition = self.getLocation(e); - self.clicked(self.clickedPosition); - } - break - default: - if (!self.exitAnyButton) { - self.mouseUpHandler(e); - } - break - } - }; - - this.mouseStartedMovingHandler = e => { - e.preventDefault(); - e.stopPropagation(); - - // Delegate from now on to self.mouseMoveHandler - movementListenedElement.removeEventListener("mousemove", self.mouseStartedMovingHandler); - movementListenedElement.addEventListener("mousemove", self.mouseMoveHandler); - - // Do actual actions - self.startDrag(); - self.started = true; - }; - - this.mouseMoveHandler = e => { - e.preventDefault(); - e.stopPropagation(); - const location = self.getLocation(e); - const movement = [e.movementX, e.movementY]; - self.dragTo(location, movement); - }; - - this.mouseUpHandler = e => { - if (!self.exitAnyButton || e.button == self.clickButton) { - // Remove the handlers of "mousemove" and "mouseup" - movementListenedElement.removeEventListener("mousemove", self.mouseStartedMovingHandler); - movementListenedElement.removeEventListener("mousemove", self.mouseMoveHandler); - document.removeEventListener("mouseup", self.mouseUpHandler); - self.endDrag(); - } - }; - - this.target.addEventListener("mousedown", this.mouseDownHandler); - if (this.clickButton == 2) { - this.target.addEventListener("contextmenu", this.preventDefault); - } - } - - preventDefault(e) { - e.preventDefault(); - } - - unlistenDOMElement() { - super.unlistenDOMElement(); - this.target.removeEventListener("mousedown", this.mouseDownHandler); - if (this.clickButton == 2) { - this.target.removeEventListener("contextmenu", this.preventDefault); - } blueprintunfocusHandler; - } - - /* Subclasses will override the following methods */ - clicked(location) { - } - - startDrag() { - } - - dragTo(location, movement) { - } - - endDrag() { - } -} - -class DragScroll extends MouseClickDrag { - - dragTo(location, movement) { - this.blueprint.scrollDelta([-movement[0], -movement[1]]); - } -} - -class GraphElement extends HTMLElement { - - /** - * - * @param {import("../template/Template").default} template The template to render this node - */ - constructor(entity, template) { - super(); - /** @type {import("../Blueprint").default}" */ - this.blueprint = null; - this.entity = entity; - this.template = template; - } - - connectedCallback() { - this.blueprint = this.closest("u-blueprint"); - this.append(...this.template.getElements(this.entity)); - } -} - -class OrderedIndexArray { - - /** - * @param {(arrayElement: number) => number} compareFunction A function that, given acouple of elements of the array telles what order are they on. - * @param {(number|array)} value Initial length or array to copy from - */ - constructor(comparisonValueSupplier = (a) => a, value = null) { - this.array = new Uint32Array(value); - this.comparisonValueSupplier = comparisonValueSupplier; - this.length = 0; - this.currentPosition = 0; - } - - /** - * - * @param {number} index The index of the value to return - * @returns The element of the array - */ - get(index) { - if (index >= 0 && index < this.length) { - return this.array[index] - } - return null - } - - /** - * Returns the array used by this object. - * @returns The array. - */ - getArray() { - return this.array - } - - /** - * Get the position that the value supplied should (or does) occupy in the aray. - * @param {number} value The value to look for (it doesn't have to be part of the array). - * @returns The position index. - */ - getPosition(value) { - let l = 0; - let r = this.length; - while (l < r) { - let m = Math.floor((l + r) / 2); - if (this.comparisonValueSupplier(this.array[m]) < value) { - l = m + 1; - } else { - r = m; - } - } - return l - } - - reserve(length) { - if (this.array.length < length) { - let newArray = new Uint32Array(length); - newArray.set(this.array); - this.array = newArray; - } - } - - /** - * Inserts the element in the array. - * @param element {number} The value to insert into the array. - * @returns {number} The position into occupied by value into the array. - */ - insert(element, comparisonValue = null) { - let position = this.getPosition(this.comparisonValueSupplier(element)); - if ( - position < this.currentPosition - || comparisonValue != null && position == this.currentPosition && this.comparisonValueSupplier(element) < comparisonValue) { - ++this.currentPosition; - } - /* - let newArray = new Uint32Array(this.array.length + 1) - newArray.set(this.array.subarray(0, position), 0) - newArray[position] = element - newArray.set(this.array.subarray(position), position + 1) - this.array = newArray - */ - this.shiftRight(position); - this.array[position] = element; - ++this.length; - return position - } - - /** - * Removes the element from the array. - * @param {number} value The value of the element to be remove. - */ - remove(element) { - let position = this.getPosition(this.comparisonValueSupplier(element)); - if (this.array[position] == element) { - this.removeAt(position); - } - } - - /** - * Removes the element into the specified position from the array. - * @param {number} position The index of the element to be remove. - */ - removeAt(position) { - if (position < this.currentPosition) { - --this.currentPosition; - } - /* - let newArray = new Uint32Array(this.array.length - 1) - newArray.set(this.array.subarray(0, position), 0) - newArray.set(this.array.subarray(position + 1), position) - this.array = newArray - */ - this.shiftLeft(position); - --this.length; - return position - } - - getNext() { - if (this.currentPosition >= 0 && this.currentPosition < this.length) { - return this.get(this.currentPosition) - } - return null - } - - getNextValue() { - if (this.currentPosition >= 0 && this.currentPosition < this.length) { - return this.comparisonValueSupplier(this.get(this.currentPosition)) - } else { - return Number.MAX_SAFE_INTEGER - } - } - - getPrev() { - if (this.currentPosition > 0) { - return this.get(this.currentPosition - 1) - } - return null - } - - getPrevValue() { - if (this.currentPosition > 0) { - return this.comparisonValueSupplier(this.get(this.currentPosition - 1)) - } else { - return Number.MIN_SAFE_INTEGER - } - } - - shiftLeft(leftLimit, steps = 1) { - this.array.set(this.array.subarray(leftLimit + steps), leftLimit); - } - - shiftRight(leftLimit, steps = 1) { - this.array.set(this.array.subarray(leftLimit, -steps), leftLimit + steps); - } -} - -class FastSelectionModel { - - /** - * @typedef {{ - * primaryInf: number, - * primarySup: number, - * secondaryInf: number, - * secondarySup: number - * }} BoundariesInfo - * @typedef {{ - * primaryBoundary: number, - * secondaryBoundary: number, - * insertionPosition: number, - * rectangle: number - * onSecondaryAxis: Boolean - * }} Metadata - * @typedef {numeric} Rectangle - * @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue]. - * @param {Rectangle[]} rectangles Rectangles that can be selected by this object. - * @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle. - * @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles. - */ - constructor(initialPosition, rectangles, boundariesFunc, selectFunc) { - this.initialPosition = initialPosition; - this.finalPosition = initialPosition; - /** @type Metadata[] */ - this.metadata = new Array(rectangles.length); - this.primaryOrder = new OrderedIndexArray((element) => this.metadata[element].primaryBoundary); - this.secondaryOrder = new OrderedIndexArray((element) => this.metadata[element].secondaryBoundary); - this.selectFunc = selectFunc; - this.rectangles = rectangles; - this.primaryOrder.reserve(this.rectangles.length); - this.secondaryOrder.reserve(this.rectangles.length); - rectangles.forEach((rect, index) => { - /** @type Metadata */ - let rectangleMetadata = { - primaryBoundary: this.initialPosition[0], - secondaryBoundary: this.initialPosition[1], - rectangle: index, // used to move both expandings inside the this.metadata array - onSecondaryAxis: false - }; - this.metadata[index] = rectangleMetadata; - selectFunc(rect, false); // Initially deselected (Eventually) - const rectangleBoundaries = boundariesFunc(rect); - - // Secondary axis first because it may be inserted in this.secondaryOrder during the primary axis check - if (this.initialPosition[1] < rectangleBoundaries.secondaryInf) { // Initial position is before the rectangle - rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondaryInf; - } else if (rectangleBoundaries.secondarySup < this.initialPosition[1]) { // Initial position is after the rectangle - rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondarySup; - } else { - rectangleMetadata.onSecondaryAxis = true; - } - - if (this.initialPosition[0] < rectangleBoundaries.primaryInf) { // Initial position is before the rectangle - rectangleMetadata.primaryBoundary = rectangleBoundaries.primaryInf; - this.primaryOrder.insert(index); - } else if (rectangleBoundaries.primarySup < this.initialPosition[0]) { // Initial position is after the rectangle - rectangleMetadata.primaryBoundary = rectangleBoundaries.primarySup; - this.primaryOrder.insert(index); - } else { // Initial lays inside the rectangle (considering just this axis) - // Secondary order depends on primary order, if primary boundaries are not satisfied, the element is not watched for secondary ones - if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) { - this.secondaryOrder.insert(index); - } else { - selectFunc(rect, true); - } - } - }); - this.primaryOrder.currentPosition = this.primaryOrder.getPosition(this.initialPosition[0]); - this.secondaryOrder.currentPosition = this.secondaryOrder.getPosition(this.initialPosition[1]); - this.computeBoundaries(this.initialPosition); - } - - computeBoundaries() { - this.boundaries = { - // Primary axis negative expanding - primaryN: { - v: this.primaryOrder.getPrevValue(), - i: this.primaryOrder.getPrev() - }, - primaryP: { - v: this.primaryOrder.getNextValue(), - i: this.primaryOrder.getNext() - }, - // Secondary axis negative expanding - secondaryN: { - v: this.secondaryOrder.getPrevValue(), - i: this.secondaryOrder.getPrev() - }, - // Secondary axis positive expanding - secondaryP: { - v: this.secondaryOrder.getNextValue(), - i: this.secondaryOrder.getNext() - } - }; - } - - selectTo(finalPosition) { - const direction = [ - Math.sign(finalPosition[0] - this.initialPosition[0]), - Math.sign(finalPosition[1] - this.initialPosition[1]) - ]; - const primaryBoundaryCrossed = (index, added) => { - if (this.metadata[index].onSecondaryAxis) { - this.selectFunc(this.rectangles[index], added); - } else { - if (added) { - this.secondaryOrder.insert(index, finalPosition[1]); - const secondaryBoundary = this.metadata[index].secondaryBoundary; - if ( - // If inserted before the current position - Math.sign(finalPosition[1] - secondaryBoundary) == direction[1] - // And after initial position - && Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1] - ) { - // Secondary axis is already satisfied then - this.selectFunc(this.rectangles[index], true); - } - } else { - this.selectFunc(this.rectangles[index], false); - this.secondaryOrder.remove(index); - } - } - this.computeBoundaries(finalPosition); - this.selectTo(finalPosition); - }; - - if (finalPosition[0] < this.boundaries.primaryN.v) { - --this.primaryOrder.currentPosition; - primaryBoundaryCrossed( - this.boundaries.primaryN.i, - this.initialPosition[0] > this.boundaries.primaryN.v && finalPosition[0] < this.initialPosition[0]); - } else if (finalPosition[0] > this.boundaries.primaryP.v) { - ++this.primaryOrder.currentPosition; - primaryBoundaryCrossed( - this.boundaries.primaryP.i, - this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0]); - } - - - const secondaryBoundaryCrossed = (index, added) => { - this.selectFunc(this.rectangles[index], added); - this.computeBoundaries(finalPosition); - this.selectTo(finalPosition); - }; - - if (finalPosition[1] < this.boundaries.secondaryN.v) { - --this.secondaryOrder.currentPosition; - secondaryBoundaryCrossed( - this.boundaries.secondaryN.i, - this.initialPosition[1] > this.boundaries.secondaryN.v && finalPosition[1] < this.initialPosition[1]); - } else if (finalPosition[1] > this.boundaries.secondaryP.v) { - ++this.secondaryOrder.currentPosition; - secondaryBoundaryCrossed( - this.boundaries.secondaryP.i, - this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1]); - } - this.finalPosition = finalPosition; - } - -} - -class GraphSelector extends GraphElement { - - constructor() { - super({}, new Template()); - /** - * @type {import("./GraphSelector").default} - */ - this.selectionModel = null; - } - - connectedCallback() { - super.connectedCallback(); - this.classList.add("ueb-selector"); - this.dataset.selecting = "false"; - } - - /** - * Create a selection rectangle starting from the specified position - * @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element) - */ - startSelecting(initialPosition) { - initialPosition = this.blueprint.compensateTranslation(initialPosition); - // Set initial position - this.style.setProperty("--ueb-select-from-x", initialPosition[0]); - this.style.setProperty("--ueb-select-from-y", initialPosition[1]); - // Final position coincide with the initial position, at the beginning of selection - this.style.setProperty("--ueb-select-to-x", initialPosition[0]); - this.style.setProperty("--ueb-select-to-y", initialPosition[1]); - this.dataset.selecting = "true"; - 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) { - finalPosition = this.blueprint.compensateTranslation(finalPosition); - this.style.setProperty("--ueb-select-to-x", finalPosition[0]); - this.style.setProperty("--ueb-select-to-y", finalPosition[1]); - this.selectionModel.selectTo(finalPosition); - } - - finishSelecting() { - this.dataset.selecting = "false"; - this.selectionModel = null; - } -} - -customElements.define("u-selector", GraphSelector); - -class MouseTracking extends Pointing { - - constructor(target, blueprint, options = {}) { - options.wantsFocusCallback = true; - super(target, blueprint, options); - - let self = this; - this.mousemoveHandler = e => { - self.blueprint.entity.mousePosition = self.getLocation(e); - }; - } - - blueprintFocused() { - this.target.addEventListener("mousemove", this.mousemoveHandler); - } - - blueprintUnfocused() { - this.target.removeEventListener("mousemove", this.mousemoveHandler); - } -} - -class Entity { - - 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] = defaultValue; - } - }; - defineAllAttributes([], this, this.getAttributes()); - } -} - -class GuidEntity extends Entity { - - static attributes = { - value: String - } - - static generateGuid(random = true) { - let values = new Uint32Array(4); - if (random === true) { - crypto.getRandomValues(values); - } - let guid = ""; - values.forEach(n => { - guid += ("0".repeat(8) + n.toString(16).toUpperCase()).slice(-8); - }); - return new GuidEntity({ valud: guid }) - } - - getAttributes() { - return GuidEntity.attributes - } - - toString() { - return this.value - } -} - -class LocalizedTextEntity extends Entity { - - static attributes = { - namespace: String, - key: String, - value: String - } - - getAttributes() { - return LocalizedTextEntity.attributes - } -} - -class ObjectReferenceEntity extends Entity { - - static attributes = { - type: String, - path: String - } - - getAttributes() { - return ObjectReferenceEntity.attributes - } -} - -class PathSymbolEntity extends Entity { - - static attributes = { - value: String - } - - getAttributes() { - return PathSymbolEntity.attributes - } - - toString() { - return this.value - } -} - -class PinReferenceEntity extends Entity { - - static attributes = { - objectName: PathSymbolEntity, - pinGuid: GuidEntity - } - - getAttributes() { - return PinReferenceEntity.attributes - } -} - -class PinEntity extends Entity { - - static attributes = { - PinId: GuidEntity, - PinName: "", - PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null), - PinToolTip: "", - Direction: new TypeInitialization(String, false, ""), - PinType: { - PinCategory: "", - PinSubCategory: "", - PinSubCategoryObject: ObjectReferenceEntity, - PinSubCategoryMemberReference: null, - PinValueType: null, - ContainerType: ObjectReferenceEntity, - bIsReference: false, - bIsConst: false, - bIsWeakPointer: false, - bIsUObjectWrapper: false - }, - LinkedTo: [PinReferenceEntity], - DefaultValue: "", - AutogeneratedDefaultValue: "", - PersistentGuid: GuidEntity, - bHidden: false, - bNotConnectable: false, - bDefaultValueIsReadOnly: false, - bDefaultValueIsIgnored: false, - bAdvancedView: false, - bOrphanedPin: false, - } - - getAttributes() { - return PinEntity.attributes - } - - /** - * - * @returns {String} - */ - getPinDisplayName() { - return this.PinName - } - - isOutput() { - if (this.Direction === "EGPD_Output") { - return true - } - } -} - -/** - * @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 ` -
- - - ${entity.getNodeDisplayName()} - -
- ` - } - - /** - * 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 ` -
-
- ${inputs.map((input, index) => ` -
- - ${input.getPinDisplayName()} -
- `).join("") ?? ""} -
-
- ${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 FunctionReferenceEntity extends Entity { - - static attributes = { - MemberParent: ObjectReferenceEntity, - MemberName: "" - } - - getAttributes() { - return FunctionReferenceEntity.attributes - } -} - -class IntegerEntity extends Entity { - - static attributes = { - value: Number - } - - getAttributes() { - return IntegerEntity.attributes - } - - constructor(options = { value: 0 }) { - options.value = Math.round(options.value); - super(options); - } - - valueOf() { - return this.value - } - - toString() { - return this.value.toString() - } -} - -class VariableReferenceEntity extends Entity { - - static attributes = { - MemberName: String, - MemberGuid: GuidEntity, - bSelfContext: false - } - - getAttributes() { - return VariableReferenceEntity.attributes - } -} - -class ObjectEntity extends Entity { - - static attributes = { - Class: ObjectReferenceEntity, - Name: "", - bIsPureFunc: new TypeInitialization(Boolean, false, false), - VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), - FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), - EventReference: new TypeInitialization(FunctionReferenceEntity, false, null,), - TargetType: new TypeInitialization(ObjectReferenceEntity, false, null), - NodePosX: IntegerEntity, - NodePosY: IntegerEntity, - NodeGuid: GuidEntity, - ErrorType: new TypeInitialization(IntegerEntity, false), - ErrorMsg: new TypeInitialization(String, false, ""), - CustomProperties: [PinEntity] - } - - getAttributes() { - return ObjectEntity.attributes - } - - /** - * - * @returns {String} The name of the node - */ - getNodeDisplayName() { - return this.Name - } -} - -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.dispatchDragEvent(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, this.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]]); - } - - dispatchDragEvent(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 SerializerFactory { - - static #serializers = new Map() - - static registerSerializer(entity, object) { - SerializerFactory.#serializers.set(entity, object); - } - - static getSerializer(entity) { - return SerializerFactory.#serializers.get(Utility.getType(entity)) - } -} - -class DragLink extends MouseClickDrag { - - constructor(target, blueprint, options) { - super(target, blueprint, options); - } - - startDrag() { - //this.selectorElement.startSelecting(this.clickedPosition) - } - - dragTo(location, movement) { - //this.selectorElement.doSelecting(location) - } - - endDrag() { - if (this.started) ; - } -} - -class GraphNode extends SelectableDraggable { - - static fromSerializedObject(str) { - let entity = SerializerFactory.getSerializer(ObjectEntity).read(str); - return new GraphNode(entity) - } - - /** - * - * @param {ObjectEntity} entity - */ - constructor(entity) { - super(entity, new NodeTemplate()); - this.graphNodeName = "n/a"; - this.dragLinkObjects = Array(); - super.setLocation([this.entity.NodePosX, this.entity.NodePosY]); - } - - 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]); - this.querySelectorAll(".ueb-node-input, .ueb-node-output").forEach(element => { - this.dragLinkObjects.push( - new DragLink(element, this.blueprint, { - clickButton: 0, - moveEverywhere: true, - exitAnyButton: true, - looseTarget: true - }) - ); - }); - } - - setLocation(value = [0, 0]) { - this.entity.NodePosX = value[0]; - this.entity.NodePosY = value[1]; - super.setLocation(value); - } -} - -customElements.define("u-node", GraphNode); - -var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - -function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; -} - -var parsimmon_umd_min = {exports: {}}; - -(function (module, exports) { -!function(n,t){module.exports=t();}("undefined"!=typeof self?self:commonjsGlobal,function(){return function(n){var t={};function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{}};return n[e].call(u.exports,u,u.exports,r),u.l=!0,u.exports}return r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e});},r.r=function(n){Object.defineProperty(n,"__esModule",{value:!0});},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},r.p="",r(r.s=0)}([function(n,t,r){function e(n){if(!(this instanceof e))return new e(n);this._=n;}var u=e.prototype;function o(n,t){for(var r=0;r>7),buf:function(n){var t=i(function(n,t,r,e){return n.concat(r===e.length-1?Buffer.from([t,0]).readUInt16BE(0):e.readUInt16BE(r))},[],n);return Buffer.from(f(function(n){return (n<<1&65535)>>8},t))}(r.buf)};}),r}function c(){return "undefined"!=typeof Buffer}function s(){if(!c())throw new Error("Buffer global does not exist; please use webpack if you need to parse Buffers in the browser.")}function l(n){s();var t=i(function(n,t){return n+t},0,n);if(t%8!=0)throw new Error("The bits ["+n.join(", ")+"] add up to "+t+" which is not an even number of bytes; the total should be divisible by 8");var r,u=t/8,o=(r=function(n){return n>48},i(function(n,t){return n||(r(t)?t:n)},null,n));if(o)throw new Error(o+" bit range requested exceeds 48 bit (6 byte) Number max.");return new e(function(t,r){var e=u+r;return e>t.length?x(r,u.toString()+" bytes"):b(e,i(function(n,t){var r=a(t,n.buf);return {coll:n.coll.concat(r.v),buf:r.buf}},{coll:[],buf:t.slice(r,e)},n).coll)})}function p(n,t){return new e(function(r,e){return s(),e+t>r.length?x(e,t+" bytes for "+n):b(e+t,r.slice(e,e+t))})}function h(n,t){if("number"!=typeof(r=t)||Math.floor(r)!==r||t<0||t>6)throw new Error(n+" requires integer length in range [0, 6].");var r;}function d(n){return h("uintBE",n),p("uintBE("+n+")",n).map(function(t){return t.readUIntBE(0,n)})}function v(n){return h("uintLE",n),p("uintLE("+n+")",n).map(function(t){return t.readUIntLE(0,n)})}function g(n){return h("intBE",n),p("intBE("+n+")",n).map(function(t){return t.readIntBE(0,n)})}function m(n){return h("intLE",n),p("intLE("+n+")",n).map(function(t){return t.readIntLE(0,n)})}function y(n){return n instanceof e}function E(n){return "[object Array]"==={}.toString.call(n)}function w(n){return c()&&Buffer.isBuffer(n)}function b(n,t){return {status:!0,index:n,value:t,furthest:-1,expected:[]}}function x(n,t){return E(t)||(t=[t]),{status:!1,index:-1,value:null,furthest:n,expected:t}}function B(n,t){if(!t)return n;if(n.furthest>t.furthest)return n;var r=n.furthest===t.furthest?function(n,t){if(function(){if(void 0!==e._supportsSet)return e._supportsSet;var n="undefined"!=typeof Set;return e._supportsSet=n,n}()&&Array.from){for(var r=new Set(n),u=0;u=0;){if(i in r){e=r[i].line,0===o&&(o=r[i].lineStart);break}"\n"===n.charAt(i)&&(u++,0===o&&(o=i+1)),i--;}var f=e+u,a=t-o;return r[t]={line:f,lineStart:o},{offset:t,line:f+1,column:a+1}}function _(n){if(!y(n))throw new Error("not a parser: "+n)}function L(n,t){return "string"==typeof n?n.charAt(t):n[t]}function O(n){if("number"!=typeof n)throw new Error("not a number: "+n)}function k(n){if("function"!=typeof n)throw new Error("not a function: "+n)}function P(n){if("string"!=typeof n)throw new Error("not a string: "+n)}var q=2,A=3,I=8,F=5*I,M=4*I,z=" ";function R(n,t){return new Array(t+1).join(n)}function U(n,t,r){var e=t-n.length;return e<=0?n:R(r,e)+n}function W(n,t,r,e){return {from:n-t>0?n-t:0,to:n+r>e?e:n+r}}function D(n,t){var r,e,u,o,a,c=t.index,s=c.offset,l=1;if(s===n.length)return "Got the end of the input";if(w(n)){var p=s-s%I,h=s-p,d=W(p,F,M+I,n.length),v=f(function(n){return f(function(n){return U(n.toString(16),2,"0")},n)},function(n,t){var r=n.length,e=[],u=0;if(r<=t)return [n.slice()];for(var o=0;o=4&&(r+=1),l=2,u=f(function(n){return n.length<=4?n.join(" "):n.slice(0,4).join(" ")+" "+n.slice(4).join(" ")},v),(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2);}else {var g=n.split(/\r\n|[\n\r\u2028\u2029]/);r=c.column-1,e=c.line-1,o=W(e,q,A,g.length),u=g.slice(o.from,o.to),a=o.to.toString().length;}var m=e-o.from;return w(n)&&(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2),i(function(t,e,u){var i,f=u===m,c=f?"> ":z;return i=w(n)?U((8*(o.from+u)).toString(16),a,"0"):U((o.from+u+1).toString(),a," "),[].concat(t,[c+i+" | "+e],f?[z+R(" ",a)+" | "+U("",r," ")+R("^",l)]:[])},[],u).join("\n")}function N(n,t){return ["\n","-- PARSING FAILED "+R("-",50),"\n\n",D(n,t),"\n\n",(r=t.expected,1===r.length?"Expected:\n\n"+r[0]:"Expected one of the following: \n\n"+r.join(", ")),"\n"].join("");var r;}function G(n){return void 0!==n.flags?n.flags:[n.global?"g":"",n.ignoreCase?"i":"",n.multiline?"m":"",n.unicode?"u":"",n.sticky?"y":""].join("")}function C(){for(var n=[].slice.call(arguments),t=n.length,r=0;r=2?O(t):t=0;var r=function(n){return RegExp("^(?:"+n.source+")",G(n))}(n),u=""+n;return e(function(n,e){var o=r.exec(n.slice(e));if(o){if(0<=t&&t<=o.length){var i=o[0],f=o[t];return b(e+i.length,f)}return x(e,"valid match group (0 to "+o.length+") in "+u)}return x(e,u)})}function X(n){return e(function(t,r){return b(r,n)})}function Y(n){return e(function(t,r){return x(r,n)})}function Z(n){if(y(n))return e(function(t,r){var e=n._(t,r);return e.index=r,e.value="",e});if("string"==typeof n)return Z(K(n));if(n instanceof RegExp)return Z(Q(n));throw new Error("not a string, regexp, or parser: "+n)}function $(n){return _(n),e(function(t,r){var e=n._(t,r),u=t.slice(r,e.index);return e.status?x(r,'not "'+u+'"'):b(r,null)})}function nn(n){return k(n),e(function(t,r){var e=L(t,r);return r=n.length?x(t,"any character/byte"):b(t+1,L(n,t))}),on=e(function(n,t){return b(n.length,n.slice(t))}),fn=e(function(n,t){return t=0}).desc(t)},e.optWhitespace=pn,e.Parser=e,e.range=function(n,t){return nn(function(r){return n<=r&&r<=t}).desc(n+"-"+t)},e.regex=Q,e.regexp=Q,e.sepBy=V,e.sepBy1=H,e.seq=C,e.seqMap=J,e.seqObj=function(){for(var n,t={},r=0,u=(n=arguments,Array.prototype.slice.call(n)),o=u.length,i=0;i255)throw new Error("Value specified to byte constructor ("+n+"=0x"+n.toString(16)+") is larger in value than a single byte.");var t=(n>15?"0x":"0x0")+n.toString(16);return e(function(r,e){var u=L(r,e);return u===n?b(e+1,u):x(e,t)})},buffer:function(n){return p("buffer",n).map(function(n){return Buffer.from(n)})},encodedString:function(n,t){return p("string",t).map(function(t){return t.toString(n)})},uintBE:d,uint8BE:d(1),uint16BE:d(2),uint32BE:d(4),uintLE:v,uint8LE:v(1),uint16LE:v(2),uint32LE:v(4),intBE:g,int8BE:g(1),int16BE:g(2),int32BE:g(4),intLE:m,int8LE:m(1),int16LE:m(2),int32LE:m(4),floatBE:p("floatBE",4).map(function(n){return n.readFloatBE(0)}),floatLE:p("floatLE",4).map(function(n){return n.readFloatLE(0)}),doubleBE:p("doubleBE",8).map(function(n){return n.readDoubleBE(0)}),doubleLE:p("doubleLE",8).map(function(n){return n.readDoubleLE(0)})},n.exports=e;}])}); -}(parsimmon_umd_min)); - -var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports); - -let P = Parsimmon; - -class Grammar { - // General - InlineWhitespace = _ => P.regex(/[^\S\n]+/).desc("inline whitespace") - InlineOptWhitespace = _ => P.regex(/[^\S\n]*/).desc("inline optional whitespace") - WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") - Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") - 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") - 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") - PathSymbolEntity = _ => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) - ReferencePath = r => P.seq(P.string("/"), r.PathSymbolEntity.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) - .tie() - .atLeast(2) - .tie() - .desc('a path (words with possibly underscore, separated by ".", separated by "/")') - Reference = r => P.alt( - r.None, - r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path })), - P.seqMap( - r.Word, - P.optWhitespace, - P.alt(P.string(`"`), P.string(`'"`)).chain( - result => r.ReferencePath.skip( - P.string(result.split("").reverse().join("")) - ) - ), - (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) - ) - ) - AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') - AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText) - LocalizedText = r => P.seqMap( - P.string("NSLOCTEXT").skip(P.optWhitespace).skip(P.string("(")), - r.String.trim(P.optWhitespace), // namespace - P.string(","), - r.String.trim(P.optWhitespace), // key - P.string(","), - r.String.trim(P.optWhitespace), // value - P.string(")"), - (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ - namespace: namespace, - key: key, - value: value - }) - ) - PinReference = r => P.seqMap( - r.PathSymbolEntity, - P.whitespace, - r.Guid, - (objectName, _, pinGuid) => new PinReferenceEntity({ - objectName: objectName, - pinGuid: pinGuid - }) - ) - static getGrammarForType(r, attributeType, defaultGrammar) { - switch (Utility.getType(attributeType)) { - case Boolean: - return r.Boolean - case Number: - return r.Number - case IntegerEntity: - return r.Integer - case String: - return r.String - case GuidEntity: - return r.Guid - case ObjectReferenceEntity: - return r.Reference - case LocalizedTextEntity: - return r.LocalizedText - case PinReferenceEntity: - return r.PinReference - case FunctionReferenceEntity: - return r.FunctionReference - case PinEntity: - return r.Pin - case Array: - return P.seqMap( - P.string("("), - attributeType - .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) - .reduce((accum, cur) => - !cur || accum === r.AttributeAnyValue - ? r.AttributeAnyValue - : accum.or(cur) - ) - .trim(P.optWhitespace) - .sepBy(P.string(",")) - .skip(P.regex(/,?\s*/)), - P.string(")"), - (_, grammar, __) => grammar - ) - default: - return defaultGrammar - } - } - // Meta grammar - static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) => - attributeGrammar.skip(valueSeparator) - .chain(attributeName => { - const attributeKey = attributeName.split("."); - const attribute = attributeSupplier(attributeKey); - let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue); - return attributeValueGrammar.map(attributeValue => - entity => Utility.objectSet(entity, attributeKey, attributeValue, true) - ) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value - }) - // Meta grammar - static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) => - /** - * Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)' - * Then it populates an object of type EntityType with the attribute values found inside the parentheses. - */ - P.seqMap( - P.seq(keyGrammar, P.optWhitespace, P.string("(")), - Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier) - .trim(P.optWhitespace) - .sepBy(P.string(",")) - .skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma - P.string(')'), - (_, attributes, __) => { - let result = new entityType(); - attributes.forEach(attributeSetter => attributeSetter(result)); - return result - }) - FunctionReference = r => Grammar.CreateMultiAttributeGrammar( - r, - P.succeed(), - FunctionReferenceEntity, - attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey) - ) - Pin = r => Grammar.CreateMultiAttributeGrammar( - r, - P.string("Pin"), - PinEntity, - attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey) - ) - CustomProperties = r => - P.string("CustomProperties") - .then(P.whitespace) - .then(r.Pin) - .map(pin => entity => { - /** @type {Array} */ - let properties = Utility.objectGet(entity, ["CustomProperties"], []); - properties.push(pin); - Utility.objectSet(entity, ["CustomProperties"], properties, true); - }) - - Object = r => P.seqMap( - P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace), - P - .alt( - r.CustomProperties, - Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey)) - ) - .sepBy1(P.whitespace), - P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")), - (_, attributes, __) => { - let result = new ObjectEntity(); - attributes.forEach(attributeSetter => attributeSetter(result)); - return result - } - ) - MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) -} - -class Serializer { - - 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 Entity) { - return serialize(value) - } - } - - 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 Serializer { - - constructor() { - super(ObjectEntity, " ", "\n", false); - } - - showProperty(attributeKey, attributeValue) { - switch (attributeKey.toString()) { - case "Class": - case "Name": - case "CustomProperties": - // Serielized separately - return false - } - return super.showProperty(attributeKey, attributeValue) - } - - read(value) { - const parseResult = Serializer.grammar.Object.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the object."); - return parseResult - } - 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 - * @returns - */ - write(object) { - 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)) - .join("")} -End Object`; - return result - } -} - -class Paste extends Context { - - constructor(target, blueprint, options = {}) { - options.wantsFocusCallback = true; - super(target, blueprint, options); - this.serializer = new ObjectSerializer(); - let self = this; - this.pasteHandle = e => self.pasted(e.clipboardData.getData("Text")); - } - - blueprintFocused() { - document.body.addEventListener("paste", this.pasteHandle); - } - - blueprintUnfocused() { - document.body.removeEventListener("paste", this.pasteHandle); - } - - pasted(value) { - let top = Number.MAX_SAFE_INTEGER; - let left = Number.MAX_SAFE_INTEGER; - let nodes = this.serializer.readMultiple(value).map(entity => { - let node = new GraphNode(entity); - top = Math.min(top, node.location[1]); - left = Math.min(left, node.location[0]); - return node - }); - if (nodes.length > 0) { - this.blueprint.unselectAll(); - } - let mousePosition = this.blueprint.entity.mousePosition; - this.blueprint.addNode(...nodes); - nodes.forEach(node => { - const locationOffset = [ - mousePosition[0] - left, - mousePosition[1] - top - ]; - node.addLocation(locationOffset); - node.setSelected(true); - }); - } -} - -class Select extends MouseClickDrag { - - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.selectorElement = this.blueprint.selectorElement; - } - - startDrag() { - this.selectorElement.startSelecting(this.clickedPosition); - } - - dragTo(location, movement) { - this.selectorElement.doSelecting(location); - } - - endDrag() { - if (this.started) { - this.selectorElement.finishSelecting(); - } else { - this.blueprint.unselectAll(); - } - } -} - -class Unfocus extends Context { - - constructor(target, blueprint, options = {}) { - options.wantsFocusCallback = true; - super(target, blueprint, options); - - let self = this; - this.clickHandler = e => self.clickedSomewhere(e); - if (this.blueprint.focuse) { - document.addEventListener("click", this.clickHandler); - } - } - - /** - * - * @param {MouseEvent} e - */ - clickedSomewhere(e) { - // If target is inside the blueprint grid - if (e.target.closest("u-blueprint")) { - return - } - this.blueprint.setFocused(false); - } - - blueprintFocused() { - document.addEventListener("click", this.clickHandler); - } - - blueprintUnfocused() { - document.removeEventListener("click", this.clickHandler); - } -} - -class MouseWheel extends Pointing { - - /** - * - * @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.getLocation(e); - self.wheel(Math.sign(e.deltaY), location); - }; - this.mouseParentWheelHandler = e => e.preventDefault(); - - if (this.blueprint.focused) { - this.movementSpace.addEventListener("wheel", this.mouseWheelHandler, false); - } - } - - blueprintFocused() { - this.movementSpace.addEventListener("wheel", this.mouseWheelHandler, false); - this.movementSpace.parentElement?.addEventListener("wheel", this.mouseParentWheelHandler); - } - - blueprintUnfocused() { - 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 MouseWheel { - - wheel(variation, location) { - let zoomLevel = this.blueprint.getZoom(); - zoomLevel -= variation; - this.blueprint.setZoom(zoomLevel, location); - } -} - -class Copy extends Context { - - constructor(target, blueprint, options = {}) { - options.wantsFocusCallback = true; - super(target, blueprint, options); - this.serializer = new ObjectSerializer(); - let self = this; - this.copyHandle = _ => self.copied(); - } - - blueprintFocused() { - document.body.addEventListener("copy", this.copyHandle); - } - - blueprintUnfocused() { - document.body.removeEventListener("copy", this.copyHandle); - } - - copied() { - const value = this.blueprint.getNodes(true).map(node => this.serializer.write(node.entity)).join("\n"); - navigator.clipboard.writeText(value); - } -} - -/** @typedef {import("./graph/GraphNode").default} GraphNode */ -class Blueprint extends GraphElement { - - constructor() { - super(new BlueprintData(), new BlueprintTemplate()); - /** @type {HTMLElement} */ - this.gridElement = null; - /** @type {HTMLElement} */ - this.viewportElement = null; - /** @type {HTMLElement} */ - this.overlayElement = null; - /** @type {GraphSelector} */ - this.selectorElement = null; - /** @type {HTMLElement} */ - this.nodesContainerElement = null; - this.dragObject = null; - this.selectObject = null; - /** @type {number} */ - this.zoom = 0; - /** @type {HTMLElement} */ - this.headerElement = null; - this.focused = false; - /** @type {(node: GraphNode) => BoundariesInfo} */ - this.nodeBoundariesSupplier = node => { - let rect = node.getBoundingClientRect(); - let gridRect = this.nodesContainerElement.getBoundingClientRect(); - const scaleCorrection = 1 / this.getScale(); - return { - primaryInf: (rect.left - gridRect.left) * scaleCorrection, - primarySup: (rect.right - gridRect.right) * scaleCorrection, - // Counter intuitive here: the y (secondary axis is positive towards the bottom, therefore upper bound "sup" is bottom) - secondaryInf: (rect.top - gridRect.top) * scaleCorrection, - secondarySup: (rect.bottom - gridRect.bottom) * scaleCorrection - } - }; - /** @type {(node: GraphNode, selected: bool) => void}} */ - this.nodeSelectToggleFunction = (node, selected) => { - node.setSelected(selected); - }; - } - - connectedCallback() { - super.connectedCallback(); - this.classList.add("ueb", `ueb-zoom-${this.zoom}`); - - this.headerElement = this.querySelector('.ueb-viewport-header'); - console.assert(this.headerElement, "Header element not provided by the template."); - this.overlayElement = this.querySelector('.ueb-viewport-overlay'); - console.assert(this.overlayElement, "Overlay element not provided by the template."); - this.viewportElement = this.querySelector('.ueb-viewport-body'); - console.assert(this.viewportElement, "Viewport element not provided by the template."); - this.gridElement = this.viewportElement.querySelector(".ueb-grid"); - console.assert(this.gridElement, "Grid element not provided by the template."); - this.selectorElement = new GraphSelector(); - this.nodesContainerElement = this.querySelector("[data-nodes]"); - console.assert(this.nodesContainerElement, "Nodes container element not provided by the template."); - this.nodesContainerElement.append(this.selectorElement); - this.querySelector("[data-nodes]").append(...this.entity.nodes); - - - this.copyObject = new Copy(this.getGridDOMElement(), this); - this.pasteObject = new Paste(this.getGridDOMElement(), this); - - this.dragObject = new DragScroll(this.getGridDOMElement(), this, { - clickButton: 2, - moveEverywhere: true, - exitAnyButton: false - }); - - this.zoomObject = new Zoom(this.getGridDOMElement(), this, { - looseTarget: true - }); - - this.selectObject = new Select(this.getGridDOMElement(), this, { - clickButton: 0, - moveEverywhere: true, - exitAnyButton: true - }); - - this.unfocusObject = new Unfocus(this.getGridDOMElement(), this); - this.mouseTrackingObject = new MouseTracking(this.getGridDOMElement(), this); - } - - getGridDOMElement() { - return this.gridElement - } - - disconnectedCallback() { - super.disconnectedCallback(); - setSelected(false); - this.dragObject.unlistenDOMElement(); - this.selectObject.unlistenDOMElement(); - this.pasteObject.unlistenDOMElement(); - } - - getScroll() { - return [this.viewportElement.scrollLeft, this.viewportElement.scrollTop] - } - - setScroll(value, smooth = false) { - this.scroll = value; - if (!smooth) { - this.viewportElement.scroll(value[0], value[1]); - } else { - this.viewportElement.scroll({ - left: value[0], - top: value[1], - behavior: "smooth" - }); - } - } - - scrollDelta(delta, smooth = false) { - const scrollMax = this.getScrollMax(); - let currentScroll = this.getScroll(); - let finalScroll = [ - currentScroll[0] + delta[0], - currentScroll[1] + delta[1] - ]; - let expand = [0, 0]; - for (let i = 0; i < 2; ++i) { - if (delta[i] < 0 && finalScroll[i] < 0.25 * this.entity.expandGridSize) { - // Expand if scrolling is diminishing and the remainig space is less that a quarter of an expansion step - expand[i] = finalScroll[i]; - if (expand[i] > 0) { - // Final scroll is still in rage (more than zero) but we want to expand to negative (left or top) - expand[i] = -this.entity.expandGridSize; - } - } else if (delta[i] > 0 && finalScroll[i] > scrollMax[i] - 0.25 * this.entity.expandGridSize) { - // Expand if scrolling is increasing and the remainig space is less that a quarter of an expansion step - expand[i] = finalScroll[i] - scrollMax[i]; - if (expand[i] < 0) { - // Final scroll is still in rage (less than the maximum scroll) but we want to expand to positive (right or bottom) - expand[i] = this.entity.expandGridSize; - } - } - } - if (expand[0] != 0 || expand[1] != 0) { - this.seamlessExpand(this.progressiveSnapToGrid(expand[0]), this.progressiveSnapToGrid(expand[1])); - currentScroll = this.getScroll(); - finalScroll = [ - currentScroll[0] + delta[0], - currentScroll[1] + delta[1] - ]; - } - this.setScroll(finalScroll, smooth); - } - - scrollCenter() { - const scroll = this.getScroll(); - const offset = [ - this.entity.translateValue[0] - scroll[0], - this.entity.translateValue[1] - scroll[1] - ]; - const targetOffset = this.getViewportSize().map(size => size / 2); - const deltaOffset = [ - offset[0] - targetOffset[0], - offset[1] - targetOffset[1] - ]; - this.scrollDelta(deltaOffset, true); - } - - getExpandGridSize() { - return this.entity.expandGridSize - } - - getViewportSize() { - return [ - this.viewportElement.clientWidth, - this.viewportElement.clientHeight - ] - } - - /** - * Get the scroll limits - * @return {array} The horizonal and vertical maximum scroll limits - */ - getScrollMax() { - return [ - this.viewportElement.scrollWidth - this.viewportElement.clientWidth, - this.viewportElement.scrollHeight - this.viewportElement.clientHeight - ] - } - - /** - * Expand the grid, considers the absolute value of params - * @param {number} x - Horizontal expansion value - * @param {number} y - Vertical expansion value - */ - _expand(x, y) { - x = Math.round(Math.abs(x)); - y = Math.round(Math.abs(y)); - this.entity.additional = [this.entity.additional[0] + x, this.entity.additional[1] + y]; - if (this.gridElement) { - this.gridElement.style.setProperty("--ueb-additional-x", this.entity.additional[0]); - this.gridElement.style.setProperty("--ueb-additional-y", this.entity.additional[1]); - } - } - - /** - * Moves the content of the grid according to the coordinates - * @param {number} x - Horizontal translation value - * @param {number} y - Vertical translation value - */ - _translate(x, y) { - x = Math.round(x); - y = Math.round(y); - this.entity.translateValue = [this.entity.translateValue[0] + x, this.entity.translateValue[1] + y]; - if (this.gridElement) { - this.gridElement.style.setProperty("--ueb-translate-x", this.entity.translateValue[0]); - this.gridElement.style.setProperty("--ueb-translate-y", this.entity.translateValue[1]); - } - } - - /** - * Expand the grind indefinitely, the content will remain into position - * @param {number} x - Horizontal expand value (negative means left, positive means right) - * @param {number} y - Vertical expand value (negative means top, positive means bottom) - */ - seamlessExpand(x, y) { - let scale = this.getScale(); - let scaledX = x / scale; - let scaledY = y / scale; - // First expand the grid to contain the additional space - this._expand(scaledX, scaledY); - // If the expansion is towards the left or top, then scroll back to give the illusion that the content is in the same position and translate it accordingly - this._translate(scaledX < 0 ? -scaledX : 0, scaledY < 0 ? -scaledY : 0); - if (x < 0) { - this.viewportElement.scrollLeft -= x; - } - if (y < 0) { - this.viewportElement.scrollTop -= y; - } - } - - progressiveSnapToGrid(x) { - return this.entity.expandGridSize * Math.round(x / this.entity.expandGridSize + 0.5 * Math.sign(x)) - } - - getZoom() { - return this.zoom - } - - setZoom(zoom, center) { - zoom = Utility.clamp(zoom, -12, 0); - if (zoom == this.zoom) { - return - } - let initialScale = this.getScale(); - this.classList.remove(`ueb-zoom-${this.zoom}`); - this.classList.add(`ueb-zoom-${zoom}`); - this.zoom = zoom; - - - if (center) { - let relativeScale = this.getScale() / initialScale; - let newCenter = [ - relativeScale * center[0], - relativeScale * center[1] - ]; - this.scrollDelta([ - (newCenter[0] - center[0]) * initialScale, - (newCenter[1] - center[1]) * initialScale - ]); - } - } - - getScale() { - return parseFloat(getComputedStyle(this.gridElement).getPropertyValue("--ueb-scale")) - } - - compensateTranslation(position) { - position[0] -= this.entity.translateValue[0]; - position[1] -= this.entity.translateValue[1]; - return position - } - - /** - * - * @returns {GraphNode[]} Nodes - */ - getNodes(selected = false) { - if (selected) { - return this.entity.nodes.filter( - /** - * - * @param {GraphNode} node - */ - node => node.selected - ) - } else { - return this.entity.nodes - } - } - - /** - * Unselect all nodes - */ - unselectAll() { - this.entity.nodes.forEach(node => this.nodeSelectToggleFunction(node, false)); - } - - /** - * - * @param {...GraphNode} graphNodes - */ - addNode(...graphNodes) { - [...graphNodes].reduce( - (s, e) => { - s.push(e); - return s - }, - this.entity.nodes); - if (this.nodesContainerElement) { - this.nodesContainerElement.append(...graphNodes); - } - } - - setFocused(value = true) { - if (this.focused == value) { - return; - } - let event = new CustomEvent(value ? "blueprintfocus" : "blueprintunfocus"); - this.focused = value; - this.dataset.focused = this.focused; - if (!this.focused) { - this.unselectAll(); - } - this.dispatchEvent(event); - } -} - -customElements.define("u-blueprint", Blueprint); - -class GraphLink extends GraphElement { - - /** - * - * @typedef {{ - * node: String, - * pin: String - * }} PinReference - * @param {?PinReference} source - * @param {?PinReference} destination - */ - constructor(source, destination) { - super(); - this.source = source; - this.destination = destination; - } - - render() { - return ` - - - - ` - } -} - -customElements.define("u-link", GraphLink); - -class GeneralSerializer extends Serializer { - - constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { - super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter); - this.wrap = wrap ?? (v => `(${v})`); - } - - read(value) { - let grammar = Grammar.getGrammarForType(Serializer.grammar, this.entityType); - const parseResult = grammar.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the entity " + this.entityType.prototype.constructor.name); - return parseResult - } - return parseResult.value - } - - write(object) { - let result = this.wrap(this.subWrite([], object)); - return result - } -} - -class CustomSerializer extends GeneralSerializer { - - constructor(objectWriter, entityType) { - super(undefined, entityType); - this.objectWriter = objectWriter; - } - - write(object) { - let result = this.objectWriter(object); - return result - } -} - -class ToStringSerializer extends GeneralSerializer { - - constructor(entityType) { - super(undefined, entityType); - } - - write(object) { - let result = object.toString(); - return result - } -} - -function initializeSerializerFactory() { - SerializerFactory.registerSerializer( - ObjectEntity, - new ObjectSerializer() - ); - SerializerFactory.registerSerializer( - PinEntity, - new GeneralSerializer(v => `Pin (${v})`, PinEntity, "", ",", true) - ); - SerializerFactory.registerSerializer( - FunctionReferenceEntity, - new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false) - ); - SerializerFactory.registerSerializer( - LocalizedTextEntity, - new GeneralSerializer(v => `NSLOCTEXT(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "") - ); - SerializerFactory.registerSerializer( - PinReferenceEntity, - new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "") - ); - SerializerFactory.registerSerializer( - ObjectReferenceEntity, - new CustomSerializer( - /** @param {ObjectReferenceEntity} objectReference */ - objectReference => (objectReference.type ?? "") + ( - objectReference.path - ? objectReference.type ? `'"${objectReference.path}"'` : objectReference.path - : "" - )) - ); - SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity)); - SerializerFactory.registerSerializer(GuidEntity, new ToStringSerializer(GuidEntity)); - SerializerFactory.registerSerializer(IntegerEntity, new ToStringSerializer(IntegerEntity)); -} - -initializeSerializerFactory(); - -export { Blueprint, GraphLink, GraphNode }; +class t{constructor(){this.nodes=new Array,this.expandGridSize=400,this.additional=[0,0],this.translateValue=[0,0],this.mousePosition=[0,0]}}const e=String.raw;class n{render(t){return""}getElements(t){let e=document.createElement("div");return e.innerHTML=this.render(t),e.childNodes}}class r extends n{header(t){return e`
1:1
`}overlay(){return e`
`}viewport(t){return e`
`}render(t){return e`${this.header(t)} ${this.overlay(t)} ${this.viewport(t)}`}}class i{constructor(t,e,n){if(this.target=t,this.blueprint=e,this.options=n,n?.wantsFocusCallback){let t=this;this.blueprintfocusHandler=e=>t.blueprintFocused(),this.blueprintunfocusHandler=e=>t.blueprintUnfocused(),this.blueprint.addEventListener("blueprintfocus",this.blueprintfocusHandler),this.blueprint.addEventListener("blueprintunfocus",this.blueprintunfocusHandler)}}unlistenDOMElement(){this.blueprint.removeEventListener("blueprintfocus",this.blueprintfocusHandler),this.blueprint.removeEventListener("blueprintunfocus",this.blueprintunfocusHandler)}blueprintFocused(){}blueprintUnfocused(){}}class s{static sanitize(t){return t instanceof Object&&(t instanceof Boolean||t instanceof Number||t instanceof String)?t.valueOf():t}constructor(t,e=!0,n){void 0===n&&(n=s.sanitize(new t)),this.value=n,this.showDefault=e,this.type=t}}class o{static clamp(t,e,n){return Math.min(Math.max(t,e),n)}static getScale(t){return getComputedStyle(t).getPropertyValue("--ueb-scale")}static objectSet(t,e,n,r=!1){if(e.constructor!=Array&&console.error("Expected keys to be an array."),1==e.length){if(r||e[0]in t)return t[e[0]]=n,!0}else if(e.length>0)return o.objectSet(t[e[0]],e.slice(1),n,r);return!1}static objectGet(t,e,n=null){return e.constructor!=Array&&console.error("Expected keys to be an array."),0!=e.length&&e[0]in t?1==e.length?t[e[0]]:o.objectGet(t[e[0]],e.slice(1),n):n}static equals(t,e){return(t=s.sanitize(t))===(e=s.sanitize(e))}static FirstCapital(t){return t.charAt(0).toUpperCase()+t.substring(1)}static getType(t){let e=t?.constructor;switch(e){case s:return t.type;case Function:return t;default:return e}}}class a extends i{constructor(t,e,n){super(t,e,n),this.movementSpace=this.blueprint?.getGridDOMElement()??document.documentElement}getLocation(t){const e=1/o.getScale(this.target),n=this.movementSpace.getBoundingClientRect();return[(t.clientX-n.x)*e,(t.clientY-n.y)*e]}}class u extends a{constructor(t,e,n){super(t,e,n),this.clickButton=n?.clickButton??0,this.exitAnyButton=n?.exitAnyButton??!0,this.moveEverywhere=n?.moveEverywhere??!1,this.looseTarget=n?.looseTarget??!1,this.started=!1,this.clickedPosition=[0,0];const r=this.moveEverywhere?document.documentElement:this.movementSpace;let i=this;this.mouseDownHandler=t=>{if(this.blueprint.setFocused(!0),t.button===i.clickButton)(i.looseTarget||t.target==t.currentTarget)&&(t.stopPropagation(),i.started=!1,r.addEventListener("mousemove",i.mouseStartedMovingHandler),document.addEventListener("mouseup",i.mouseUpHandler),i.clickedPosition=i.getLocation(t),i.clicked(i.clickedPosition));else i.exitAnyButton||i.mouseUpHandler(t)},this.mouseStartedMovingHandler=t=>{t.preventDefault(),t.stopPropagation(),r.removeEventListener("mousemove",i.mouseStartedMovingHandler),r.addEventListener("mousemove",i.mouseMoveHandler),i.startDrag(),i.started=!0},this.mouseMoveHandler=t=>{t.preventDefault(),t.stopPropagation();const e=i.getLocation(t),n=[t.movementX,t.movementY];i.dragTo(e,n)},this.mouseUpHandler=t=>{i.exitAnyButton&&t.button!=i.clickButton||(r.removeEventListener("mousemove",i.mouseStartedMovingHandler),r.removeEventListener("mousemove",i.mouseMoveHandler),document.removeEventListener("mouseup",i.mouseUpHandler),i.endDrag())},this.target.addEventListener("mousedown",this.mouseDownHandler),2==this.clickButton&&this.target.addEventListener("contextmenu",this.preventDefault)}preventDefault(t){t.preventDefault()}unlistenDOMElement(){super.unlistenDOMElement(),this.target.removeEventListener("mousedown",this.mouseDownHandler),2==this.clickButton&&this.target.removeEventListener("contextmenu",this.preventDefault),blueprintunfocusHandler}clicked(t){}startDrag(){}dragTo(t,e){}endDrag(){}}class l extends u{dragTo(t,e){this.blueprint.scrollDelta([-e[0],-e[1]])}}class c extends HTMLElement{constructor(t,e){super(),this.blueprint=null,this.entity=t,this.template=e}connectedCallback(){this.blueprint=this.closest("u-blueprint"),this.append(...this.template.getElements(this.entity))}}class h{constructor(t=(t=>t),e=null){this.array=new Uint32Array(e),this.comparisonValueSupplier=t,this.length=0,this.currentPosition=0}get(t){return t>=0&&t=0&&this.currentPosition=0&&this.currentPosition0?this.get(this.currentPosition-1):null}getPrevValue(){return this.currentPosition>0?this.comparisonValueSupplier(this.get(this.currentPosition-1)):Number.MIN_SAFE_INTEGER}shiftLeft(t,e=1){this.array.set(this.array.subarray(t+e),t)}shiftRight(t,e=1){this.array.set(this.array.subarray(t,-e),t+e)}}class d{constructor(t,e,n,r){this.initialPosition=t,this.finalPosition=t,this.metadata=new Array(e.length),this.primaryOrder=new h((t=>this.metadata[t].primaryBoundary)),this.secondaryOrder=new h((t=>this.metadata[t].secondaryBoundary)),this.selectFunc=r,this.rectangles=e,this.primaryOrder.reserve(this.rectangles.length),this.secondaryOrder.reserve(this.rectangles.length),e.forEach(((t,e)=>{let i={primaryBoundary:this.initialPosition[0],secondaryBoundary:this.initialPosition[1],rectangle:e,onSecondaryAxis:!1};this.metadata[e]=i,r(t,!1);const s=n(t);this.initialPosition[1]{if(this.metadata[n].onSecondaryAxis)this.selectFunc(this.rectangles[n],r);else if(r){this.secondaryOrder.insert(n,t[1]);const r=this.metadata[n].secondaryBoundary;Math.sign(t[1]-r)==e[1]&&Math.sign(r-this.initialPosition[1])==e[1]&&this.selectFunc(this.rectangles[n],!0)}else this.selectFunc(this.rectangles[n],!1),this.secondaryOrder.remove(n);this.computeBoundaries(t),this.selectTo(t)};t[0]this.boundaries.primaryN.v&&t[0]this.boundaries.primaryP.v&&(++this.primaryOrder.currentPosition,n(this.boundaries.primaryP.i,this.initialPosition[0]{this.selectFunc(this.rectangles[e],n),this.computeBoundaries(t),this.selectTo(t)};t[1]this.boundaries.secondaryN.v&&t[1]this.boundaries.secondaryP.v&&(++this.secondaryOrder.currentPosition,r(this.boundaries.secondaryP.i,this.initialPosition[1]{r.blueprint.entity.mousePosition=r.getLocation(t)}}blueprintFocused(){this.target.addEventListener("mousemove",this.mousemoveHandler)}blueprintUnfocused(){this.target.removeEventListener("mousemove",this.mousemoveHandler)}}class m{constructor(t={}){const e=(n,r,i)=>{let a=n.concat("");const u=a.length-1;for(let n in i){if(a[u]=n,i[n]?.constructor===Object){r[n]={},e(a,r[n],i[n]);continue}const l=o.objectGet(t,a);if(null!==l){r[n]=l;continue}let c=i[n];if(c instanceof s){if(!c.showDefault)continue;c=c.value}c instanceof Array?r[n]=[]:(c instanceof Function&&(c=s.sanitize(new c)),r[n]=c)}};e([],this,this.getAttributes())}}class g extends m{static attributes={value:String};static generateGuid(t=!0){let e=new Uint32Array(4);!0===t&&crypto.getRandomValues(e);let n="";return e.forEach((t=>{n+=("0".repeat(8)+t.toString(16).toUpperCase()).slice(-8)})),new g({valud:n})}getAttributes(){return g.attributes}toString(){return this.value}}class b extends m{static attributes={namespace:String,key:String,value:String};getAttributes(){return b.attributes}}class y extends m{static attributes={type:String,path:String};getAttributes(){return y.attributes}}class v extends m{static attributes={value:String};getAttributes(){return v.attributes}toString(){return this.value}}class w extends m{static attributes={objectName:v,pinGuid:g};getAttributes(){return w.attributes}}class E extends m{static attributes={PinId:g,PinName:"",PinFriendlyName:new s(b,!1,null),PinToolTip:"",Direction:new s(String,!1,""),PinType:{PinCategory:"",PinSubCategory:"",PinSubCategoryObject:y,PinSubCategoryMemberReference:null,PinValueType:null,ContainerType:y,bIsReference:!1,bIsConst:!1,bIsWeakPointer:!1,bIsUObjectWrapper:!1},LinkedTo:[w],DefaultValue:"",AutogeneratedDefaultValue:"",PersistentGuid:g,bHidden:!1,bNotConnectable:!1,bDefaultValueIsReadOnly:!1,bDefaultValueIsIgnored:!1,bAdvancedView:!1,bOrphanedPin:!1};getAttributes(){return E.attributes}getPinDisplayName(){return this.PinName}isOutput(){if("EGPD_Output"===this.Direction)return!0}}class S extends n{header(t){return e`
${t.getNodeDisplayName()}
`}body(t){let n=t.CustomProperties.filter((t=>t instanceof E)),r=n.filter((t=>t.isOutput()));return n=n.filter((t=>!t.isOutput())),e`
${n.map(((t,r)=>e`
${t.getPinDisplayName()}
`)).join("")??""}
${r.map(((t,n)=>e`
${t.getPinDisplayName()}
`)).join("")??""}
`}render(t){return e`
${this.header(t)} ${this.body(t)}
`}}class P extends m{static attributes={MemberParent:y,MemberName:""};getAttributes(){return P.attributes}}class x extends m{static attributes={value:Number};getAttributes(){return x.attributes}constructor(t={}){super(t),this.value=Math.round(this.value)}valueOf(){return this.value}toString(){return this.value.toString()}}class O extends m{static attributes={MemberName:String,MemberGuid:g,bSelfContext:!1};getAttributes(){return O.attributes}}class L extends m{static attributes={Class:y,Name:"",bIsPureFunc:new s(Boolean,!1,!1),VariableReference:new s(O,!1,null),FunctionReference:new s(P,!1,null),EventReference:new s(P,!1,null),TargetType:new s(y,!1,null),NodePosX:x,NodePosY:x,NodeGuid:g,ErrorType:new s(x,!1),ErrorMsg:new s(String,!1,""),CustomProperties:[E]};getAttributes(){return L.attributes}getNodeDisplayName(){return this.Name}}class B extends u{constructor(t,e,n){super(t,e,n),this.stepSize=parseInt(n?.stepSize),this.mousePosition=[0,0]}snapToGrid(t){return[this.stepSize*Math.round(t[0]/this.stepSize),this.stepSize*Math.round(t[1]/this.stepSize)]}startDrag(){(isNaN(this.stepSize)||this.stepSize<=0)&&(this.stepSize=parseInt(getComputedStyle(this.target).getPropertyValue("--ueb-grid-snap")),(isNaN(this.stepSize)||this.stepSize<=0)&&(this.stepSize=1)),this.mousePosition=1!=this.stepSize?this.snapToGrid(this.clickedPosition):this.clickedPosition}dragTo(t,e){const n=1!=this.stepSize?this.snapToGrid(t):t,r=[n[0]-this.mousePosition[0],n[1]-this.mousePosition[1]];0==r[0]&&0==r[1]||(this.target.dispatchDragEvent(r),this.mousePosition=n)}}class N extends c{constructor(...t){super(...t),this.dragObject=null,this.location=[0,0],this.selected=!1;let e=this;this.dragHandler=t=>{e.addLocation(t.detail.value)}}connectedCallback(){super.connectedCallback(),this.dragObject=new B(this,this.blueprint,{looseTarget:!0})}disconnectedCallback(){this.dragObject.unlistenDOMElement()}setLocation(t=[0,0]){this.location=t,this.style.setProperty("--ueb-position-x",this.location[0]),this.style.setProperty("--ueb-position-y",this.location[1])}addLocation(t){this.setLocation([this.location[0]+t[0],this.location[1]+t[1]])}dispatchDragEvent(t){this.selected||(this.blueprint.unselectAll(),this.setSelected(!0));let e=new CustomEvent("uDragSelected",{detail:{instigator:this,value:t},bubbles:!1,cancelable:!0,composed:!1});this.blueprint.dispatchEvent(e)}setSelected(t=!0){this.selected!=t&&(this.selected=t,this.selected?(this.classList.add("ueb-selected"),this.blueprint.addEventListener("uDragSelected",this.dragHandler)):(this.classList.remove("ueb-selected"),this.blueprint.removeEventListener("uDragSelected",this.dragHandler)))}}class M{static#t=new Map;static registerSerializer(t,e){M.#t.set(t,e)}static getSerializer(t){return M.#t.get(o.getType(t))}}class j extends u{constructor(t,e,n){super(t,e,n)}startDrag(){}dragTo(t,e){}endDrag(){this.started}}class A extends N{static fromSerializedObject(t){let e=M.getSerializer(L).read(t);return new A(e)}constructor(t){super(t,new S),this.graphNodeName="n/a",this.dragLinkObjects=Array(),super.setLocation([this.entity.NodePosX,this.entity.NodePosY])}connectedCallback(){this.getAttribute("type")?.trim(),super.connectedCallback(),this.classList.add("ueb-node"),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]),this.querySelectorAll(".ueb-node-input, .ueb-node-output").forEach((t=>{this.dragLinkObjects.push(new j(t,this.blueprint,{clickButton:0,moveEverywhere:!0,exitAnyButton:!0,looseTarget:!0}))}))}setLocation(t=[0,0]){this.entity.NodePosX=t[0],this.entity.NodePosY=t[1],super.setLocation(t)}}customElements.define("u-node",A);"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function k(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var T={exports:{}};"undefined"!=typeof self&&self;var z=k(T.exports=function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){function r(t){if(!(this instanceof r))return new r(t);this._=t}var i=r.prototype;function s(t,e){for(var n=0;n>7),buf:function(t){var e=o((function(t,e,n,r){return t.concat(n===r.length-1?Buffer.from([e,0]).readUInt16BE(0):r.readUInt16BE(n))}),[],t);return Buffer.from(a((function(t){return(t<<1&65535)>>8}),e))}(n.buf)}})),n}function l(){return"undefined"!=typeof Buffer}function c(){if(!l())throw new Error("Buffer global does not exist; please use webpack if you need to parse Buffers in the browser.")}function h(t){c();var e=o((function(t,e){return t+e}),0,t);if(e%8!=0)throw new Error("The bits ["+t.join(", ")+"] add up to "+e+" which is not an even number of bytes; the total should be divisible by 8");var n,i=e/8,s=(n=function(t){return t>48},o((function(t,e){return t||(n(e)?e:t)}),null,t));if(s)throw new Error(s+" bit range requested exceeds 48 bit (6 byte) Number max.");return new r((function(e,n){var r=i+n;return r>e.length?S(n,i.toString()+" bytes"):E(r,o((function(t,e){var n=u(e,t.buf);return{coll:t.coll.concat(n.v),buf:n.buf}}),{coll:[],buf:e.slice(n,r)},t).coll)}))}function d(t,e){return new r((function(n,r){return c(),r+e>n.length?S(r,e+" bytes for "+t):E(r+e,n.slice(r,r+e))}))}function p(t,e){if("number"!=typeof(n=e)||Math.floor(n)!==n||e<0||e>6)throw new Error(t+" requires integer length in range [0, 6].");var n}function f(t){return p("uintBE",t),d("uintBE("+t+")",t).map((function(e){return e.readUIntBE(0,t)}))}function m(t){return p("uintLE",t),d("uintLE("+t+")",t).map((function(e){return e.readUIntLE(0,t)}))}function g(t){return p("intBE",t),d("intBE("+t+")",t).map((function(e){return e.readIntBE(0,t)}))}function b(t){return p("intLE",t),d("intLE("+t+")",t).map((function(e){return e.readIntLE(0,t)}))}function y(t){return t instanceof r}function v(t){return"[object Array]"==={}.toString.call(t)}function w(t){return l()&&Buffer.isBuffer(t)}function E(t,e){return{status:!0,index:t,value:e,furthest:-1,expected:[]}}function S(t,e){return v(e)||(e=[e]),{status:!1,index:-1,value:null,furthest:t,expected:e}}function P(t,e){if(!e)return t;if(t.furthest>e.furthest)return t;var n=t.furthest===e.furthest?function(t,e){if(function(){if(void 0!==r._supportsSet)return r._supportsSet;var t="undefined"!=typeof Set;return r._supportsSet=t,t}()&&Array.from){for(var n=new Set(t),i=0;i=0;){if(o in n){r=n[o].line,0===s&&(s=n[o].lineStart);break}"\n"===t.charAt(o)&&(i++,0===s&&(s=o+1)),o--}var a=r+i,u=e-s;return n[e]={line:a,lineStart:s},{offset:e,line:a+1,column:u+1}}function L(t){if(!y(t))throw new Error("not a parser: "+t)}function B(t,e){return"string"==typeof t?t.charAt(e):t[e]}function N(t){if("number"!=typeof t)throw new Error("not a number: "+t)}function M(t){if("function"!=typeof t)throw new Error("not a function: "+t)}function j(t){if("string"!=typeof t)throw new Error("not a string: "+t)}var A=2,k=3,T=8,z=5*T,C=4*T,D=" ";function G(t,e){return new Array(e+1).join(t)}function F(t,e,n){var r=e-t.length;return r<=0?t:G(n,r)+t}function H(t,e,n,r){return{from:t-e>0?t-e:0,to:t+n>r?r:t+n}}function V(t,e){var n,r,i,s,u,l=e.index,c=l.offset,h=1;if(c===t.length)return"Got the end of the input";if(w(t)){var d=c-c%T,p=c-d,f=H(d,z,C+T,t.length),m=a((function(t){return a((function(t){return F(t.toString(16),2,"0")}),t)}),function(t,e){var n=t.length,r=[],i=0;if(n<=e)return[t.slice()];for(var s=0;s=4&&(n+=1),h=2,i=a((function(t){return t.length<=4?t.join(" "):t.slice(0,4).join(" ")+" "+t.slice(4).join(" ")}),m),(u=(8*(s.to>0?s.to-1:s.to)).toString(16).length)<2&&(u=2)}else{var g=t.split(/\r\n|[\n\r\u2028\u2029]/);n=l.column-1,r=l.line-1,s=H(r,A,k,g.length),i=g.slice(s.from,s.to),u=s.to.toString().length}var b=r-s.from;return w(t)&&(u=(8*(s.to>0?s.to-1:s.to)).toString(16).length)<2&&(u=2),o((function(e,r,i){var o,a=i===b,l=a?"> ":D;return o=w(t)?F((8*(s.from+i)).toString(16),u,"0"):F((s.from+i+1).toString(),u," "),[].concat(e,[l+o+" | "+r],a?[D+G(" ",u)+" | "+F("",n," ")+G("^",h)]:[])}),[],i).join("\n")}function W(t,e){return["\n","-- PARSING FAILED "+G("-",50),"\n\n",V(t,e),"\n\n",(n=e.expected,1===n.length?"Expected:\n\n"+n[0]:"Expected one of the following: \n\n"+n.join(", ")),"\n"].join("");var n}function _(t){return void 0!==t.flags?t.flags:[t.global?"g":"",t.ignoreCase?"i":"",t.multiline?"m":"",t.unicode?"u":"",t.sticky?"y":""].join("")}function I(){for(var t=[].slice.call(arguments),e=t.length,n=0;n=2?N(e):e=0;var n=function(t){return RegExp("^(?:"+t.source+")",_(t))}(t),i=""+t;return r((function(t,r){var s=n.exec(t.slice(r));if(s){if(0<=e&&e<=s.length){var o=s[0],a=s[e];return E(r+o.length,a)}return S(r,"valid match group (0 to "+s.length+") in "+i)}return S(r,i)}))}function Y(t){return r((function(e,n){return E(n,t)}))}function K(t){return r((function(e,n){return S(n,t)}))}function J(t){if(y(t))return r((function(e,n){var r=t._(e,n);return r.index=n,r.value="",r}));if("string"==typeof t)return J(X(t));if(t instanceof RegExp)return J(Z(t));throw new Error("not a string, regexp, or parser: "+t)}function Q(t){return L(t),r((function(e,n){var r=t._(e,n),i=e.slice(n,r.index);return r.status?S(n,'not "'+i+'"'):E(n,null)}))}function tt(t){return M(t),r((function(e,n){var r=B(e,n);return n=t.length?S(e,"any character/byte"):E(e+1,B(t,e))})),st=r((function(t,e){return E(t.length,t.slice(e))})),ot=r((function(t,e){return e=0})).desc(e)},r.optWhitespace=ht,r.Parser=r,r.range=function(t,e){return tt((function(n){return t<=n&&n<=e})).desc(t+"-"+e)},r.regex=Z,r.regexp=Z,r.sepBy=R,r.sepBy1=U,r.seq=I,r.seqMap=q,r.seqObj=function(){for(var t,e={},n=0,i=(t=arguments,Array.prototype.slice.call(t)),s=i.length,o=0;o255)throw new Error("Value specified to byte constructor ("+t+"=0x"+t.toString(16)+") is larger in value than a single byte.");var e=(t>15?"0x":"0x0")+t.toString(16);return r((function(n,r){var i=B(n,r);return i===t?E(r+1,i):S(r,e)}))},buffer:function(t){return d("buffer",t).map((function(t){return Buffer.from(t)}))},encodedString:function(t,e){return d("string",e).map((function(e){return e.toString(t)}))},uintBE:f,uint8BE:f(1),uint16BE:f(2),uint32BE:f(4),uintLE:m,uint8LE:m(1),uint16LE:m(2),uint32LE:m(4),intBE:g,int8BE:g(1),int16BE:g(2),int32BE:g(4),intLE:b,int8LE:b(1),int16LE:b(2),int32LE:b(4),floatBE:d("floatBE",4).map((function(t){return t.readFloatBE(0)})),floatLE:d("floatLE",4).map((function(t){return t.readFloatLE(0)})),doubleBE:d("doubleBE",8).map((function(t){return t.readDoubleBE(0)})),doubleLE:d("doubleLE",8).map((function(t){return t.readDoubleLE(0)}))},t.exports=r}]));let C=z;class D{InlineWhitespace=t=>C.regex(/[^\S\n]+/).desc("inline whitespace");InlineOptWhitespace=t=>C.regex(/[^\S\n]*/).desc("inline optional whitespace");WhitespaceNewline=t=>C.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline");Null=t=>C.seq(C.string("("),t.InlineOptWhitespace,C.string(")")).map((t=>null)).desc("null: ()");None=t=>C.string("None").map((t=>new y({type:"None",path:""}))).desc("none");Boolean=t=>C.alt(C.string("True"),C.string("False")).map((t=>"True"===t)).desc("either True or False");Number=t=>C.regex(/[\-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number");Integer=t=>C.regex(/[\-\+]?[0-9]+/).map((t=>new x({value:t}))).desc("an integer");String=t=>C.regex(/(?:[^"\\]|\\.)*/).wrap(C.string('"'),C.string('"')).desc('string (with possibility to escape the quote using ")');Word=t=>C.regex(/[a-zA-Z]+/).desc("a word");Guid=t=>C.regex(/[0-9a-zA-Z]{32}/).map((t=>new g({value:t}))).desc("32 digit hexadecimal (accepts all the letters for safety) value");PathSymbolEntity=t=>C.regex(/[0-9a-zA-Z_]+/).map((t=>new v({value:t})));ReferencePath=t=>C.seq(C.string("/"),t.PathSymbolEntity.map((t=>t.toString())).sepBy1(C.string(".")).tieWith(".")).tie().atLeast(2).tie().desc('a path (words with possibly underscore, separated by ".", separated by "/")');Reference=t=>C.alt(t.None,t.ReferencePath.map((t=>new y({type:"",path:t}))),C.seqMap(t.Word,C.optWhitespace,C.alt(C.string('"'),C.string("'\"")).chain((e=>t.ReferencePath.skip(C.string(e.split("").reverse().join(""))))),((t,e,n)=>new y({type:t,path:n}))));AttributeName=t=>t.Word.sepBy1(C.string(".")).tieWith(".").desc('words separated by ""');AttributeAnyValue=t=>C.alt(t.Null,t.None,t.Boolean,t.Number,t.Integer,t.String,t.Guid,t.Reference,t.LocalizedText);LocalizedText=t=>C.seqMap(C.string("NSLOCTEXT").skip(C.optWhitespace).skip(C.string("(")),t.String.trim(C.optWhitespace),C.string(","),t.String.trim(C.optWhitespace),C.string(","),t.String.trim(C.optWhitespace),C.string(")"),((t,e,n,r,i,s,o)=>new b({namespace:e,key:r,value:s})));PinReference=t=>C.seqMap(t.PathSymbolEntity,C.whitespace,t.Guid,((t,e,n)=>new w({objectName:t,pinGuid:n})));static getGrammarForType(t,e,n){switch(o.getType(e)){case Boolean:return t.Boolean;case Number:return t.Number;case x:return t.Integer;case String:return t.String;case g:return t.Guid;case y:return t.Reference;case b:return t.LocalizedText;case w:return t.PinReference;case P:return t.FunctionReference;case E:return t.Pin;case Array:return C.seqMap(C.string("("),e.map((e=>D.getGrammarForType(t,o.getType(e)))).reduce(((e,n)=>n&&e!==t.AttributeAnyValue?e.or(n):t.AttributeAnyValue)).trim(C.optWhitespace).sepBy(C.string(",")).skip(C.regex(/,?\s*/)),C.string(")"),((t,e,n)=>e));default:return n}}static CreateAttributeGrammar=(t,e,n,r=C.string("=").trim(C.optWhitespace))=>e.skip(r).chain((e=>{const r=e.split("."),i=n(r);return D.getGrammarForType(t,i,t.AttributeAnyValue).map((t=>e=>o.objectSet(e,r,t,!0)))}));static CreateMultiAttributeGrammar=(t,e,n,r)=>C.seqMap(C.seq(e,C.optWhitespace,C.string("(")),D.CreateAttributeGrammar(t,t.AttributeName,r).trim(C.optWhitespace).sepBy(C.string(",")).skip(C.regex(/,?/).then(C.optWhitespace)),C.string(")"),((t,e,r)=>{let i=new n;return e.forEach((t=>t(i))),i}));FunctionReference=t=>D.CreateMultiAttributeGrammar(t,C.succeed(),P,(t=>o.objectGet(P.attributes,t)));Pin=t=>D.CreateMultiAttributeGrammar(t,C.string("Pin"),E,(t=>o.objectGet(E.attributes,t)));CustomProperties=t=>C.string("CustomProperties").then(C.whitespace).then(t.Pin).map((t=>e=>{let n=o.objectGet(e,["CustomProperties"],[]);n.push(t),o.objectSet(e,["CustomProperties"],n,!0)}));Object=t=>C.seqMap(C.seq(C.string("Begin"),C.whitespace,C.string("Object"),C.whitespace),C.alt(t.CustomProperties,D.CreateAttributeGrammar(t,t.AttributeName,(t=>o.objectGet(L.attributes,t)))).sepBy1(C.whitespace),C.seq(t.WhitespaceNewline,C.string("End"),C.whitespace,C.string("Object")),((t,e,n)=>{let r=new L;return e.forEach((t=>t(r))),r}));MultipleObject=t=>t.Object.sepBy1(C.whitespace).trim(C.optWhitespace)}class G{static grammar=z.createLanguage(new D);constructor(t,e,n,r,i,s){this.entityType=t,this.prefix=e??"",this.separator=n??",",this.trailingSeparator=r??!1,this.attributeValueConjunctionSign=i??"=",this.attributeKeyPrinter=s??(t=>t.join("."))}writeValue(t){if(null===t)return"()";const e=t=>M.getSerializer(o.getType(t)).write(t);switch(t?.constructor){case Function:return this.writeValue(t());case Boolean:return o.FirstCapital(t.toString());case Number:return t.toString();case String:return`"${t}"`}return t instanceof Array?`(${t.map((t=>e(t)+","))})`:t instanceof m?e(t):void 0}subWrite(t,e){let n="",r=t.concat("");const i=r.length-1;for(const t in e){r[i]=t;const s=e[t];e[t]?.constructor===Object?n+=(n.length?this.separator:"")+this.subWrite(r,s):this.showProperty(r,s)&&(n+=(n.length?this.separator:"")+this.prefix+this.attributeKeyPrinter(r)+this.attributeValueConjunctionSign+this.writeValue(s))}return this.trailingSeparator&&n.length&&0===r.length&&(n+=this.separator),n}showProperty(t,e){const n=this.entityType.attributes,r=o.objectGet(n,t);return!(r instanceof s)||(!o.equals(r.value,e)||r.showDefault)}}class F extends G{constructor(){super(L," ","\n",!1)}showProperty(t,e){switch(t.toString()){case"Class":case"Name":case"CustomProperties":return!1}return super.showProperty(t,e)}read(t){const e=G.grammar.Object.parse(t);return e.status?e.value:(console.error("Error when trying to parse the object."),e)}readMultiple(t){const e=G.grammar.MultipleObject.parse(t);return e.status?e.value:(console.error("Error when trying to parse the object."),e)}write(t){return`Begin Object Class=${this.writeValue(t.Class)} Name=${this.writeValue(t.Name)}\n${this.subWrite([],t)+t.CustomProperties.map((t=>this.separator+this.prefix+"CustomProperties "+M.getSerializer(E).write(t))).join("")}\nEnd Object`}}class H extends i{constructor(t,e,n={}){n.wantsFocusCallback=!0,super(t,e,n),this.serializer=new F;let r=this;this.pasteHandle=t=>r.pasted(t.clipboardData.getData("Text"))}blueprintFocused(){document.body.addEventListener("paste",this.pasteHandle)}blueprintUnfocused(){document.body.removeEventListener("paste",this.pasteHandle)}pasted(t){let e=Number.MAX_SAFE_INTEGER,n=Number.MAX_SAFE_INTEGER,r=this.serializer.readMultiple(t).map((t=>{let r=new A(t);return e=Math.min(e,r.location[1]),n=Math.min(n,r.location[0]),r}));r.length>0&&this.blueprint.unselectAll();let i=this.blueprint.entity.mousePosition;this.blueprint.addNode(...r),r.forEach((t=>{const r=[i[0]-n,i[1]-e];t.addLocation(r),t.setSelected(!0)}))}}class V extends u{constructor(t,e,n){super(t,e,n),this.selectorElement=this.blueprint.selectorElement}startDrag(){this.selectorElement.startSelecting(this.clickedPosition)}dragTo(t,e){this.selectorElement.doSelecting(t)}endDrag(){this.started?this.selectorElement.finishSelecting():this.blueprint.unselectAll()}}class W extends i{constructor(t,e,n={}){n.wantsFocusCallback=!0,super(t,e,n);let r=this;this.clickHandler=t=>r.clickedSomewhere(t),this.blueprint.focuse&&document.addEventListener("click",this.clickHandler)}clickedSomewhere(t){t.target.closest("u-blueprint")||this.blueprint.setFocused(!1)}blueprintFocused(){document.addEventListener("click",this.clickHandler)}blueprintUnfocused(){document.removeEventListener("click",this.clickHandler)}}class _ extends a{constructor(t,e,n){n.wantsFocusCallback=!0,super(t,e,n),this.looseTarget=n?.looseTarget??!0;let r=this;this.mouseWheelHandler=t=>{t.preventDefault();const e=r.getLocation(t);r.wheel(Math.sign(t.deltaY),e)},this.mouseParentWheelHandler=t=>t.preventDefault(),this.blueprint.focused&&this.movementSpace.addEventListener("wheel",this.mouseWheelHandler,!1)}blueprintFocused(){this.movementSpace.addEventListener("wheel",this.mouseWheelHandler,!1),this.movementSpace.parentElement?.addEventListener("wheel",this.mouseParentWheelHandler)}blueprintUnfocused(){this.movementSpace.removeEventListener("wheel",this.mouseWheelHandler,!1),this.movementSpace.parentElement?.removeEventListener("wheel",this.mouseParentWheelHandler)}wheel(t,e){}}class I extends _{wheel(t,e){let n=this.blueprint.getZoom();n-=t,this.blueprint.setZoom(n,e)}}class q extends i{constructor(t,e,n={}){n.wantsFocusCallback=!0,super(t,e,n),this.serializer=new F;let r=this;this.copyHandle=t=>r.copied()}blueprintFocused(){document.body.addEventListener("copy",this.copyHandle)}blueprintUnfocused(){document.body.removeEventListener("copy",this.copyHandle)}copied(){const t=this.blueprint.getNodes(!0).map((t=>this.serializer.write(t.entity))).join("\n");navigator.clipboard.writeText(t)}}class $ extends c{constructor(){super(new t,new r),this.gridElement=null,this.viewportElement=null,this.overlayElement=null,this.selectorElement=null,this.nodesContainerElement=null,this.dragObject=null,this.selectObject=null,this.zoom=0,this.headerElement=null,this.focused=!1,this.nodeBoundariesSupplier=t=>{let e=t.getBoundingClientRect(),n=this.nodesContainerElement.getBoundingClientRect();const r=1/this.getScale();return{primaryInf:(e.left-n.left)*r,primarySup:(e.right-n.right)*r,secondaryInf:(e.top-n.top)*r,secondarySup:(e.bottom-n.bottom)*r}},this.nodeSelectToggleFunction=(t,e)=>{t.setSelected(e)}}connectedCallback(){super.connectedCallback(),this.classList.add("ueb",`ueb-zoom-${this.zoom}`),this.headerElement=this.querySelector(".ueb-viewport-header"),console.assert(this.headerElement,"Header element not provided by the template."),this.overlayElement=this.querySelector(".ueb-viewport-overlay"),console.assert(this.overlayElement,"Overlay element not provided by the template."),this.viewportElement=this.querySelector(".ueb-viewport-body"),console.assert(this.viewportElement,"Viewport element not provided by the template."),this.gridElement=this.viewportElement.querySelector(".ueb-grid"),console.assert(this.gridElement,"Grid element not provided by the template."),this.selectorElement=new p,this.nodesContainerElement=this.querySelector("[data-nodes]"),console.assert(this.nodesContainerElement,"Nodes container element not provided by the template."),this.nodesContainerElement.append(this.selectorElement),this.querySelector("[data-nodes]").append(...this.entity.nodes),this.copyObject=new q(this.getGridDOMElement(),this),this.pasteObject=new H(this.getGridDOMElement(),this),this.dragObject=new l(this.getGridDOMElement(),this,{clickButton:2,moveEverywhere:!0,exitAnyButton:!1}),this.zoomObject=new I(this.getGridDOMElement(),this,{looseTarget:!0}),this.selectObject=new V(this.getGridDOMElement(),this,{clickButton:0,moveEverywhere:!0,exitAnyButton:!0}),this.unfocusObject=new W(this.getGridDOMElement(),this),this.mouseTrackingObject=new f(this.getGridDOMElement(),this)}getGridDOMElement(){return this.gridElement}disconnectedCallback(){super.disconnectedCallback(),setSelected(!1),this.dragObject.unlistenDOMElement(),this.selectObject.unlistenDOMElement(),this.pasteObject.unlistenDOMElement()}getScroll(){return[this.viewportElement.scrollLeft,this.viewportElement.scrollTop]}setScroll(t,e=!1){this.scroll=t,e?this.viewportElement.scroll({left:t[0],top:t[1],behavior:"smooth"}):this.viewportElement.scroll(t[0],t[1])}scrollDelta(t,e=!1){const n=this.getScrollMax();let r=this.getScroll(),i=[r[0]+t[0],r[1]+t[1]],s=[0,0];for(let e=0;e<2;++e)t[e]<0&&i[e]<.25*this.entity.expandGridSize?(s[e]=i[e],s[e]>0&&(s[e]=-this.entity.expandGridSize)):t[e]>0&&i[e]>n[e]-.25*this.entity.expandGridSize&&(s[e]=i[e]-n[e],s[e]<0&&(s[e]=this.entity.expandGridSize));0==s[0]&&0==s[1]||(this.seamlessExpand(this.progressiveSnapToGrid(s[0]),this.progressiveSnapToGrid(s[1])),r=this.getScroll(),i=[r[0]+t[0],r[1]+t[1]]),this.setScroll(i,e)}scrollCenter(){const t=this.getScroll(),e=[this.entity.translateValue[0]-t[0],this.entity.translateValue[1]-t[1]],n=this.getViewportSize().map((t=>t/2)),r=[e[0]-n[0],e[1]-n[1]];this.scrollDelta(r,!0)}getExpandGridSize(){return this.entity.expandGridSize}getViewportSize(){return[this.viewportElement.clientWidth,this.viewportElement.clientHeight]}getScrollMax(){return[this.viewportElement.scrollWidth-this.viewportElement.clientWidth,this.viewportElement.scrollHeight-this.viewportElement.clientHeight]}_expand(t,e){t=Math.round(Math.abs(t)),e=Math.round(Math.abs(e)),this.entity.additional=[this.entity.additional[0]+t,this.entity.additional[1]+e],this.gridElement&&(this.gridElement.style.setProperty("--ueb-additional-x",this.entity.additional[0]),this.gridElement.style.setProperty("--ueb-additional-y",this.entity.additional[1]))}_translate(t,e){t=Math.round(t),e=Math.round(e),this.entity.translateValue=[this.entity.translateValue[0]+t,this.entity.translateValue[1]+e],this.gridElement&&(this.gridElement.style.setProperty("--ueb-translate-x",this.entity.translateValue[0]),this.gridElement.style.setProperty("--ueb-translate-y",this.entity.translateValue[1]))}seamlessExpand(t,e){let n=this.getScale(),r=t/n,i=e/n;this._expand(r,i),this._translate(r<0?-r:0,i<0?-i:0),t<0&&(this.viewportElement.scrollLeft-=t),e<0&&(this.viewportElement.scrollTop-=e)}progressiveSnapToGrid(t){return this.entity.expandGridSize*Math.round(t/this.entity.expandGridSize+.5*Math.sign(t))}getZoom(){return this.zoom}setZoom(t,e){if((t=o.clamp(t,-12,0))==this.zoom)return;let n=this.getScale();if(this.classList.remove(`ueb-zoom-${this.zoom}`),this.classList.add(`ueb-zoom-${t}`),this.zoom=t,e){let t=this.getScale()/n,r=[t*e[0],t*e[1]];this.scrollDelta([(r[0]-e[0])*n,(r[1]-e[1])*n])}}getScale(){return parseFloat(getComputedStyle(this.gridElement).getPropertyValue("--ueb-scale"))}compensateTranslation(t){return t[0]-=this.entity.translateValue[0],t[1]-=this.entity.translateValue[1],t}getNodes(t=!1){return t?this.entity.nodes.filter((t=>t.selected)):this.entity.nodes}unselectAll(){this.entity.nodes.forEach((t=>this.nodeSelectToggleFunction(t,!1)))}addNode(...t){[...t].reduce(((t,e)=>(t.push(e),t)),this.entity.nodes),this.nodesContainerElement&&this.nodesContainerElement.append(...t)}setFocused(t=!0){if(this.focused==t)return;let e=new CustomEvent(t?"blueprintfocus":"blueprintunfocus");this.focused=t,this.dataset.focused=this.focused,this.focused||this.unselectAll(),this.dispatchEvent(e)}}customElements.define("u-blueprint",$);class R extends c{constructor(t,e){super(),this.source=t,this.destination=e}render(){return'\n \n \n \n '}}customElements.define("u-link",R);class U extends G{constructor(t,e,n,r,i,s,o){super(e,n,r,i,s,o),this.wrap=t??(t=>`(${t})`)}read(t){const e=D.getGrammarForType(G.grammar,this.entityType).parse(t);return e.status?e.value:(console.error("Error when trying to parse the entity "+this.entityType.prototype.constructor.name),e)}write(t){return this.wrap(this.subWrite([],t))}}class X extends U{constructor(t,e){super(void 0,e),this.objectWriter=t}write(t){return this.objectWriter(t)}}class Z extends U{constructor(t){super(void 0,t)}write(t){return t.toString()}}M.registerSerializer(L,new F),M.registerSerializer(E,new U((t=>`Pin (${t})`),E,"",",",!0)),M.registerSerializer(P,new U((t=>`(${t})`),P,"",",",!1)),M.registerSerializer(b,new U((t=>`NSLOCTEXT(${t})`),b,"",",",!1,"",(t=>""))),M.registerSerializer(w,new U((t=>t),w,""," ",!1,"",(t=>""))),M.registerSerializer(y,new X((t=>(t.type??"")+(t.path?t.type?`'"${t.path}"'`:t.path:"")))),M.registerSerializer(v,new Z(v)),M.registerSerializer(g,new Z(g)),M.registerSerializer(x,new Z(x));export{$ as Blueprint,R as GraphLink,A as GraphNode}; diff --git a/js/entity/IntegerEntity.js b/js/entity/IntegerEntity.js index a57a334..a16a5fe 100755 --- a/js/entity/IntegerEntity.js +++ b/js/entity/IntegerEntity.js @@ -10,9 +10,9 @@ export default class IntegerEntity extends Entity { return IntegerEntity.attributes } - constructor(options = { value: 0 }) { - options.value = Math.round(options.value) + constructor(options = {}) { super(options) + this.value = Math.round(this.value) } valueOf() { diff --git a/js/entity/NaturalNumberEntity.js b/js/entity/NaturalNumberEntity.js new file mode 100755 index 0000000..9221f3b --- /dev/null +++ b/js/entity/NaturalNumberEntity.js @@ -0,0 +1,10 @@ +import IntegerEntity from "./IntegerEntity" +import Utility from "../Utility" + +export default class NaturalNumberEntity extends IntegerEntity { + + constructor(options = {}) { + super(options) + this.value = Math.round(Utility.clamp(this.value, 0)) + } +} diff --git a/js/input/Context.js b/js/input/Context.js index 817a5dc..c7fc7f5 100644 --- a/js/input/Context.js +++ b/js/input/Context.js @@ -20,6 +20,8 @@ export default class Context { this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler) } + + /* Subclasses will probabily override the following methods */ blueprintFocused() { } diff --git a/js/template/BlueprintTemplate.js b/js/template/BlueprintTemplate.js index 5dd2fb4..737927b 100755 --- a/js/template/BlueprintTemplate.js +++ b/js/template/BlueprintTemplate.js @@ -1,8 +1,9 @@ +import html from "./html" import Template from "./Template" export default class BlueprintTemplate extends Template { header(element) { - return ` + return html`
1:1
@@ -10,7 +11,7 @@ export default class BlueprintTemplate extends Template { } overlay() { - return ` + return html`
` } @@ -21,7 +22,7 @@ export default class BlueprintTemplate extends Template { * @returns */ viewport(element) { - return ` + return html`
@@ -37,7 +38,7 @@ export default class BlueprintTemplate extends Template { * @returns The computed html */ render(element) { - return ` + return html` ${this.header(element)} ${this.overlay(element)} ${this.viewport(element)} diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js index fb5654a..f4671fd 100755 --- a/js/template/NodeTemplate.js +++ b/js/template/NodeTemplate.js @@ -1,3 +1,4 @@ +import html from "./html" import PinEntity from "../entity/PinEntity" import Template from "./Template" @@ -12,7 +13,7 @@ export default class NodeTemplate extends Template { * @returns The computed html */ header(entity) { - return ` + return html`
@@ -32,10 +33,10 @@ export default class NodeTemplate extends Template { let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity) let outputs = inputs.filter(v => v.isOutput()) inputs = inputs.filter(v => !v.isOutput()) - return ` + return html`
- ${inputs.map((input, index) => ` + ${inputs.map((input, index) => html`
${input.getPinDisplayName()} @@ -43,7 +44,7 @@ export default class NodeTemplate extends Template { `).join("") ?? ""}
- ${outputs.map((output, index) => ` + ${outputs.map((output, index) => html`
${output.getPinDisplayName()} @@ -60,7 +61,7 @@ export default class NodeTemplate extends Template { * @returns The computed html */ render(entity) { - return ` + return html`
${this.header(entity)} diff --git a/js/template/html.js b/js/template/html.js new file mode 100644 index 0000000..73b15be --- /dev/null +++ b/js/template/html.js @@ -0,0 +1,2 @@ +const html = String.raw +export default html diff --git a/package.json b/package.json index fa474f7..a20bcb6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@rollup/plugin-commonjs": "^21.0.0", "@rollup/plugin-node-resolve": "^13.0.5", "rollup": "^2.58.0", + "rollup-plugin-minify-html-template-literals": "^1.2.0", "rollup-plugin-terser": "^7.0.2", "terser": "^5.9.0" }, diff --git a/rollup.config.js b/rollup.config.js index b71ff3b..f4848e2 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,5 @@ import { nodeResolve } from '@rollup/plugin-node-resolve' +import minifyHTML from 'rollup-plugin-minify-html-template-literals' import commonjs from '@rollup/plugin-commonjs' import { terser } from 'rollup-plugin-terser' @@ -10,7 +11,8 @@ export default { }, plugins: [ nodeResolve({ browser: true }), + minifyHTML(), commonjs(), - //terser() + terser() ] } \ No newline at end of file