diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 984280f..95761ce 100644 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -1,14 +1,3 @@ -class TypeInitialization { - constructor(value, showDefault = true, type = Utility.getType(value)) { - if (type.prototype.constructor.name != value.constructor.name) { - throw new Error("Default value expected to be of the same 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) @@ -107,1163 +96,14 @@ class Utility { } } -class Pointing { - - constructor(target, blueprint, options) { - /** @type {HTMLElement} */ - this.target = target; - /** @type {import("../Blueprint").Blueprint}" */ - this.blueprint = blueprint; - this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement; - } - - 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 +class TypeInitialization { + constructor(value, showDefault = true, type = Utility.getType(value)) { + this.value = value; + this.showDefault = showDefault; + this.type = type; } } -/** - * 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 = function (e) { - 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 = function (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 = function (e) { - e.preventDefault(); - e.stopPropagation(); - const location = self.getLocation(e); - const movement = [e.movementX, e.movementY]; - self.dragTo(location, movement); - }; - - this.mouseUpHandler = function (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() { - this.target.removeEventListener('mousedown', this.mouseDownHandler); - if (this.clickButton == 2) { - this.target.removeEventListener('contextmenu', this.preventDefault); - } - } - - /* 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 Select extends MouseClickDrag { - - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.stepSize = options?.stepSize; - this.mousePosition = [0, 0]; - 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 MouseWheel extends Pointing { - - /** - * - * @param {HTMLElement} target - * @param {import("../Blueprint").Blueprint} blueprint - * @param {Object} options - */ - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.looseTarget = options?.looseTarget ?? true; - let self = this; - - this.mouseWheelHandler = function (e) { - e.preventDefault(); - const location = self.getLocation(e); - self.wheel(Math.sign(e.deltaY), location); - }; - - this.movementSpace.addEventListener('wheel', this.mouseWheelHandler, false); - // Prevent movement space from being scrolled - this.movementSpace.parentElement?.addEventListener('wheel', e => e.preventDefault()); - } - - /* 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); - } -} - -/** - * A Graph Entity is an element that can stay directly (as a first child) on the blueprint grid. Those entities are either nodes or links - */ -class GraphEntity extends HTMLElement { - /** - * - * @param {import("../template/Template").default} template The template to render this node - */ - constructor(template) { - super(); - /** @type {import("../Blueprint").Blueprint}" */ - this.blueprint = null; - this.template = template; - } - - connectedCallback() { - this.blueprint = this.closest('u-blueprint'); - this.append(...this.template.getElements(this)); - } - - // Subclasses want to rewrite this - render() { - return '' - } -} - -/** - * @typedef {import("../graph/GraphNode").default} GraphNode - */ -class Template { - - /** - * Computes the html content of the target element. - * @param {GraphNode} element Target element - * @returns The computed html - */ - render(element) { - return `` - } - - /** - * Returns the html elements rendered by this template. - * @param {GraphNode} element Target element - * @returns The rendered elements - */ - getElements(element) { - let aDiv = document.createElement('div'); - aDiv.innerHTML = this.render(element); - 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 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 GraphEntity { - - 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.nodes, 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); - -/** - * @typedef {import("./graph/GraphNode").default} GraphNode - */ -class Blueprint extends GraphEntity { - - insertChildren() { - this.querySelector('[data-nodes]').append(...this.nodes); - } - - constructor() { - super(new BlueprintTemplate()); - /** @type {GraphNode[]}" */ - this.nodes = new Array(); - this.expandGridSize = 400; - /** @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 {Array} */ - this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]; - /** @type {Array} */ - this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]; - /** @type {number} */ - this.zoom = 0; - /** @type {HTMLElement} */ - this.headerElement = null; - /** @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.insertChildren(); - - 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 - }); - } - - getGridDOMElement() { - return this.gridElement - } - - disconnectedCallback() { - super.disconnectedCallback(); - this.dragObject.unlistenDOMElement(); - this.selectObject.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.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.expandGridSize; - } - } else if (delta[i] > 0 && finalScroll[i] > scrollMax[i] - 0.25 * this.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.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.translateValue[0] - scroll[0], - this.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.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.additional = [this.additional[0] + x, this.additional[1] + y]; - if (this.gridElement) { - this.gridElement.style.setProperty('--ueb-additional-x', this.additional[0]); - this.gridElement.style.setProperty('--ueb-additional-y', this.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.translateValue = [this.translateValue[0] + x, this.translateValue[1] + y]; - if (this.gridElement) { - this.gridElement.style.setProperty('--ueb-translate-x', this.translateValue[0]); - this.gridElement.style.setProperty('--ueb-translate-y', this.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.expandGridSize * Math.round(x / this.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.translateValue[0]; - position[1] -= this.translateValue[1]; - return position - } - - /** - * Unselect all nodes - */ - unselectAll() { - this.nodes.forEach(node => this.nodeSelectToggleFunction(node, false)); - } - - /** - * - * @param {...GraphNode} graphNodes - */ - addNode(...graphNodes) { - [...graphNodes].reduce( - (s, e) => { - s.push(e); - return s - }, - this.nodes); - if (this.nodesContainerElement) { - this.nodesContainerElement.append(...graphNodes); - } - } -} - -customElements.define('u-blueprint', Blueprint); - -class Drag extends MouseClickDrag { - constructor(target, blueprint, options) { - super(target, blueprint, options); - this.stepSize = parseInt(options?.stepSize); - this.mousePosition = [0, 0]; - } - - snapToGrid(location) { - return [ - this.stepSize * Math.round(location[0] / this.stepSize), - this.stepSize * Math.round(location[1] / this.stepSize) - ] - } - - startDrag() { - if (isNaN(this.stepSize) || this.stepSize <= 0) { - this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap')); - if (isNaN(this.stepSize) || this.stepSize <= 0) { - this.stepSize = 1; - } - } - // Get the current mouse position - this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition; - } - - dragTo(location, movement) { - const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location; - const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]]; - - if (d[0] == 0 && d[1] == 0) { - return - } - - this.target.dragDispatch(d); - - // Reassign the position of mouse - this.mousePosition = mousePosition; - } -} - -class SelectableDraggable extends GraphEntity { - - constructor(template) { - super(template); - this.dragObject = null; - this.location = [0, 0]; - this.selected = false; - - let self = this; - this.dragHandler = (e) => { - self.addLocation(e.detail.value); - }; - } - - connectedCallback() { - super.connectedCallback(); - this.dragObject = new Drag(this, null, { // UDrag doesn't need blueprint - looseTarget: true - }); - } - - disconnectedCallback() { - this.dragObject.unlistenDOMElement(); - } - - setLocation(value = [0, 0]) { - this.location = value; - this.style.setProperty('--ueb-position-x', this.location[0]); - this.style.setProperty('--ueb-position-y', this.location[1]); - } - - addLocation(value) { - this.setLocation([this.location[0] + value[0], this.location[1] + value[1]]); - } - - dragDispatch(value) { - if (!this.selected) { - this.blueprint.unselectAll(); - this.setSelected(true); - } - let dragEvent = new CustomEvent('uDragSelected', { - detail: { - instigator: this, - value: value - }, - bubbles: false, - cancelable: true, - composed: false, - }); - this.blueprint.dispatchEvent(dragEvent); - } - - setSelected(value = true) { - if (this.selected == value) { - return - } - this.selected = value; - if (this.selected) { - this.classList.add('ueb-selected'); - this.blueprint.addEventListener('uDragSelected', this.dragHandler); - } else { - this.classList.remove('ueb-selected'); - this.blueprint.removeEventListener('uDragSelected', this.dragHandler); - } - } - -} - -class NodeTemplate extends Template { - - /** - * Computes the html content of the target element. - * @param {HTMLElement} element Target element - * @returns The computed html - */ - header(element) { - return ` -
- - - ${element.graphNodeName} - -
- ` - } - - /** - * Computes the html content of the target element. - * @param {HTMLElement} element Target element - * @returns The computed html - */ - body(element) { - return ` -
-
- ${element.inputs.forEach((input, index) => ` -
- - ${input.name} -
- `) ?? ''} -
-
- ${element.outputs.forEach((output, index) => ` -
- ${output.name} - -
- `) ?? ''} -
-
- ` - } - - /** - * Computes the html content of the target element. - * @param {HTMLElement} element Target element - * @returns The computed html - */ - render(element) { - return ` -
-
- ${this.header(element)} - ${this.body(element)} -
-
- ` - } -} - -class GraphNode extends SelectableDraggable { - - constructor() { - super(new NodeTemplate()); - this.graphNodeName = 'n/a'; - this.inputs = []; - this.outputs = []; - } - - connectedCallback() { - this.getAttribute('type')?.trim(); - super.connectedCallback(); - this.classList.add('ueb-node'); - if (this.selected) { - this.classList.add('ueb-selected'); - } - this.style.setProperty('--ueb-position-x', this.location[0]); - this.style.setProperty('--ueb-position-y', this.location[1]); - } -} - -customElements.define('u-node', GraphNode); - class Entity { constructor(options = {}) { /** @@ -1296,6 +136,12 @@ class Entity { continue } let defaultValue = properties[property]; + if (defaultValue instanceof TypeInitialization) { + if (!defaultValue.showDefault) { + continue + } + defaultValue = defaultValue.value; + } if (defaultValue instanceof Array) { propertySetter(target, property, []); defineAllAttributes( @@ -1305,12 +151,6 @@ class Entity { (t, _, v) => t.push(v)); continue } - if (defaultValue instanceof TypeInitialization) { - if (!defaultValue.showDefault) { - continue - } - defaultValue = defaultValue.value; - } if (defaultValue instanceof Function) { defaultValue = Utility.sanitize(new defaultValue()); } @@ -1321,7 +161,12 @@ class Entity { } } -class Guid { +class GuidEntity extends Entity { + + static attributes = { + value: String + } + static generateGuid(random) { let values = new Uint32Array(4); if (random === true) { @@ -1335,23 +180,37 @@ class Guid { } constructor(guid) { - switch (guid?.constructor) { - case String: - this.value = guid; - break - case Guid: - this.value = guid.value; - break - default: - this.value = Guid.generateGuid(guid === true); + if (guid?.constructor === String) { + guid = { + value: guid + }; + } else if (guid?.constructor === Boolean) { + guid = { + value: GuidEntity.generateGuid(guid == true) + }; } + super(guid); } - toString() { - return this.value + getAttributes() { + return GuidEntity.attributes } } +class LocalizedTextEntity extends Entity { + + static attributes = { + namespace: "", + key: "", + value: "" + } + + getAttributes() { + return LocalizedTextEntity.attributes + } + +} + class ObjectReferenceEntity extends Entity { static attributes = { @@ -1372,23 +231,22 @@ class ObjectReferenceEntity extends Entity { } } -class LocalizedTextEntity extends Entity { +class PinReferenceEntity extends Entity { static attributes = { - namespace: "", - key: "", - value: "" + objectName: String, + pinGuid: GuidEntity } getAttributes() { - return LocalizedTextEntity.attributes + return PinReferenceEntity.attributes } - } class PinEntity$1 extends Entity { + static attributes = { - PinId: Guid, + PinId: GuidEntity, PinName: "", PinFriendlyName: new TypeInitialization(new LocalizedTextEntity(), false), PinToolTip: "", @@ -1405,10 +263,10 @@ class PinEntity$1 extends Entity { bIsWeakPointer: false, bIsUObjectWrapper: false }, - LinkedTo: Guid, + LinkedTo: [new TypeInitialization(null, false, PinReferenceEntity)], DefaultValue: "", AutogeneratedDefaultValue: "", - PersistentGuid: Guid, + PersistentGuid: GuidEntity, bHidden: false, bNotConnectable: false, bDefaultValueIsReadOnly: false, @@ -1420,22 +278,14 @@ class PinEntity$1 extends Entity { getAttributes() { return PinEntity$1.attributes } + + isOutput() { + if (this.Direction === "EGPD_Output") { + return true + } + } } -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); - class FunctionReferenceEntity extends Entity { static attributes = { MemberParent: ObjectReferenceEntity, @@ -1470,10 +320,25 @@ class Integer extends Entity { } } +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); + class VariableReferenceEntity extends Entity { + static attributes = { - MemberName: "", - MemberGuid: Guid, + MemberName: String, + MemberGuid: GuidEntity, bSelfContext: true } @@ -1493,7 +358,7 @@ class ObjectEntity extends Entity { TargetType: new TypeInitialization(new ObjectReferenceEntity(), false), NodePosX: Integer, NodePosY: Integer, - NodeGuid: Guid, + NodeGuid: GuidEntity, CustomProperties: [PinEntity$1] } @@ -1516,8 +381,9 @@ class Grammar { Integer = _ => P.regex(/[0-9]+/).map(v => new Integer(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}/).desc("32 digit hexadecimal (accepts all the letters for safety) value") - ReferencePath = _ => P.seq(P.string("/"), P.regex(/[0-9a-zA-Z_]+/).sepBy1(P.string(".")).tieWith(".")) + 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") + PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) + ReferencePath = _ => P.seq(P.string("/"), r.PathSymbol.sepBy1(P.string(".")).tieWith(".")) .tie() .atLeast(2) .tie() @@ -1543,11 +409,11 @@ class Grammar { 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), + r.String.trim(P.optWhitespace), // namespace P.string(","), - r.String.trim(P.optWhitespace), + r.String.trim(P.optWhitespace), // key P.string(","), - r.String.trim(P.optWhitespace), + r.String.trim(P.optWhitespace), // value P.string(")"), (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ namespace: namespace, @@ -1555,8 +421,17 @@ class Grammar { value: value }) ) - static getGrammarForType(r, type, defaultGrammar) { - switch (type) { + PinReference = r => P.seqMap( + r.PathSymbol, + 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: @@ -1565,16 +440,34 @@ class Grammar { return r.Integer case String: return r.String - case Guid: + 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$1: 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 } @@ -1585,26 +478,10 @@ class Grammar { .chain(attributeName => { const attributeKey = attributeName.split("."); const attribute = attributeSupplier(attributeKey); - const type = Utility.getType(attribute); - let attributeValueGrammar = type === Array - ? attribute - .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) - .reduce((accum, cur) => - !cur || accum === r.AttributeAnyValue - ? r.AttributeAnyValue - : accum.or(cur) - ) - : Grammar.getGrammarForType(r, type, r.AttributeAnyValue); - // After the attribute name (already parsed at this point, we continue with an equal sign (possibly surrounded by whitespace) then the expected attribute value) - return attributeValueGrammar.map(attributeValue => type === Array - ? entity => { - /** @type {Array} */ - let array = Utility.objectGet(entity, attributeKey, []); - array.push(attributeValue); - return Utility.objectSet(entity, attributeKey, array, true) - } - : entity => Utility.objectSet(entity, attributeKey, attributeValue, true) - ) // returns attributeSetter + 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) => @@ -1650,6 +527,7 @@ class Grammar { return result } ) + MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) } class Serializer { @@ -1666,7 +544,7 @@ class Serializer { case Boolean: return Utility.FirstCapital(value.toString()) case ObjectReferenceEntity: - case Guid: + case GuidEntity: return value.toString() case String: return `"${value}"` @@ -1748,35 +626,4 @@ class PinSerializer extends Serializer { } } -class ObjectSerializer extends Serializer { - - showProperty(attributeKey, attributeValue) { - switch (attributeKey.toString()) { - case "Class": - case "Name": - // 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 - } - - write(object) { - let result = ` -Begin Object Class=${object.Class} Name=${object.Name} -${this.subWrite([], object, "\n", " ")} -End Object -`; - return result - } -} - -export { Grammar, GraphNode, ObjectReferenceEntity, ObjectSerializer, PinEntity$1 as PinEntity, PinSerializer, Blueprint as UEBlueprint }; +export { PinSerializer }; diff --git a/js/Guid.js b/js/Guid.js deleted file mode 100644 index afe5f66..0000000 --- a/js/Guid.js +++ /dev/null @@ -1,30 +0,0 @@ -export default class Guid { - static generateGuid(random) { - let values = new Uint32Array(4); - if (random === true) { - crypto.getRandomValues(values) - } - let result = "" - values.forEach(n => { - result += ('00000000' + n.toString(16).toUpperCase()).slice(-8) - }) - return result - } - - constructor(guid) { - switch (guid?.constructor) { - case String: - this.value = guid - break - case Guid: - this.value = guid.value - break - default: - this.value = Guid.generateGuid(guid === true) - } - } - - toString() { - return this.value - } -} \ No newline at end of file diff --git a/js/entity/Entity.js b/js/entity/Entity.js index c5e3d6f..346c8d4 100644 --- a/js/entity/Entity.js +++ b/js/entity/Entity.js @@ -33,6 +33,12 @@ export default class Entity { continue } let defaultValue = properties[property] + if (defaultValue instanceof TypeInitialization) { + if (!defaultValue.showDefault) { + continue + } + defaultValue = defaultValue.value + } if (defaultValue instanceof Array) { propertySetter(target, property, []) defineAllAttributes( @@ -42,12 +48,6 @@ export default class Entity { (t, _, v) => t.push(v)) continue } - if (defaultValue instanceof TypeInitialization) { - if (!defaultValue.showDefault) { - continue - } - defaultValue = defaultValue.value - } if (defaultValue instanceof Function) { defaultValue = Utility.sanitize(new defaultValue()) } diff --git a/js/entity/GuidEntity.js b/js/entity/GuidEntity.js new file mode 100644 index 0000000..cb80a5d --- /dev/null +++ b/js/entity/GuidEntity.js @@ -0,0 +1,37 @@ +import Entity from "./Entity"; + +export default class GuidEntity extends Entity { + + static attributes = { + value: String + } + + static generateGuid(random) { + let values = new Uint32Array(4); + if (random === true) { + crypto.getRandomValues(values) + } + let result = "" + values.forEach(n => { + result += ('00000000' + n.toString(16).toUpperCase()).slice(-8) + }) + return result + } + + constructor(guid) { + if (guid?.constructor === String) { + guid = { + value: guid + } + } else if (guid?.constructor === Boolean) { + guid = { + value: GuidEntity.generateGuid(guid == true) + } + } + super(guid) + } + + getAttributes() { + return GuidEntity.attributes + } +} \ No newline at end of file diff --git a/js/entity/ObjectEntity.js b/js/entity/ObjectEntity.js index 8348f1a..7f4308b 100644 --- a/js/entity/ObjectEntity.js +++ b/js/entity/ObjectEntity.js @@ -1,6 +1,6 @@ import Entity from "./Entity" import FunctionReferenceEntity from "./FunctionReferenceEntity" -import Guid from "../Guid" +import GuidEntity from "./GuidEntity" import Integer from "./Integer" import ObjectReferenceEntity from "./ObjectReferenceEntity" import PinEntity from "./PinEntity" @@ -18,7 +18,7 @@ export default class ObjectEntity extends Entity { TargetType: new TypeInitialization(new ObjectReferenceEntity(), false), NodePosX: Integer, NodePosY: Integer, - NodeGuid: Guid, + NodeGuid: GuidEntity, CustomProperties: [PinEntity] } diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js index 169ae2a..a786334 100644 --- a/js/entity/PinEntity.js +++ b/js/entity/PinEntity.js @@ -1,12 +1,14 @@ -import Entity from "./Entity"; -import Guid from "../Guid"; -import ObjectReferenceEntity from "./ObjectReferenceEntity"; -import TypeInitialization from "./TypeInitialization"; -import LocalizedTextEntity from "./LocalizedTextEntity"; +import Entity from "./Entity" +import GuidEntity from "./GuidEntity" +import LocalizedTextEntity from "./LocalizedTextEntity" +import ObjectReferenceEntity from "./ObjectReferenceEntity" +import TypeInitialization from "./TypeInitialization" +import PinReferenceEntity from "./PinReferenceEntity" export default class PinEntity extends Entity { + static attributes = { - PinId: Guid, + PinId: GuidEntity, PinName: "", PinFriendlyName: new TypeInitialization(new LocalizedTextEntity(), false), PinToolTip: "", @@ -23,10 +25,10 @@ export default class PinEntity extends Entity { bIsWeakPointer: false, bIsUObjectWrapper: false }, - LinkedTo: Guid, + LinkedTo: [new TypeInitialization(null, false, PinReferenceEntity)], DefaultValue: "", AutogeneratedDefaultValue: "", - PersistentGuid: Guid, + PersistentGuid: GuidEntity, bHidden: false, bNotConnectable: false, bDefaultValueIsReadOnly: false, @@ -38,4 +40,10 @@ export default class PinEntity extends Entity { getAttributes() { return PinEntity.attributes } -} \ No newline at end of file + + isOutput() { + if (this.Direction === "EGPD_Output") { + return true + } + } +} diff --git a/js/entity/PinReferenceEntity.js b/js/entity/PinReferenceEntity.js new file mode 100644 index 0000000..bb19642 --- /dev/null +++ b/js/entity/PinReferenceEntity.js @@ -0,0 +1,14 @@ +import Entity from "./Entity" +import GuidEntity from "./GuidEntity" + +export default class PinReferenceEntity extends Entity { + + static attributes = { + objectName: String, + pinGuid: GuidEntity + } + + getAttributes() { + return PinReferenceEntity.attributes + } +} \ No newline at end of file diff --git a/js/entity/TypeInitialization.js b/js/entity/TypeInitialization.js index bb35b99..2e539ab 100644 --- a/js/entity/TypeInitialization.js +++ b/js/entity/TypeInitialization.js @@ -2,9 +2,6 @@ import Utility from "../Utility" export default class TypeInitialization { constructor(value, showDefault = true, type = Utility.getType(value)) { - if (type.prototype.constructor.name != value.constructor.name) { - throw new Error("Default value expected to be of the same type.") - } this.value = value this.showDefault = showDefault this.type = type diff --git a/js/entity/VariableReferenceEntity.js b/js/entity/VariableReferenceEntity.js index 2f6ee09..da15cbf 100644 --- a/js/entity/VariableReferenceEntity.js +++ b/js/entity/VariableReferenceEntity.js @@ -1,11 +1,11 @@ -import Guid from "../Guid" +import GuidEntity from "./GuidEntity" import Entity from "./Entity" -import ObjectReferenceEntity from "./ObjectReferenceEntity" export default class VariableReferenceEntity extends Entity { + static attributes = { - MemberName: "", - MemberGuid: Guid, + MemberName: String, + MemberGuid: GuidEntity, bSelfContext: true } diff --git a/js/export.js b/js/export.js index e23b3e3..d0c7f90 100644 --- a/js/export.js +++ b/js/export.js @@ -1,9 +1,3 @@ -import Blueprint from "./Blueprint" -import GraphNode from "./graph/GraphNode" import PinSerializer from "./serialization/PinSerializer" -import PinEntity from "./entity/PinEntity" -import Grammar from "./serialization/Grammar" -import ObjectReferenceEntity from "./entity/ObjectReferenceEntity" -import ObjectSerializer from "./serialization/ObjectSerialize" -export { Blueprint as UEBlueprint, GraphNode as GraphNode, PinSerializer as PinSerializer, PinEntity as PinEntity, Grammar as Grammar, ObjectReferenceEntity as ObjectReferenceEntity, ObjectSerializer as ObjectSerializer } \ No newline at end of file +export { PinSerializer as PinSerializer } \ No newline at end of file diff --git a/js/graph/GraphEntity.js b/js/graph/GraphEntity.js index c2ed4c2..707cb8a 100644 --- a/js/graph/GraphEntity.js +++ b/js/graph/GraphEntity.js @@ -1,6 +1,3 @@ -/** - * A Graph Entity is an element that can stay directly (as a first child) on the blueprint grid. Those entities are either nodes or links - */ export default class GraphEntity extends HTMLElement { /** * diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js index cd0dfd0..8dd20e3 100644 --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -1,5 +1,5 @@ import FunctionReferenceEntity from "../entity/FunctionReferenceEntity" -import Guid from "../Guid" +import GuidEntity from "../entity/GuidEntity" import Integer from "../entity/Integer" import ObjectReferenceEntity from "../entity/ObjectReferenceEntity" import Parsimmon from "parsimmon" @@ -7,6 +7,7 @@ import PinEntity from "../entity/PinEntity" import Utility from "../Utility" import ObjectEntity from "../entity/ObjectEntity" import LocalizedTextEntity from "../entity/LocalizedTextEntity" +import PinReferenceEntity from "../entity/PinReferenceEntity" let P = Parsimmon @@ -22,8 +23,9 @@ export default class Grammar { Integer = _ => P.regex(/[0-9]+/).map(v => new Integer(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}/).desc("32 digit hexadecimal (accepts all the letters for safety) value") - ReferencePath = _ => P.seq(P.string("/"), P.regex(/[0-9a-zA-Z_]+/).sepBy1(P.string(".")).tieWith(".")) + 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") + PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) + ReferencePath = _ => P.seq(P.string("/"), r.PathSymbol.sepBy1(P.string(".")).tieWith(".")) .tie() .atLeast(2) .tie() @@ -49,11 +51,11 @@ export default class Grammar { 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), + r.String.trim(P.optWhitespace), // namespace P.string(","), - r.String.trim(P.optWhitespace), + r.String.trim(P.optWhitespace), // key P.string(","), - r.String.trim(P.optWhitespace), + r.String.trim(P.optWhitespace), // value P.string(")"), (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ namespace: namespace, @@ -61,8 +63,17 @@ export default class Grammar { value: value }) ) - static getGrammarForType(r, type, defaultGrammar) { - switch (type) { + PinReference = r => P.seqMap( + r.PathSymbol, + 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: @@ -71,16 +82,34 @@ export default class Grammar { return r.Integer case String: return r.String - case Guid: + 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 } @@ -91,26 +120,10 @@ export default class Grammar { .chain(attributeName => { const attributeKey = attributeName.split(".") const attribute = attributeSupplier(attributeKey) - const type = Utility.getType(attribute) - let attributeValueGrammar = type === Array - ? attribute - .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) - .reduce((accum, cur) => - !cur || accum === r.AttributeAnyValue - ? r.AttributeAnyValue - : accum.or(cur) - ) - : Grammar.getGrammarForType(r, type, r.AttributeAnyValue) - // After the attribute name (already parsed at this point, we continue with an equal sign (possibly surrounded by whitespace) then the expected attribute value) - return attributeValueGrammar.map(attributeValue => type === Array - ? entity => { - /** @type {Array} */ - let array = Utility.objectGet(entity, attributeKey, []) - array.push(attributeValue) - return Utility.objectSet(entity, attributeKey, array, true) - } - : entity => Utility.objectSet(entity, attributeKey, attributeValue, true) - ) // returns attributeSetter + 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) => @@ -156,4 +169,5 @@ export default class Grammar { return result } ) + MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) } \ No newline at end of file diff --git a/js/serialization/Serializer.js b/js/serialization/Serializer.js index e0018c3..e59b088 100644 --- a/js/serialization/Serializer.js +++ b/js/serialization/Serializer.js @@ -1,9 +1,9 @@ -import Parsimmon from "parsimmon" import Grammar from "./Grammar" -import Utility from "../Utility" -import TypeInitialization from "../entity/TypeInitialization" +import GuidEntity from "../entity/GuidEntity" import ObjectReferenceEntity from "../entity/ObjectReferenceEntity" -import Guid from "../Guid" +import Parsimmon from "parsimmon" +import TypeInitialization from "../entity/TypeInitialization" +import Utility from "../Utility" export default class Serializer { @@ -20,7 +20,7 @@ export default class Serializer { case Boolean: return Utility.FirstCapital(value.toString()) case ObjectReferenceEntity: - case Guid: + case GuidEntity: return value.toString() case String: return `"${value}"` diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js index fd325cb..96b4b2f 100644 --- a/js/template/NodeTemplate.js +++ b/js/template/NodeTemplate.js @@ -1,18 +1,19 @@ +import { PinEntity } from "../../dist/ueblueprint" import Template from "./Template" export default class NodeTemplate extends Template { /** * Computes the html content of the target element. - * @param {HTMLElement} element Target element + * @param {HTMLElement} entity Entity representing the element * @returns The computed html */ - header(element) { + header(entity) { return `
- ${element.graphNodeName} + ${entity.graphNodeName}
` @@ -20,27 +21,30 @@ export default class NodeTemplate extends Template { /** * Computes the html content of the target element. - * @param {HTMLElement} element Target element + * @param {import("../entity/ObjectEntity").default} entity Entity representing the element * @returns The computed html */ - body(element) { + body(entity) { + let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity) + let outputs = inputs.filter(v => v.isOutput()) + inputs = inputs.filter(v => !v.isOutput()) return `
- ${element.inputs.forEach((input, index) => ` + ${inputs.map((input, index) => `
- + ${input.name}
- `) ?? ''} + `).join("") ?? ""}
- ${element.outputs.forEach((output, index) => ` + ${outputs.map((output, index) => `
${output.name} - +
- `) ?? ''} + `).join("") ?? ''}
` @@ -48,15 +52,15 @@ export default class NodeTemplate extends Template { /** * Computes the html content of the target element. - * @param {HTMLElement} element Target element + * @param {HTMLElement} entity Entity representing the element * @returns The computed html */ - render(element) { + render(entity) { return `
- ${this.header(element)} - ${this.body(element)} + ${this.header(entity)} + ${this.body(entity)}
` diff --git a/js/template/Template.js b/js/template/Template.js index efcaedd..27b30d0 100644 --- a/js/template/Template.js +++ b/js/template/Template.js @@ -5,19 +5,19 @@ export default class Template { /** * Computes the html content of the target element. - * @param {GraphNode} element Target element + * @param {Entity} entity Entity representing the element * @returns The computed html */ - render(element) { + render(entity) { return `` } /** * Returns the html elements rendered by this template. - * @param {GraphNode} element Target element + * @param {GraphNode} entity Entity representing the element * @returns The rendered elements */ - getElements(element) { + getElements(entity) { let aDiv = document.createElement('div') aDiv.innerHTML = this.render(element) return aDiv.childNodes diff --git a/ueblueprint.html b/ueblueprint.html index 91b4142..e01184b 100644 --- a/ueblueprint.html +++ b/ueblueprint.html @@ -14,17 +14,13 @@
Hello