diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 77f247e..e163524 100755 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -590,7 +590,6 @@ class Configuration { static deleteNodesKeyboardKey = "Delete" static expandGridSize = 400 static gridSize = 16 - static gridSnap = 16 } class Context { @@ -730,6 +729,21 @@ class Utility { return constructor } } + + /** + * + * @param {Number[]} location + * @param {Number} gridSize + */ + static snapToGrid(location, gridSize = Configuration.gridSize) { + if (gridSize === 1) { + return location + } + return [ + gridSize * Math.round(location[0] / gridSize), + gridSize * Math.round(location[1] / gridSize) + ] + } } class Entity { @@ -867,6 +881,7 @@ class IntegerEntity extends Entity { class LocalizedTextEntity extends Entity { + static lookbehind = "NSLOCTEXT" static attributes = { namespace: String, key: String, @@ -1059,7 +1074,7 @@ class Grammar { 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("(")), + P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), r.String.trim(P.optWhitespace), // namespace P.string(","), r.String.trim(P.optWhitespace), // key @@ -1541,6 +1556,65 @@ class LinkTemplate extends Template { } } +/** + * @type {import("./GraphPin").default} GraphPin + */ +class GraphLink extends GraphElement { + + /** @type {GraphPin} */ + #source + /** @type {GraphPin} */ + #destination + #nodeDeleteHandler = _ => this.blueprint.removeGraphElement(this) + #nodeDragSourceHandler = _ => this.setSourceLocation(this.#source.getLinkLocation()) + #nodeDragDestinatonHandler = _ => this.setDestinationLocation(this.#destination.getLinkLocation()) + + /** + * @param {?GraphPin} source + * @param {?GraphPin} destination + */ + constructor(source, destination) { + super(this, new LinkTemplate()); + /** @type {import("../template/LinkTemplate").default} */ + this.template; + this.setSource(source); + this.setDestination(destination); + } + + setSourceLocation(location) { + this.template.applySourceLocation(this.#source.getLinkLocation()); + } + + setDestinationLocation(location) { + this.template.applyDestinationLocation(this.#destination.getLinkLocation()); + } + + /** + * @param {GraphPin} graphPin + */ + setSourcePin(graphPin) { + this.#source?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler); + this.#source?.removeEventListener("ueb-node-drag", this.#nodeDragSourceHandler); + this.#source = graphPin; + this.#source?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler); + this.#source?.addEventListener("ueb-node-drag", this.#nodeDragSourceHandler); + } + + /** + * + * @param {GraphPin} graphPin + */ + setDestinationPin(graphPin) { + this.#destination?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler); + this.#destination?.removeEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler); + this.#destination = graphPin; + this.#destination?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler); + this.#destination?.addEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler); + } +} + +customElements.define("ueb-link", GraphLink); + /** * @typedef {import("../graph/GraphPin").default} GraphPin */ @@ -1666,65 +1740,6 @@ class GraphPin extends GraphElement { customElements.define("ueb-pin", GraphPin); -/** - * @type {import("./GraphPin").default} GraphPin - */ -class GraphLink extends GraphElement { - - /** @type {GraphPin} */ - #source - /** @type {GraphPin} */ - #destination - #nodeDeleteHandler = _ => this.blueprint.removeGraphElement(this) - #nodeDragSourceHandler = _ => this.setSourceLocation(this.#source.getLinkLocation()) - #nodeDragDestinatonHandler = _ => this.setDestinationLocation(this.#destination.getLinkLocation()) - - /** - * @param {?GraphPin} source - * @param {?GraphPin} destination - */ - constructor(source, destination) { - super(this, new LinkTemplate()); - /** @type {import("../template/LinkTemplate").default} */ - this.template; - this.setSource(source); - this.setDestination(destination); - } - - setSourceLocation(location) { - this.template.applySourceLocation(this.#source.getLinkLocation()); - } - - setDestinationLocation(location) { - this.template.applyDestinationLocation(this.#destination.getLinkLocation()); - } - - /** - * @param {GraphPin} graphPin - */ - setSourcePin(graphPin) { - this.#source?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler); - this.#source?.removeEventListener("ueb-node-drag", this.#nodeDragSourceHandler); - this.#source = graphPin; - this.#source?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler); - this.#source?.addEventListener("ueb-node-drag", this.#nodeDragSourceHandler); - } - - /** - * - * @param {GraphPin} graphPin - */ - setDestinationPin(graphPin) { - this.#destination?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler); - this.#destination?.removeEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler); - this.#destination = graphPin; - this.#destination?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler); - this.#destination?.addEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler); - } -} - -customElements.define("ueb-link", GraphLink); - /** * @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable */ @@ -1825,41 +1840,51 @@ class NodeTemplate extends SelectableDraggableTemplate { } } +/** + * @typedef {import("../../graph/SelectableDraggable").default} SelectableDraggable + */ class MouseMoveNodes extends MouseClickDrag { + /** + * + * @param {SelectableDraggable} target + * @param {*} blueprint + * @param {*} options + */ 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) - ] + this.stepSize = parseInt(options?.stepSize ?? this.blueprint.gridSize); + this.mouseLocation = [0, 0]; + /** @type {SelectableDraggable} */ + this.target; } startDrag() { - if (isNaN(this.stepSize) || this.stepSize <= 0) { - this.stepSize = this.blueprint.gridSnap; - } // Get the current mouse position - this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition; + this.mouseLocation = Utility.snapToGrid(this.clickedPosition, this.stepSize); } dragTo(location, movement) { - const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location; - const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]]; + const [mouseLocation, targetLocation] = this.stepSize > 1 + ? [Utility.snapToGrid(location, this.stepSize), Utility.snapToGrid(this.target.location, this.stepSize)] + : [location, this.target.location]; + const d = [ + mouseLocation[0] - this.mouseLocation[0], + mouseLocation[1] - this.mouseLocation[1] + ]; if (d[0] == 0 && d[1] == 0) { return } + // Make sure it snaps on the grid + d[0] += targetLocation[0] - this.target.location[0]; + d[1] += targetLocation[1] - this.target.location[1]; + this.target.dispatchDragEvent(d); // Reassign the position of mouse - this.mousePosition = mousePosition; + this.mouseLocation = mouseLocation; } } @@ -1923,6 +1948,13 @@ class SelectableDraggable extends GraphElement { }); this.blueprint.dispatchEvent(dragEvent); } + + snapToGrid() { + let snappedLocation = this.blueprint.snapToGrid(this.location); + if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) { + this.setLocation(snappedLocation); + } + } } class GraphNode extends SelectableDraggable { @@ -2111,6 +2143,7 @@ class Paste extends Context { ]; node.addLocation(this.blueprint.compensateTranslation(locationOffset)); node.setSelected(true); + node.snapToGrid(); }); } } @@ -2231,8 +2264,6 @@ class Blueprint extends GraphElement { this.template; /** @type {number} */ this.gridSize = Configuration.gridSize; - /** @type {number} */ - this.gridSnap = Configuration.gridSnap; /** @type {GraphNode[]}" */ this.nodes = []; /** @type {GraphLink[]}" */ @@ -2425,10 +2456,7 @@ class Blueprint extends GraphElement { } snapToGrid(location) { - return [ - this.gridSnap * Math.round(location[0] / this.gridSnap), - this.gridSnap * Math.round(location[1] / this.gridSnap) - ] + return Utility.snapToGrid(location, this.gridSize) } /** @@ -2566,7 +2594,7 @@ class Blueprint extends GraphElement { setFocused(value = true) { if (this.focused == value) { - return; + return } let event = new CustomEvent(value ? "blueprint-focus" : "blueprint-unfocus"); this.focused = value; @@ -2583,8 +2611,9 @@ customElements.define("ueb-blueprint", Blueprint); class GeneralSerializer extends Serializer { constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { + wrap = wrap ?? (v => `(${v})`); super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter); - this.wrap = wrap ?? (v => `(${v})`); + this.wrap = wrap; } read(value) { @@ -2635,7 +2664,7 @@ function initializeSerializerFactory() { ); SerializerFactory.registerSerializer( PinEntity$1, - new GeneralSerializer(v => `Pin (${v})`, PinEntity$1, "", ",", true) + new GeneralSerializer(v => `${PinEntity$1.lookbehind} (${v})`, PinEntity$1, "", ",", true) ); SerializerFactory.registerSerializer( FunctionReferenceEntity, @@ -2643,7 +2672,7 @@ function initializeSerializerFactory() { ); SerializerFactory.registerSerializer( LocalizedTextEntity, - new GeneralSerializer(v => `NSLOCTEXT(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "") + new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "") ); SerializerFactory.registerSerializer( PinReferenceEntity, diff --git a/js/Blueprint.js b/js/Blueprint.js index e5293d8..3152807 100755 --- a/js/Blueprint.js +++ b/js/Blueprint.js @@ -22,8 +22,6 @@ export default class Blueprint extends GraphElement { this.template /** @type {number} */ this.gridSize = Configuration.gridSize - /** @type {number} */ - this.gridSnap = Configuration.gridSnap /** @type {GraphNode[]}" */ this.nodes = [] /** @type {GraphLink[]}" */ @@ -216,10 +214,7 @@ export default class Blueprint extends GraphElement { } snapToGrid(location) { - return [ - this.gridSnap * Math.round(location[0] / this.gridSnap), - this.gridSnap * Math.round(location[1] / this.gridSnap) - ] + return Utility.snapToGrid(location, this.gridSize) } /** @@ -357,7 +352,7 @@ export default class Blueprint extends GraphElement { setFocused(value = true) { if (this.focused == value) { - return; + return } let event = new CustomEvent(value ? "blueprint-focus" : "blueprint-unfocus") this.focused = value diff --git a/js/Configuration.js b/js/Configuration.js index b5c8138..52a8942 100755 --- a/js/Configuration.js +++ b/js/Configuration.js @@ -3,5 +3,4 @@ export default class Configuration { static deleteNodesKeyboardKey = "Delete" static expandGridSize = 400 static gridSize = 16 - static gridSnap = 16 } diff --git a/js/Utility.js b/js/Utility.js index 8ee37fa..ba6a6b9 100755 --- a/js/Utility.js +++ b/js/Utility.js @@ -1,3 +1,4 @@ +import Configuration from "./Configuration" import TypeInitialization from "./entity/TypeInitialization" export default class Utility { @@ -77,4 +78,19 @@ export default class Utility { return constructor } } + + /** + * + * @param {Number[]} location + * @param {Number} gridSize + */ + static snapToGrid(location, gridSize = Configuration.gridSize) { + if (gridSize === 1) { + return location + } + return [ + gridSize * Math.round(location[0] / gridSize), + gridSize * Math.round(location[1] / gridSize) + ] + } } diff --git a/js/entity/LocalizedTextEntity.js b/js/entity/LocalizedTextEntity.js index 40b58b2..7eeadf4 100755 --- a/js/entity/LocalizedTextEntity.js +++ b/js/entity/LocalizedTextEntity.js @@ -2,6 +2,7 @@ import Entity from "./Entity" export default class LocalizedTextEntity extends Entity { + static lookbehind = "NSLOCTEXT" static attributes = { namespace: String, key: String, diff --git a/js/graph/GraphLink.js b/js/graph/GraphLink.js index 6847112..046182f 100755 --- a/js/graph/GraphLink.js +++ b/js/graph/GraphLink.js @@ -1,6 +1,5 @@ import GraphElement from "./GraphElement" import LinkTemplate from "../template/LinkTemplate" -import GraphPin from "./GraphPin" /** diff --git a/js/graph/SelectableDraggable.js b/js/graph/SelectableDraggable.js index 9dcf0b5..8523218 100755 --- a/js/graph/SelectableDraggable.js +++ b/js/graph/SelectableDraggable.js @@ -61,4 +61,11 @@ export default class SelectableDraggable extends GraphElement { }) this.blueprint.dispatchEvent(dragEvent) } + + snapToGrid() { + let snappedLocation = this.blueprint.snapToGrid(this.location) + if (this.location[0] != snappedLocation[0] || this.location[1] != snappedLocation[1]) { + this.setLocation(snappedLocation) + } + } } diff --git a/js/input/common/Paste.js b/js/input/common/Paste.js index 0f7b981..5260520 100755 --- a/js/input/common/Paste.js +++ b/js/input/common/Paste.js @@ -45,6 +45,7 @@ export default class Paste extends Context { ] node.addLocation(this.blueprint.compensateTranslation(locationOffset)) node.setSelected(true) + node.snapToGrid() }) } } diff --git a/js/input/mouse/MouseMoveNodes.js b/js/input/mouse/MouseMoveNodes.js index da872e6..bda12f3 100755 --- a/js/input/mouse/MouseMoveNodes.js +++ b/js/input/mouse/MouseMoveNodes.js @@ -1,39 +1,50 @@ import MouseClickDrag from "./MouseClickDrag" +import Utility from "../../Utility" +/** + * @typedef {import("../../graph/SelectableDraggable").default} SelectableDraggable + */ export default class MouseMoveNodes extends MouseClickDrag { + /** + * + * @param {SelectableDraggable} target + * @param {*} blueprint + * @param {*} options + */ 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) - ] + this.stepSize = parseInt(options?.stepSize ?? this.blueprint.gridSize) + this.mouseLocation = [0, 0] + /** @type {SelectableDraggable} */ + this.target } startDrag() { - if (isNaN(this.stepSize) || this.stepSize <= 0) { - this.stepSize = this.blueprint.gridSnap - } // Get the current mouse position - this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition + this.mouseLocation = Utility.snapToGrid(this.clickedPosition, this.stepSize) } dragTo(location, movement) { - const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location - const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]] + const [mouseLocation, targetLocation] = this.stepSize > 1 + ? [Utility.snapToGrid(location, this.stepSize), Utility.snapToGrid(this.target.location, this.stepSize)] + : [location, this.target.location] + const d = [ + mouseLocation[0] - this.mouseLocation[0], + mouseLocation[1] - this.mouseLocation[1] + ] if (d[0] == 0 && d[1] == 0) { return } + // Make sure it snaps on the grid + d[0] += targetLocation[0] - this.target.location[0] + d[1] += targetLocation[1] - this.target.location[1] + this.target.dispatchDragEvent(d) // Reassign the position of mouse - this.mousePosition = mousePosition + this.mouseLocation = mouseLocation } } diff --git a/js/serialization/GeneralSerializer.js b/js/serialization/GeneralSerializer.js index 51f2d3d..97dd85e 100755 --- a/js/serialization/GeneralSerializer.js +++ b/js/serialization/GeneralSerializer.js @@ -4,8 +4,9 @@ import Serializer from "./Serializer" export default class GeneralSerializer extends Serializer { constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { + wrap = wrap ?? (v => `(${v})`) super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) - this.wrap = wrap ?? (v => `(${v})`) + this.wrap = wrap } read(value) { diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js index 5e6799b..e2a237a 100755 --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -48,7 +48,7 @@ export default class Grammar { 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("(")), + P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), r.String.trim(P.optWhitespace), // namespace P.string(","), r.String.trim(P.optWhitespace), // key diff --git a/js/serialization/initializeSerializerFactory.js b/js/serialization/initializeSerializerFactory.js index 2986f36..26d85e7 100755 --- a/js/serialization/initializeSerializerFactory.js +++ b/js/serialization/initializeSerializerFactory.js @@ -20,7 +20,7 @@ export default function initializeSerializerFactory() { ) SerializerFactory.registerSerializer( PinEntity, - new GeneralSerializer(v => `Pin (${v})`, PinEntity, "", ",", true) + new GeneralSerializer(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true) ) SerializerFactory.registerSerializer( FunctionReferenceEntity, @@ -28,7 +28,7 @@ export default function initializeSerializerFactory() { ) SerializerFactory.registerSerializer( LocalizedTextEntity, - new GeneralSerializer(v => `NSLOCTEXT(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "") + new GeneralSerializer(v => `${LocalizedTextEntity.lookbehind}(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "") ) SerializerFactory.registerSerializer( PinReferenceEntity,