From 418630255e518af13a26352479e20a1a654ca059 Mon Sep 17 00:00:00 2001 From: barsdeveloper Date: Wed, 27 Oct 2021 19:27:19 +0200 Subject: [PATCH] Various fixes --- dist/ueblueprint.js | 1262 ++++++++++---------- js/Utility.js | 194 +-- js/entity/Entity.js | 108 +- js/entity/FunctionReferenceEntity.js | 26 +- js/entity/ObjectEntity.js | 54 +- js/entity/PinEntity.js | 98 +- js/entity/PinReferenceEntity.js | 28 +- js/entity/TypeInitialization.js | 38 +- js/entity/VariableReferenceEntity.js | 30 +- js/entity/primitive/Guid.js | 64 +- js/entity/primitive/Integer.js | 50 +- js/entity/primitive/LocalizedTextEntity.js | 44 +- js/entity/primitive/ObjectReference.js | 46 +- js/entity/primitive/Primitive.js | 10 +- js/export.js | 15 +- js/graph/GraphEntity.js | 44 +- js/graph/GraphLink.js | 58 +- js/graph/GraphNode.js | 50 +- js/graph/GraphSelector.js | 108 +- js/graph/SelectableDraggable.js | 140 +-- js/input/Drag.js | 82 +- js/input/DragScroll.js | 18 +- js/input/MouseClickDrag.js | 200 ++-- js/input/MouseWheel.js | 62 +- js/input/Pointing.js | 44 +- js/input/Select.js | 54 +- js/input/Zoom.js | 18 +- js/selection/FastSelectionModel.js | 328 ++--- js/selection/OrderedIndexArray.js | 306 ++--- js/selection/SimpleSelectionModel.js | 88 +- js/serialization/GeneralSerializer.js | 25 + js/serialization/Grammar.js | 354 +++--- js/serialization/ObjectSerialize.js | 32 - js/serialization/ObjectSerializer.js | 46 + js/serialization/PinSerializer.js | 23 - js/serialization/Serializer.js | 154 ++- js/serialization/SerializerFactory.js | 32 +- js/template/BlueprintTemplate.js | 92 +- js/template/NodeTemplate.js | 136 +-- js/template/Template.js | 50 +- ueblueprint.html | 127 +- 41 files changed, 2401 insertions(+), 2337 deletions(-) mode change 100644 => 100755 js/Utility.js mode change 100644 => 100755 js/entity/Entity.js mode change 100644 => 100755 js/entity/FunctionReferenceEntity.js mode change 100644 => 100755 js/entity/ObjectEntity.js mode change 100644 => 100755 js/entity/PinEntity.js mode change 100644 => 100755 js/entity/PinReferenceEntity.js mode change 100644 => 100755 js/entity/TypeInitialization.js mode change 100644 => 100755 js/entity/VariableReferenceEntity.js mode change 100644 => 100755 js/entity/primitive/Guid.js mode change 100644 => 100755 js/entity/primitive/Integer.js mode change 100644 => 100755 js/entity/primitive/LocalizedTextEntity.js mode change 100644 => 100755 js/entity/primitive/ObjectReference.js mode change 100644 => 100755 js/entity/primitive/Primitive.js mode change 100644 => 100755 js/export.js mode change 100644 => 100755 js/graph/GraphEntity.js mode change 100644 => 100755 js/graph/GraphLink.js mode change 100644 => 100755 js/graph/GraphNode.js mode change 100644 => 100755 js/graph/GraphSelector.js mode change 100644 => 100755 js/graph/SelectableDraggable.js mode change 100644 => 100755 js/input/Drag.js mode change 100644 => 100755 js/input/DragScroll.js mode change 100644 => 100755 js/input/MouseClickDrag.js mode change 100644 => 100755 js/input/MouseWheel.js mode change 100644 => 100755 js/input/Pointing.js mode change 100644 => 100755 js/input/Select.js mode change 100644 => 100755 js/input/Zoom.js mode change 100644 => 100755 js/selection/FastSelectionModel.js mode change 100644 => 100755 js/selection/OrderedIndexArray.js mode change 100644 => 100755 js/selection/SimpleSelectionModel.js create mode 100755 js/serialization/GeneralSerializer.js mode change 100644 => 100755 js/serialization/Grammar.js delete mode 100644 js/serialization/ObjectSerialize.js create mode 100755 js/serialization/ObjectSerializer.js delete mode 100644 js/serialization/PinSerializer.js mode change 100644 => 100755 js/serialization/Serializer.js mode change 100644 => 100755 js/serialization/SerializerFactory.js mode change 100644 => 100755 js/template/BlueprintTemplate.js mode change 100644 => 100755 js/template/NodeTemplate.js mode change 100644 => 100755 js/template/Template.js mode change 100644 => 100755 ueblueprint.html diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 2c68436..e1c684b 100644 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -1,336 +1,260 @@ -class Primitive { - toString() { - return "Unimplemented for " + this.constructor.name - } +class Primitive { + toString() { + return "Unimplemented for " + this.constructor.name + } } -class Integer extends Primitive { - - constructor(value) { - super(); - // Using constructor equality and not instanceof in order to consider both primitives and objects - if (value?.constructor === String) { - value = Number(value); - } - if (value?.constructor === Number) { - value = Math.round(value); - } - /** @type {number} */ - this.value = value; - } - - valueOf() { - this.value; - } - - toString() { - return this.value.toString() - } +class Integer extends Primitive { + + constructor(value) { + super(); + // Using constructor equality and not instanceof in order to consider both primitives and objects + if (value?.constructor === String) { + value = Number(value); + } + if (value?.constructor === Number) { + value = Math.round(value); + } + /** @type {number} */ + this.value = value; + } + + valueOf() { + this.value; + } + + toString() { + return this.value.toString() + } } -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 sanitize(value) { - if (!(value instanceof Object)) { - return value // Is already primitive - } - if (value instanceof Boolean || value instanceof Integer || value instanceof Number) { - return value.valueOf() - } - if (value instanceof String) { - return value.toString() - } - return value - } - - static equals(a, b) { - a = Utility.sanitize(a); - b = Utility.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 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 sanitize(value) { + if (!(value instanceof Object)) { + return value // Is already primitive + } + if (value instanceof Boolean || value instanceof Integer || value instanceof Number) { + return value.valueOf() + } + if (value instanceof String) { + return value.toString() + } + return value + } + + static equals(a, b) { + a = Utility.sanitize(a); + b = Utility.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 TypeInitialization { - - /** - * - * @param {typeof Object} type - * @param {boolean} showDefault - * @param {*} value - */ - constructor(type, showDefault = true, value = undefined) { - if (value === undefined) { - value = Utility.sanitize(new type()); - } - this.value = value; - this.showDefault = showDefault; - this.type = type; - } +class TypeInitialization { + + /** + * + * @param {typeof Object} type + * @param {boolean} showDefault + * @param {*} value + */ + constructor(type, showDefault = true, value = undefined) { + if (value === undefined) { + value = Utility.sanitize(new type()); + } + this.value = value; + this.showDefault = showDefault; + this.type = type; + } } -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 = Utility.sanitize(new defaultValue()); - } - target[property] = defaultValue; - } - }; - defineAllAttributes([], this, this.getAttributes()); - } +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 = Utility.sanitize(new defaultValue()); + } + target[property] = defaultValue; + } + }; + defineAllAttributes([], this, this.getAttributes()); + } } -class Guid extends Primitive { - - 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) { - super(); - // Using constructor equality and not instanceof in order to consider both primitives and objects - if (guid?.constructor === Boolean) { - guid = Guid.generateGuid(guid == true); - } - if (guid instanceof Guid) { - guid = guid.value; - } - this.value = guid; - } - - toString() { - return this.value.toString() - } +class ObjectReference extends Primitive { + + /** + * + * @param {String} type + * @param {String} path + */ + constructor(type, path) { + super(); + this.type = type; + this.path = path; + } + + toString() { + return (this.type ?? "") + ( + this.path + ? this.type ? `'"${this.path}"'` : this.path + : "" + ) + } } -class LocalizedTextEntity extends Primitive { - - /** - * - * @param {String} namespace - * @param {String} key - * @param {String} value - */ - constructor(namespace, key, value) { - super(); - this.namespace = namespace; - this.key = key; - this.value = value; - } - - toString() { - "NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")"; - } - +class FunctionReferenceEntity extends Entity { + static attributes = { + MemberParent: ObjectReference, + MemberName: "" + } + + getAttributes() { + return FunctionReferenceEntity.attributes + } } -class ObjectReference extends Primitive { - - /** - * - * @param {String} type - * @param {String} path - */ - constructor(type, path) { - super(); - this.type = type; - this.path = path; - } - - toString() { - return (this.type ?? "") + ( - this.path - ? this.type ? `'"${this.path}"'` : this.path - : "" - ) - } -} - -class PinReferenceEntity extends Entity { - - static attributes = { - objectName: String, - pinGuid: Guid - } - - getAttributes() { - return PinReferenceEntity.attributes - } -} - -class PinEntity$1 extends Entity { - - static attributes = { - PinId: Guid, - PinName: "", - PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null), - PinToolTip: "", - Direction: new TypeInitialization(String, false, ""), - PinType: { - PinCategory: "", - PinSubCategory: "", - PinSubCategoryObject: ObjectReference, - PinSubCategoryMemberReference: null, - PinValueType: null, - ContainerType: ObjectReference, - bIsReference: false, - bIsConst: false, - bIsWeakPointer: false, - bIsUObjectWrapper: false - }, - LinkedTo: [new TypeInitialization(PinReferenceEntity, false, null)], - DefaultValue: "", - AutogeneratedDefaultValue: "", - PersistentGuid: Guid, - bHidden: false, - bNotConnectable: false, - bDefaultValueIsReadOnly: false, - bDefaultValueIsIgnored: false, - bAdvancedView: false, - bOrphanedPin: false, - } - - getAttributes() { - return PinEntity$1.attributes - } - - isOutput() { - if (this.Direction === "EGPD_Output") { - return true - } - } -} - -class FunctionReferenceEntity extends Entity { - static attributes = { - MemberParent: ObjectReference, - MemberName: "" - } - - getAttributes() { - return FunctionReferenceEntity.attributes - } +class Guid extends Primitive { + + 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) { + super(); + // Using constructor equality and not instanceof in order to consider both primitives and objects + if (guid?.constructor === Boolean) { + guid = Guid.generateGuid(guid == true); + } + if (guid instanceof Guid) { + guid = guid.value; + } + this.value = guid; + } + + toString() { + return this.value.toString() + } } var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; @@ -347,300 +271,426 @@ var parsimmon_umd_min = {exports: {}}; var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports); -class VariableReferenceEntity extends Entity { - - static attributes = { - MemberName: String, - MemberGuid: Guid, - bSelfContext: false - } - - getAttributes() { - return VariableReferenceEntity.attributes - } +class LocalizedTextEntity extends Primitive { + + /** + * + * @param {String} namespace + * @param {String} key + * @param {String} value + */ + constructor(namespace, key, value) { + super(); + this.namespace = namespace; + this.key = key; + this.value = value; + } + + toString() { + "NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")"; + } + } -class ObjectEntity extends Entity { - - static attributes = { - Class: ObjectReference, - Name: "", - bIsPureFunc: new TypeInitialization(Boolean, false, false), - VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), - FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), - TargetType: new TypeInitialization(ObjectReference, false, null), - NodePosX: 0, - NodePosY: 0, - NodeGuid: Guid, - CustomProperties: [PinEntity$1] - } - - getAttributes() { - return ObjectEntity.attributes - } +class PinReferenceEntity extends Entity { + + static attributes = { + objectName: String, + pinGuid: Guid + } + + getAttributes() { + return PinReferenceEntity.attributes + } } -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 ObjectReference({ type: "None" })).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 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}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value") - PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) - ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.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 ObjectReference("", 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 ObjectReference(referenceType, 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, key, value) - ) - 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: - return r.Number - case Integer: - return r.Integer - case String: - return r.String - case Guid: - return r.Guid - case ObjectReference: - 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 - } - } - // 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$1, - attributeKey => Utility.objectGet(PinEntity$1.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 PinEntity extends Entity { + + static attributes = { + PinId: Guid, + PinName: "", + PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null), + PinToolTip: "", + Direction: new TypeInitialization(String, false, ""), + PinType: { + PinCategory: "", + PinSubCategory: "", + PinSubCategoryObject: ObjectReference, + PinSubCategoryMemberReference: null, + PinValueType: null, + ContainerType: ObjectReference, + bIsReference: false, + bIsConst: false, + bIsWeakPointer: false, + bIsUObjectWrapper: false + }, + LinkedTo: [PinReferenceEntity], + DefaultValue: "", + AutogeneratedDefaultValue: "", + PersistentGuid: Guid, + bHidden: false, + bNotConnectable: false, + bDefaultValueIsReadOnly: false, + bDefaultValueIsIgnored: false, + bAdvancedView: false, + bOrphanedPin: false, + } + + getAttributes() { + return PinEntity.attributes + } + + isOutput() { + if (this.Direction === "EGPD_Output") { + return true + } + } } -class Serializer { - - static grammar = Parsimmon.createLanguage(new Grammar()) - - writeValue(value) { - if (value === null) { - return "()" - } - switch (value?.constructor) { - case Function: - return this.writeValue(value()) - case Boolean: - return Utility.FirstCapital(value.toString()) - case ObjectReference: - case Guid: - return value.toString() - case String: - return `"${value}"` - } - } - - /** - * - * @param {String[]} prefix - * @param {Object} object - * @param {String} separator - * @returns - */ - subWrite(key, object, separator = "\n", prefix = "") { - 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 += this.subWrite(fullKey, value, separator, prefix); - } else if (this.showProperty(fullKey, value)) { - result += prefix + fullKey.join(".") + "=" + this.writeValue(value) + separator; - } - } - return result - } - - getAttributes() { - return PinEntity.attributes - } - - showProperty(attributeKey, attributeValue) { - const attributes = this.getAttributes(); - const attribute = Utility.objectGet(attributes, attributeKey); - if (attribute instanceof TypeInitialization) { - return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault - } - return true - } - - /** - * - * @param {String} value - */ - read(value) { - } - - /** - * Returns a string representing the object (serialization) - * @param {*} object - * @returns The serialized string - */ - write(object) { - return '' - } +class VariableReferenceEntity extends Entity { + + static attributes = { + MemberName: String, + MemberGuid: Guid, + bSelfContext: false + } + + getAttributes() { + return VariableReferenceEntity.attributes + } } -class PinSerializer extends Serializer { - - getAttributes() { - return PinEntity$1.attributes - } - - read(value) { - const parseResult = Serializer.grammar.Pin.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the pin."); - return parseResult - } - return parseResult.value - } - - write(object) { - let result = `Pin (${this.subWrite([], object, ",")})`; - return result - } +class ObjectEntity extends Entity { + + static attributes = { + Class: ObjectReference, + Name: "", + bIsPureFunc: new TypeInitialization(Boolean, false, false), + VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), + FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), + TargetType: new TypeInitialization(ObjectReference, false, null), + NodePosX: 0, + NodePosY: 0, + NodeGuid: Guid, + CustomProperties: [PinEntity] + } + + getAttributes() { + return ObjectEntity.attributes + } } -export { PinSerializer }; +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 ObjectReference("None", "")).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 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}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value") + PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) + ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.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 ObjectReference("", 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 ObjectReference(referenceType, 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, key, value) + ) + 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: + return r.Number + case Integer: + return r.Integer + case String: + return r.String + case Guid: + return r.Guid + case ObjectReference: + 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 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 Serializer { + + static grammar = Parsimmon.createLanguage(new Grammar()) + + constructor(entityType, prefix = "", separator = ",", trailingSeparator = false) { + this.entityType = entityType; + this.prefix = prefix; + this.separator = separator; + this.trailingSeparator = trailingSeparator; + } + + writeValue(value) { + if (value === null) { + return "()" + } + 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 Entity) { + return SerializerFactory.getSerializer(Utility.getType(value)).write(value) + } + if (value instanceof Primitive) { + return value.toString() + } + } + + 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 += this.subWrite(fullKey, value, this.prefix, this.separator); + } else if (this.showProperty(fullKey, value)) { + result += (result.length ? this.separator : "") + this.prefix + fullKey.join(".") + "=" + this.writeValue(value); + } + } + if (this.trailingSeparator && result.length) { + // 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 GeneralSerializer extends Serializer { + + constructor(keyword = "", entityType, prefix = "", separator = ",", trailingSeparator = false) { + super(entityType, prefix, separator, trailingSeparator); + this.keyword = keyword; + } + + 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.key ?? ""}(${this.subWrite([], object)})`; + return result + } +} + +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 {ObjectEntity} object + * @returns + */ + write(object) { + let result = `Begin Object Class=${object.Class} Name=${object.Name} +${this.subWrite([], object) + + object + .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin)) + .join("")} +End Object`; + return result + } +} + +SerializerFactory.registerSerializer(ObjectEntity, new ObjectSerializer()); +SerializerFactory.registerSerializer(PinEntity, new GeneralSerializer("Pin ", PinEntity, "", ",", true)); +SerializerFactory.registerSerializer(FunctionReferenceEntity, new GeneralSerializer("", FunctionReferenceEntity, "", ",", false)); + +export { ObjectEntity, SerializerFactory }; diff --git a/js/Utility.js b/js/Utility.js old mode 100644 new mode 100755 index 938b4cc..40c2563 --- a/js/Utility.js +++ b/js/Utility.js @@ -1,97 +1,97 @@ -import Integer from "./entity/primitive/Integer" -import TypeInitialization from "./entity/TypeInitialization" - -export default 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 sanitize(value) { - if (!(value instanceof Object)) { - return value // Is already primitive - } - if (value instanceof Boolean || value instanceof Integer || value instanceof Number) { - return value.valueOf() - } - if (value instanceof String) { - return value.toString() - } - return value - } - - static equals(a, b) { - a = Utility.sanitize(a) - b = Utility.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 - } - } -} \ No newline at end of file +import Integer from "./entity/primitive/Integer" +import TypeInitialization from "./entity/TypeInitialization" + +export default 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 sanitize(value) { + if (!(value instanceof Object)) { + return value // Is already primitive + } + if (value instanceof Boolean || value instanceof Integer || value instanceof Number) { + return value.valueOf() + } + if (value instanceof String) { + return value.toString() + } + return value + } + + static equals(a, b) { + a = Utility.sanitize(a) + b = Utility.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 + } + } +} diff --git a/js/entity/Entity.js b/js/entity/Entity.js old mode 100644 new mode 100755 index 98aa909..88fca5d --- a/js/entity/Entity.js +++ b/js/entity/Entity.js @@ -1,54 +1,54 @@ -import TypeInitialization from "./TypeInitialization" -import Utility from "../Utility" - -export default 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 = Utility.sanitize(new defaultValue()) - } - target[property] = defaultValue - } - } - defineAllAttributes([], this, this.getAttributes()) - } -} \ No newline at end of file +import TypeInitialization from "./TypeInitialization" +import Utility from "../Utility" + +export default 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 = Utility.sanitize(new defaultValue()) + } + target[property] = defaultValue + } + } + defineAllAttributes([], this, this.getAttributes()) + } +} diff --git a/js/entity/FunctionReferenceEntity.js b/js/entity/FunctionReferenceEntity.js old mode 100644 new mode 100755 index 441c6ac..9d4a9c4 --- a/js/entity/FunctionReferenceEntity.js +++ b/js/entity/FunctionReferenceEntity.js @@ -1,13 +1,13 @@ -import Entity from "./Entity" -import ObjectReference from "./primitive/ObjectReference" - -export default class FunctionReferenceEntity extends Entity { - static attributes = { - MemberParent: ObjectReference, - MemberName: "" - } - - getAttributes() { - return FunctionReferenceEntity.attributes - } -} \ No newline at end of file +import Entity from "./Entity" +import ObjectReference from "./primitive/ObjectReference" + +export default class FunctionReferenceEntity extends Entity { + static attributes = { + MemberParent: ObjectReference, + MemberName: "" + } + + getAttributes() { + return FunctionReferenceEntity.attributes + } +} diff --git a/js/entity/ObjectEntity.js b/js/entity/ObjectEntity.js old mode 100644 new mode 100755 index 77ea5a2..d0205c1 --- a/js/entity/ObjectEntity.js +++ b/js/entity/ObjectEntity.js @@ -1,27 +1,27 @@ -import Entity from "./Entity" -import FunctionReferenceEntity from "./FunctionReferenceEntity" -import Guid from "./primitive/Guid" -import ObjectReference from "./primitive/ObjectReference" -import PinEntity from "./PinEntity" -import TypeInitialization from "./TypeInitialization" -import VariableReferenceEntity from "./VariableReferenceEntity" - -export default class ObjectEntity extends Entity { - - static attributes = { - Class: ObjectReference, - Name: "", - bIsPureFunc: new TypeInitialization(Boolean, false, false), - VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), - FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), - TargetType: new TypeInitialization(ObjectReference, false, null), - NodePosX: 0, - NodePosY: 0, - NodeGuid: Guid, - CustomProperties: [PinEntity] - } - - getAttributes() { - return ObjectEntity.attributes - } -} \ No newline at end of file +import Entity from "./Entity" +import FunctionReferenceEntity from "./FunctionReferenceEntity" +import Guid from "./primitive/Guid" +import ObjectReference from "./primitive/ObjectReference" +import PinEntity from "./PinEntity" +import TypeInitialization from "./TypeInitialization" +import VariableReferenceEntity from "./VariableReferenceEntity" + +export default class ObjectEntity extends Entity { + + static attributes = { + Class: ObjectReference, + Name: "", + bIsPureFunc: new TypeInitialization(Boolean, false, false), + VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), + FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), + TargetType: new TypeInitialization(ObjectReference, false, null), + NodePosX: 0, + NodePosY: 0, + NodeGuid: Guid, + CustomProperties: [PinEntity] + } + + getAttributes() { + return ObjectEntity.attributes + } +} diff --git a/js/entity/PinEntity.js b/js/entity/PinEntity.js old mode 100644 new mode 100755 index 156b251..c3e962e --- a/js/entity/PinEntity.js +++ b/js/entity/PinEntity.js @@ -1,49 +1,49 @@ -import Entity from "./Entity" -import Guid from "./primitive/Guid" -import LocalizedTextEntity from "./primitive/LocalizedTextEntity" -import ObjectReference from "./primitive/ObjectReference" -import PinReferenceEntity from "./PinReferenceEntity" -import TypeInitialization from "./TypeInitialization" - -export default class PinEntity extends Entity { - - static attributes = { - PinId: Guid, - PinName: "", - PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null), - PinToolTip: "", - Direction: new TypeInitialization(String, false, ""), - PinType: { - PinCategory: "", - PinSubCategory: "", - PinSubCategoryObject: ObjectReference, - PinSubCategoryMemberReference: null, - PinValueType: null, - ContainerType: ObjectReference, - bIsReference: false, - bIsConst: false, - bIsWeakPointer: false, - bIsUObjectWrapper: false - }, - LinkedTo: [new TypeInitialization(PinReferenceEntity, false, null)], - DefaultValue: "", - AutogeneratedDefaultValue: "", - PersistentGuid: Guid, - bHidden: false, - bNotConnectable: false, - bDefaultValueIsReadOnly: false, - bDefaultValueIsIgnored: false, - bAdvancedView: false, - bOrphanedPin: false, - } - - getAttributes() { - return PinEntity.attributes - } - - isOutput() { - if (this.Direction === "EGPD_Output") { - return true - } - } -} +import Entity from "./Entity" +import Guid from "./primitive/Guid" +import LocalizedTextEntity from "./primitive/LocalizedTextEntity" +import ObjectReference from "./primitive/ObjectReference" +import PinReferenceEntity from "./PinReferenceEntity" +import TypeInitialization from "./TypeInitialization" + +export default class PinEntity extends Entity { + + static attributes = { + PinId: Guid, + PinName: "", + PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null), + PinToolTip: "", + Direction: new TypeInitialization(String, false, ""), + PinType: { + PinCategory: "", + PinSubCategory: "", + PinSubCategoryObject: ObjectReference, + PinSubCategoryMemberReference: null, + PinValueType: null, + ContainerType: ObjectReference, + bIsReference: false, + bIsConst: false, + bIsWeakPointer: false, + bIsUObjectWrapper: false + }, + LinkedTo: [PinReferenceEntity], + DefaultValue: "", + AutogeneratedDefaultValue: "", + PersistentGuid: Guid, + bHidden: false, + bNotConnectable: false, + bDefaultValueIsReadOnly: false, + bDefaultValueIsIgnored: false, + bAdvancedView: false, + bOrphanedPin: false, + } + + getAttributes() { + return PinEntity.attributes + } + + isOutput() { + if (this.Direction === "EGPD_Output") { + return true + } + } +} diff --git a/js/entity/PinReferenceEntity.js b/js/entity/PinReferenceEntity.js old mode 100644 new mode 100755 index ca3598c..7435541 --- a/js/entity/PinReferenceEntity.js +++ b/js/entity/PinReferenceEntity.js @@ -1,14 +1,14 @@ -import Entity from "./Entity" -import Guid from "./primitive/Guid" - -export default class PinReferenceEntity extends Entity { - - static attributes = { - objectName: String, - pinGuid: Guid - } - - getAttributes() { - return PinReferenceEntity.attributes - } -} \ No newline at end of file +import Entity from "./Entity" +import Guid from "./primitive/Guid" + +export default class PinReferenceEntity extends Entity { + + static attributes = { + objectName: String, + pinGuid: Guid + } + + getAttributes() { + return PinReferenceEntity.attributes + } +} diff --git a/js/entity/TypeInitialization.js b/js/entity/TypeInitialization.js old mode 100644 new mode 100755 index 9b7ba97..04742d9 --- a/js/entity/TypeInitialization.js +++ b/js/entity/TypeInitialization.js @@ -1,19 +1,19 @@ -import Utility from "../Utility" - -export default class TypeInitialization { - - /** - * - * @param {typeof Object} type - * @param {boolean} showDefault - * @param {*} value - */ - constructor(type, showDefault = true, value = undefined) { - if (value === undefined) { - value = Utility.sanitize(new type()) - } - this.value = value - this.showDefault = showDefault - this.type = type - } -} \ No newline at end of file +import Utility from "../Utility" + +export default class TypeInitialization { + + /** + * + * @param {typeof Object} type + * @param {boolean} showDefault + * @param {*} value + */ + constructor(type, showDefault = true, value = undefined) { + if (value === undefined) { + value = Utility.sanitize(new type()) + } + this.value = value + this.showDefault = showDefault + this.type = type + } +} diff --git a/js/entity/VariableReferenceEntity.js b/js/entity/VariableReferenceEntity.js old mode 100644 new mode 100755 index 37f526d..cf4e1ed --- a/js/entity/VariableReferenceEntity.js +++ b/js/entity/VariableReferenceEntity.js @@ -1,15 +1,15 @@ -import Entity from "./Entity" -import Guid from "./primitive/Guid" - -export default class VariableReferenceEntity extends Entity { - - static attributes = { - MemberName: String, - MemberGuid: Guid, - bSelfContext: false - } - - getAttributes() { - return VariableReferenceEntity.attributes - } -} \ No newline at end of file +import Entity from "./Entity" +import Guid from "./primitive/Guid" + +export default class VariableReferenceEntity extends Entity { + + static attributes = { + MemberName: String, + MemberGuid: Guid, + bSelfContext: false + } + + getAttributes() { + return VariableReferenceEntity.attributes + } +} diff --git a/js/entity/primitive/Guid.js b/js/entity/primitive/Guid.js old mode 100644 new mode 100755 index 8377a4d..13430b5 --- a/js/entity/primitive/Guid.js +++ b/js/entity/primitive/Guid.js @@ -1,32 +1,32 @@ -import Primitive from "./Primitive"; - -export default class Guid extends Primitive { - - 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) { - super() - // Using constructor equality and not instanceof in order to consider both primitives and objects - if (guid?.constructor === Boolean) { - guid = Guid.generateGuid(guid == true) - } - if (guid instanceof Guid) { - guid = guid.value - } - this.value = guid - } - - toString() { - return this.value.toString() - } -} \ No newline at end of file +import Primitive from "./Primitive" + +export default class Guid extends Primitive { + + 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) { + super() + // Using constructor equality and not instanceof in order to consider both primitives and objects + if (guid?.constructor === Boolean) { + guid = Guid.generateGuid(guid == true) + } + if (guid instanceof Guid) { + guid = guid.value + } + this.value = guid + } + + toString() { + return this.value.toString() + } +} diff --git a/js/entity/primitive/Integer.js b/js/entity/primitive/Integer.js old mode 100644 new mode 100755 index e691b99..c8f7b18 --- a/js/entity/primitive/Integer.js +++ b/js/entity/primitive/Integer.js @@ -1,25 +1,25 @@ -import Primitive from "./Primitive" - -export default class Integer extends Primitive { - - constructor(value) { - super() - // Using constructor equality and not instanceof in order to consider both primitives and objects - if (value?.constructor === String) { - value = Number(value) - } - if (value?.constructor === Number) { - value = Math.round(value) - } - /** @type {number} */ - this.value = value - } - - valueOf() { - this.value - } - - toString() { - return this.value.toString() - } -} \ No newline at end of file +import Primitive from "./Primitive" + +export default class Integer extends Primitive { + + constructor(value) { + super() + // Using constructor equality and not instanceof in order to consider both primitives and objects + if (value?.constructor === String) { + value = Number(value) + } + if (value?.constructor === Number) { + value = Math.round(value) + } + /** @type {number} */ + this.value = value + } + + valueOf() { + this.value + } + + toString() { + return this.value.toString() + } +} diff --git a/js/entity/primitive/LocalizedTextEntity.js b/js/entity/primitive/LocalizedTextEntity.js old mode 100644 new mode 100755 index 9304fad..0aca8d9 --- a/js/entity/primitive/LocalizedTextEntity.js +++ b/js/entity/primitive/LocalizedTextEntity.js @@ -1,22 +1,22 @@ -import Primitive from "./Primitive" - -export default class LocalizedTextEntity extends Primitive { - - /** - * - * @param {String} namespace - * @param {String} key - * @param {String} value - */ - constructor(namespace, key, value) { - super() - this.namespace = namespace - this.key = key - this.value = value - } - - toString() { - "NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")" - } - -} \ No newline at end of file +import Primitive from "./Primitive" + +export default class LocalizedTextEntity extends Primitive { + + /** + * + * @param {String} namespace + * @param {String} key + * @param {String} value + */ + constructor(namespace, key, value) { + super() + this.namespace = namespace + this.key = key + this.value = value + } + + toString() { + "NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")" + } + +} diff --git a/js/entity/primitive/ObjectReference.js b/js/entity/primitive/ObjectReference.js old mode 100644 new mode 100755 index bc9f0e0..8222472 --- a/js/entity/primitive/ObjectReference.js +++ b/js/entity/primitive/ObjectReference.js @@ -1,23 +1,23 @@ -import Primitive from "./Primitive" - -export default class ObjectReference extends Primitive { - - /** - * - * @param {String} type - * @param {String} path - */ - constructor(type, path) { - super() - this.type = type - this.path = path - } - - toString() { - return (this.type ?? "") + ( - this.path - ? this.type ? `'"${this.path}"'` : this.path - : "" - ) - } -} \ No newline at end of file +import Primitive from "./Primitive" + +export default class ObjectReference extends Primitive { + + /** + * + * @param {String} type + * @param {String} path + */ + constructor(type, path) { + super() + this.type = type + this.path = path + } + + toString() { + return (this.type ?? "") + ( + this.path + ? this.type ? `'"${this.path}"'` : this.path + : "" + ) + } +} diff --git a/js/entity/primitive/Primitive.js b/js/entity/primitive/Primitive.js old mode 100644 new mode 100755 index fb850da..4d68d8b --- a/js/entity/primitive/Primitive.js +++ b/js/entity/primitive/Primitive.js @@ -1,5 +1,5 @@ -export default class Primitive { - toString() { - return "Unimplemented for " + this.constructor.name - } -} \ No newline at end of file +export default class Primitive { + toString() { + return "Unimplemented for " + this.constructor.name + } +} diff --git a/js/export.js b/js/export.js old mode 100644 new mode 100755 index d0c7f90..37ce579 --- a/js/export.js +++ b/js/export.js @@ -1,3 +1,12 @@ -import PinSerializer from "./serialization/PinSerializer" - -export { PinSerializer as PinSerializer } \ No newline at end of file +import GeneralSerializer from "./serialization/GeneralSerializer" +import ObjectEntity from "./entity/ObjectEntity" +import ObjectSerializer from "./serialization/ObjectSerializer" +import PinEntity from "./entity/PinEntity" +import SerializerFactory from "./serialization/SerializerFactory" +import FunctionReferenceEntity from "./entity/FunctionReferenceEntity" + +SerializerFactory.registerSerializer(ObjectEntity, new ObjectSerializer()) +SerializerFactory.registerSerializer(PinEntity, new GeneralSerializer("Pin ", PinEntity, "", ",", true)) +SerializerFactory.registerSerializer(FunctionReferenceEntity, new GeneralSerializer("", FunctionReferenceEntity, "", ",", false)) + +export { SerializerFactory as SerializerFactory, ObjectEntity as ObjectEntity } \ No newline at end of file diff --git a/js/graph/GraphEntity.js b/js/graph/GraphEntity.js old mode 100644 new mode 100755 index 707cb8a..dd7c7a4 --- a/js/graph/GraphEntity.js +++ b/js/graph/GraphEntity.js @@ -1,22 +1,22 @@ -export default 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 '' - } -} \ No newline at end of file +export default 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 '' + } +} diff --git a/js/graph/GraphLink.js b/js/graph/GraphLink.js old mode 100644 new mode 100755 index ecadc79..5ae8d28 --- a/js/graph/GraphLink.js +++ b/js/graph/GraphLink.js @@ -1,29 +1,29 @@ -import UBlueprintEntity from "./UBlueprintEntity" - -export default class GraphLink extends UBlueprintEntity { - - /** - * - * @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) +import UBlueprintEntity from "./UBlueprintEntity" + +export default class GraphLink extends UBlueprintEntity { + + /** + * + * @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) diff --git a/js/graph/GraphNode.js b/js/graph/GraphNode.js old mode 100644 new mode 100755 index e7450f2..28a86c3 --- a/js/graph/GraphNode.js +++ b/js/graph/GraphNode.js @@ -1,25 +1,25 @@ -import SelectableDraggable from "./SelectableDraggable" -import NodeTemplate from "../template/NodeTemplate" - -export default class GraphNode extends SelectableDraggable { - - constructor() { - super(new NodeTemplate()) - this.graphNodeName = 'n/a' - this.inputs = [] - this.outputs = [] - } - - connectedCallback() { - const type = 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) +import NodeTemplate from "../template/NodeTemplate" +import SelectableDraggable from "./SelectableDraggable" + +export default class GraphNode extends SelectableDraggable { + + constructor() { + super(new NodeTemplate()) + this.graphNodeName = 'n/a' + this.inputs = [] + this.outputs = [] + } + + connectedCallback() { + const type = 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) diff --git a/js/graph/GraphSelector.js b/js/graph/GraphSelector.js old mode 100644 new mode 100755 index 8b2f5b6..e97a71d --- a/js/graph/GraphSelector.js +++ b/js/graph/GraphSelector.js @@ -1,54 +1,54 @@ -import FastSelectionModel from "../selection/FastSelectionModel"; -import GraphEntity from "./GraphEntity"; -import Template from "../template/Template"; - -export default 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) \ No newline at end of file +import FastSelectionModel from "../selection/FastSelectionModel" +import GraphEntity from "./GraphEntity" +import Template from "../template/Template" + +export default 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) diff --git a/js/graph/SelectableDraggable.js b/js/graph/SelectableDraggable.js old mode 100644 new mode 100755 index 063b2dc..4fe4ce2 --- a/js/graph/SelectableDraggable.js +++ b/js/graph/SelectableDraggable.js @@ -1,70 +1,70 @@ -import Drag from "../input/Drag" -import GraphEntity from "./GraphEntity" - -export default 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) - } - } - -} \ No newline at end of file +import Drag from "../input/Drag" +import GraphEntity from "./GraphEntity" + +export default 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) + } + } + +} diff --git a/js/input/Drag.js b/js/input/Drag.js old mode 100644 new mode 100755 index 6f31ded..d5875e3 --- a/js/input/Drag.js +++ b/js/input/Drag.js @@ -1,41 +1,41 @@ -import MouseClickDrag from "./MouseClickDrag" - -export default 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 - } -} \ No newline at end of file +import MouseClickDrag from "./MouseClickDrag" + +export default 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 + } +} diff --git a/js/input/DragScroll.js b/js/input/DragScroll.js old mode 100644 new mode 100755 index a3ef91f..dbfc9bd --- a/js/input/DragScroll.js +++ b/js/input/DragScroll.js @@ -1,9 +1,9 @@ -import MouseClickDrag from "./MouseClickDrag" - -export default class DragScroll extends MouseClickDrag { - - dragTo(location, movement) { - this.blueprint.scrollDelta([-movement[0], -movement[1]]) - } - -} \ No newline at end of file +import MouseClickDrag from "./MouseClickDrag" + +export default class DragScroll extends MouseClickDrag { + + dragTo(location, movement) { + this.blueprint.scrollDelta([-movement[0], -movement[1]]) + } + +} diff --git a/js/input/MouseClickDrag.js b/js/input/MouseClickDrag.js old mode 100644 new mode 100755 index 8e369d1..b70f4f8 --- a/js/input/MouseClickDrag.js +++ b/js/input/MouseClickDrag.js @@ -1,100 +1,100 @@ -import Pointing from "./Pointing" - -/** - * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses. - */ -export default 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() { - } -} \ No newline at end of file +import Pointing from "./Pointing" + +/** + * This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses. + */ +export default 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() { + } +} diff --git a/js/input/MouseWheel.js b/js/input/MouseWheel.js old mode 100644 new mode 100755 index 512436f..7097365 --- a/js/input/MouseWheel.js +++ b/js/input/MouseWheel.js @@ -1,31 +1,31 @@ -import Pointing from "./Pointing" - -export default 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) { - - } -} \ No newline at end of file +import Pointing from "./Pointing" + +export default 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) { + + } +} diff --git a/js/input/Pointing.js b/js/input/Pointing.js old mode 100644 new mode 100755 index 3c359ce..d738130 --- a/js/input/Pointing.js +++ b/js/input/Pointing.js @@ -1,22 +1,22 @@ -import Utility from "../Utility" - -export default 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 - } -} \ No newline at end of file +import Utility from "../Utility" + +export default 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 + } +} diff --git a/js/input/Select.js b/js/input/Select.js old mode 100644 new mode 100755 index 5011b84..f544053 --- a/js/input/Select.js +++ b/js/input/Select.js @@ -1,27 +1,27 @@ -import MouseClickDrag from "./MouseClickDrag" - -export default 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() - } - } -} \ No newline at end of file +import MouseClickDrag from "./MouseClickDrag" + +export default 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() + } + } +} diff --git a/js/input/Zoom.js b/js/input/Zoom.js old mode 100644 new mode 100755 index f9d5927..3987f42 --- a/js/input/Zoom.js +++ b/js/input/Zoom.js @@ -1,9 +1,9 @@ -import MouseWheel from "./MouseWheel"; - -export default class Zoom extends MouseWheel { - wheel(variation, location) { - let zoomLevel = this.blueprint.getZoom() - zoomLevel -= variation - this.blueprint.setZoom(zoomLevel, location) - } -} \ No newline at end of file +import MouseWheel from "./MouseWheel"; + +export default class Zoom extends MouseWheel { + wheel(variation, location) { + let zoomLevel = this.blueprint.getZoom() + zoomLevel -= variation + this.blueprint.setZoom(zoomLevel, location) + } +} diff --git a/js/selection/FastSelectionModel.js b/js/selection/FastSelectionModel.js old mode 100644 new mode 100755 index 0153886..cd4271c --- a/js/selection/FastSelectionModel.js +++ b/js/selection/FastSelectionModel.js @@ -1,164 +1,164 @@ -import OrderedIndexArray from "./OrderedIndexArray" - -export default 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 - } - -} \ No newline at end of file +import OrderedIndexArray from "./OrderedIndexArray" + +export default 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 + } + +} diff --git a/js/selection/OrderedIndexArray.js b/js/selection/OrderedIndexArray.js old mode 100644 new mode 100755 index 62e5f92..3310133 --- a/js/selection/OrderedIndexArray.js +++ b/js/selection/OrderedIndexArray.js @@ -1,153 +1,153 @@ -export default 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) - } -} +export default 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) + } +} diff --git a/js/selection/SimpleSelectionModel.js b/js/selection/SimpleSelectionModel.js old mode 100644 new mode 100755 index 5e0e5cf..a8ea3ad --- a/js/selection/SimpleSelectionModel.js +++ b/js/selection/SimpleSelectionModel.js @@ -1,44 +1,44 @@ -export default class SimpleSelectionModel { - - /** - * @typedef {{ - * primaryInf: number, - * primarySup: number, - * secondaryInf: number, - * secondarySup: number - * }} BoundariesInfo - * @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} selectToggleFunction A function that selects or deselects individual rectangles. - */ - constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) { - this.initialPosition = initialPosition - this.finalPosition = initialPosition - this.boundariesFunc = boundariesFunc - this.selectToggleFunction = selectToggleFunction - this.rectangles = rectangles - } - - selectTo(finalPosition) { - let primaryInf = Math.min(finalPosition[0], this.initialPosition[0]) - let primarySup = Math.max(finalPosition[0], this.initialPosition[0]) - let secondaryInf = Math.min(finalPosition[1], this.initialPosition[1]) - let secondarySup = Math.max(finalPosition[1], this.initialPosition[1]) - this.finalPosition = finalPosition - this.rectangles.forEach(rect => { - let boundaries = this.boundariesFunc(rect) - if ( - Math.max(boundaries.primaryInf, primaryInf) < Math.min(boundaries.primarySup, primarySup) - && Math.max(boundaries.secondaryInf, secondaryInf) < Math.min(boundaries.secondarySup, secondarySup) - ) { - this.selectToggleFunction(rect, true) - } else { - - this.selectToggleFunction(rect, false) - } - }) - } - -} \ No newline at end of file +export default class SimpleSelectionModel { + + /** + * @typedef {{ + * primaryInf: number, + * primarySup: number, + * secondaryInf: number, + * secondarySup: number + * }} BoundariesInfo + * @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} selectToggleFunction A function that selects or deselects individual rectangles. + */ + constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) { + this.initialPosition = initialPosition + this.finalPosition = initialPosition + this.boundariesFunc = boundariesFunc + this.selectToggleFunction = selectToggleFunction + this.rectangles = rectangles + } + + selectTo(finalPosition) { + let primaryInf = Math.min(finalPosition[0], this.initialPosition[0]) + let primarySup = Math.max(finalPosition[0], this.initialPosition[0]) + let secondaryInf = Math.min(finalPosition[1], this.initialPosition[1]) + let secondarySup = Math.max(finalPosition[1], this.initialPosition[1]) + this.finalPosition = finalPosition + this.rectangles.forEach(rect => { + let boundaries = this.boundariesFunc(rect) + if ( + Math.max(boundaries.primaryInf, primaryInf) < Math.min(boundaries.primarySup, primarySup) + && Math.max(boundaries.secondaryInf, secondaryInf) < Math.min(boundaries.secondarySup, secondarySup) + ) { + this.selectToggleFunction(rect, true) + } else { + + this.selectToggleFunction(rect, false) + } + }) + } + +} diff --git a/js/serialization/GeneralSerializer.js b/js/serialization/GeneralSerializer.js new file mode 100755 index 0000000..5960442 --- /dev/null +++ b/js/serialization/GeneralSerializer.js @@ -0,0 +1,25 @@ +import Grammar from "./Grammar" +import Serializer from "./Serializer" + +export default class GeneralSerializer extends Serializer { + + constructor(keyword = "", entityType, prefix = "", separator = ",", trailingSeparator = false) { + super(entityType, prefix, separator, trailingSeparator) + this.keyword = keyword + } + + 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.key ?? ""}(${this.subWrite([], object)})` + return result + } +} diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js old mode 100644 new mode 100755 index 37f39e0..9482e6e --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -1,177 +1,177 @@ -import FunctionReferenceEntity from "../entity/FunctionReferenceEntity" -import Guid from "../entity/primitive/Guid" -import Integer from "../entity/primitive/Integer" -import ObjectReference from "../entity/primitive/ObjectReference" -import Parsimmon from "parsimmon" -import PinEntity from "../entity/PinEntity" -import Utility from "../Utility" -import ObjectEntity from "../entity/ObjectEntity" -import LocalizedTextEntity from "../entity/primitive/LocalizedTextEntity" -import PinReferenceEntity from "../entity/PinReferenceEntity" - -let P = Parsimmon - -export default 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 ObjectReference({ type: "None" })).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 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}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value") - PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) - ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.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 ObjectReference("", 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 ObjectReference(referenceType, 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, key, value) - ) - 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: - return r.Number - case Integer: - return r.Integer - case String: - return r.String - case Guid: - return r.Guid - case ObjectReference: - 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) -} \ No newline at end of file +import FunctionReferenceEntity from "../entity/FunctionReferenceEntity" +import Guid from "../entity/primitive/Guid" +import Integer from "../entity/primitive/Integer" +import LocalizedTextEntity from "../entity/primitive/LocalizedTextEntity" +import ObjectEntity from "../entity/ObjectEntity" +import ObjectReference from "../entity/primitive/ObjectReference" +import Parsimmon from "parsimmon" +import PinEntity from "../entity/PinEntity" +import PinReferenceEntity from "../entity/PinReferenceEntity" +import Utility from "../Utility" + +let P = Parsimmon + +export default 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 ObjectReference("None", "")).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 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}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value") + PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/) + ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.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 ObjectReference("", 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 ObjectReference(referenceType, 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, key, value) + ) + 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: + return r.Number + case Integer: + return r.Integer + case String: + return r.String + case Guid: + return r.Guid + case ObjectReference: + 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) +} diff --git a/js/serialization/ObjectSerialize.js b/js/serialization/ObjectSerialize.js deleted file mode 100644 index 9fb10fe..0000000 --- a/js/serialization/ObjectSerialize.js +++ /dev/null @@ -1,32 +0,0 @@ -import Serializer from "./Serializer"; - -export default 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 - } -} \ No newline at end of file diff --git a/js/serialization/ObjectSerializer.js b/js/serialization/ObjectSerializer.js new file mode 100755 index 0000000..2b3e082 --- /dev/null +++ b/js/serialization/ObjectSerializer.js @@ -0,0 +1,46 @@ +import ObjectEntity from "../entity/ObjectEntity" +import PinEntity from "../entity/PinEntity" +import Serializer from "./Serializer" +import SerializerFactory from "./SerializerFactory" + +export default 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 {ObjectEntity} object + * @returns + */ + write(object) { + let result = `Begin Object Class=${object.Class} Name=${object.Name} +${this.subWrite([], object) + + object + .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin)) + .join("")} +End Object` + return result + } +} diff --git a/js/serialization/PinSerializer.js b/js/serialization/PinSerializer.js deleted file mode 100644 index d76e34e..0000000 --- a/js/serialization/PinSerializer.js +++ /dev/null @@ -1,23 +0,0 @@ -import PinEntity from "../entity/PinEntity" -import Serializer from "./Serializer" - -export default class PinSerializer extends Serializer { - - getAttributes() { - return PinEntity.attributes - } - - read(value) { - const parseResult = Serializer.grammar.Pin.parse(value) - if (!parseResult.status) { - console.error("Error when trying to parse the pin.") - return parseResult - } - return parseResult.value - } - - write(object) { - let result = `Pin (${this.subWrite([], object, ",")})` - return result - } -} \ No newline at end of file diff --git a/js/serialization/Serializer.js b/js/serialization/Serializer.js old mode 100644 new mode 100755 index 26404be..4bb0831 --- a/js/serialization/Serializer.js +++ b/js/serialization/Serializer.js @@ -1,82 +1,72 @@ -import Grammar from "./Grammar" -import Guid from "../entity/primitive/Guid" -import ObjectReference from "../entity/primitive/ObjectReference" -import Parsimmon from "parsimmon" -import TypeInitialization from "../entity/TypeInitialization" -import Utility from "../Utility" - - -export default class Serializer { - - static grammar = Parsimmon.createLanguage(new Grammar()) - - writeValue(value) { - if (value === null) { - return "()" - } - switch (value?.constructor) { - case Function: - return this.writeValue(value()) - case Boolean: - return Utility.FirstCapital(value.toString()) - case ObjectReference: - case Guid: - return value.toString() - case String: - return `"${value}"` - } - } - - /** - * - * @param {String[]} prefix - * @param {Object} object - * @param {String} separator - * @returns - */ - subWrite(key, object, separator = "\n", prefix = "") { - 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 += this.subWrite(fullKey, value, separator, prefix) - } else if (this.showProperty(fullKey, value)) { - result += prefix + fullKey.join(".") + "=" + this.writeValue(value) + separator - } - } - return result - } - - getAttributes() { - return PinEntity.attributes - } - - showProperty(attributeKey, attributeValue) { - const attributes = this.getAttributes() - const attribute = Utility.objectGet(attributes, attributeKey) - if (attribute instanceof TypeInitialization) { - return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault - } - return true - } - - /** - * - * @param {String} value - */ - read(value) { - } - - /** - * Returns a string representing the object (serialization) - * @param {*} object - * @returns The serialized string - */ - write(object) { - return '' - } -} \ No newline at end of file +import Entity from "../entity/Entity" +import Grammar from "./Grammar" +import Parsimmon from "parsimmon" +import Primitive from "../entity/primitive/Primitive" +import SerializerFactory from "./SerializerFactory" +import TypeInitialization from "../entity/TypeInitialization" +import Utility from "../Utility" + + +export default class Serializer { + + static grammar = Parsimmon.createLanguage(new Grammar()) + + constructor(entityType, prefix = "", separator = ",", trailingSeparator = false) { + this.entityType = entityType + this.prefix = prefix + this.separator = separator + this.trailingSeparator = trailingSeparator + } + + writeValue(value) { + if (value === null) { + return "()" + } + 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 Entity) { + return SerializerFactory.getSerializer(Utility.getType(value)).write(value) + } + if (value instanceof Primitive) { + return value.toString() + } + } + + 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 += this.subWrite(fullKey, value, this.prefix, this.separator) + } else if (this.showProperty(fullKey, value)) { + result += (result.length ? this.separator : "") + this.prefix + fullKey.join(".") + "=" + this.writeValue(value) + } + } + if (this.trailingSeparator && result.length) { + // 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 + } +} diff --git a/js/serialization/SerializerFactory.js b/js/serialization/SerializerFactory.js old mode 100644 new mode 100755 index d05b196..8f4c893 --- a/js/serialization/SerializerFactory.js +++ b/js/serialization/SerializerFactory.js @@ -1,17 +1,15 @@ -import PinEntity from "../entity/PinEntity" -import Utility from "../Utility" -import PinSerializer from "./PinSerializer" -import ObjectEntity from "../entity/ObjectEntity" -import ObjectSerialize from "./ObjectSerialize" - - -export default class SerializerFactory { - static serializers = new Map([ - [PinEntity, PinSerializer], - [ObjectEntity, ObjectSerialize] - ]) - - static createSerializer(object) { - return new SerializerFactory.serializers.get(Utility.getType(object))() - } -} \ No newline at end of file +import Utility from "../Utility" + + +export default 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)) + } +} diff --git a/js/template/BlueprintTemplate.js b/js/template/BlueprintTemplate.js old mode 100644 new mode 100755 index 904b298..5dd2fb4 --- a/js/template/BlueprintTemplate.js +++ b/js/template/BlueprintTemplate.js @@ -1,46 +1,46 @@ -import Template from "./Template"; - -export default 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)} - ` - } -} \ No newline at end of file +import Template from "./Template" + +export default 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)} + ` + } +} diff --git a/js/template/NodeTemplate.js b/js/template/NodeTemplate.js old mode 100644 new mode 100755 index 96b4b2f..c987221 --- a/js/template/NodeTemplate.js +++ b/js/template/NodeTemplate.js @@ -1,68 +1,68 @@ -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} entity Entity representing the element - * @returns The computed html - */ - header(entity) { - return ` -
- - - ${entity.graphNodeName} - -
- ` - } - - /** - * Computes the html content of the target element. - * @param {import("../entity/ObjectEntity").default} entity Entity representing the element - * @returns The computed html - */ - body(entity) { - let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity) - let outputs = inputs.filter(v => v.isOutput()) - inputs = inputs.filter(v => !v.isOutput()) - return ` -
-
- ${inputs.map((input, index) => ` -
- - ${input.name} -
- `).join("") ?? ""} -
-
- ${outputs.map((output, index) => ` -
- ${output.name} - -
- `).join("") ?? ''} -
-
- ` - } - - /** - * Computes the html content of the target element. - * @param {HTMLElement} entity Entity representing the element - * @returns The computed html - */ - render(entity) { - return ` -
-
- ${this.header(entity)} - ${this.body(entity)} -
-
- ` - } -} \ No newline at end of file +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} entity Entity representing the element + * @returns The computed html + */ + header(entity) { + return ` +
+ + + ${entity.graphNodeName} + +
+ ` + } + + /** + * Computes the html content of the target element. + * @param {import("../entity/ObjectEntity").default} entity Entity representing the element + * @returns The computed html + */ + body(entity) { + let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity) + let outputs = inputs.filter(v => v.isOutput()) + inputs = inputs.filter(v => !v.isOutput()) + return ` +
+
+ ${inputs.map((input, index) => ` +
+ + ${input.name} +
+ `).join("") ?? ""} +
+
+ ${outputs.map((output, index) => ` +
+ ${output.name} + +
+ `).join("") ?? ''} +
+
+ ` + } + + /** + * Computes the html content of the target element. + * @param {HTMLElement} entity Entity representing the element + * @returns The computed html + */ + render(entity) { + return ` +
+
+ ${this.header(entity)} + ${this.body(entity)} +
+
+ ` + } +} diff --git a/js/template/Template.js b/js/template/Template.js old mode 100644 new mode 100755 index 27b30d0..ec08ee5 --- a/js/template/Template.js +++ b/js/template/Template.js @@ -1,25 +1,25 @@ -/** - * @typedef {import("../graph/GraphNode").default} GraphNode - */ -export default 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 {GraphNode} entity Entity representing the element - * @returns The rendered elements - */ - getElements(entity) { - let aDiv = document.createElement('div') - aDiv.innerHTML = this.render(element) - return aDiv.childNodes - } -} \ No newline at end of file +/** + * @typedef {import("../graph/GraphNode").default} GraphNode + */ +export default 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 {GraphNode} entity Entity representing the element + * @returns The rendered elements + */ + getElements(entity) { + let aDiv = document.createElement('div') + aDiv.innerHTML = this.render(element) + return aDiv.childNodes + } +} diff --git a/ueblueprint.html b/ueblueprint.html old mode 100644 new mode 100755 index 6aea59a..f720c6b --- a/ueblueprint.html +++ b/ueblueprint.html @@ -1,64 +1,65 @@ - - - - - - - - - - - - - -
Hello
- - - - + + + + + + + + + + + + + +
Hello
+ + + + \ No newline at end of file