From a224903f3580dab28e0e5002e8826b3c22693a8a Mon Sep 17 00:00:00 2001 From: barsdeveloper Date: Tue, 23 Nov 2021 20:05:36 +0100 Subject: [PATCH] PathSymbol => PathSymbolEntity --- dist/ueblueprint.js | 1444 ++++++++++++------------ ueblueprint.html => index.html | 0 js/entity/PathSymbolEntity.js | 16 + js/entity/PinReferenceEntity.js | 2 +- js/entity/primitive/PathSymbol.js | 17 - js/export.js | 19 +- js/serialization/Grammar.js | 8 +- js/serialization/ToStringSerializer.js | 13 + 8 files changed, 773 insertions(+), 746 deletions(-) rename ueblueprint.html => index.html (100%) create mode 100644 js/entity/PathSymbolEntity.js delete mode 100644 js/entity/primitive/PathSymbol.js create mode 100644 js/serialization/ToStringSerializer.js diff --git a/dist/ueblueprint.js b/dist/ueblueprint.js index 787c48f..0166cb7 100644 --- a/dist/ueblueprint.js +++ b/dist/ueblueprint.js @@ -1,3 +1,119 @@ +/** @typedef {import("./graph/GraphNode").default} GraphNode */ +class BlueprintData { + + constructor() { + /** @type {GraphNode[]}" */ + this.nodes = new Array(); + this.expandGridSize = 400; + /** @type {number[]} */ + this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]; + /** @type {number[]} */ + this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]; + /** @type {number[]} */ + this.mousePosition = [0, 0]; + } +} + +/** + * @typedef {import(""../entity/Entity"").default} Entity + */ +class Template { + + /** + * Computes the html content of the target element. + * @param {Entity} entity Entity representing the element + * @returns The computed html + */ + render(entity) { + return "" + } + + /** + * Returns the html elements rendered by this template. + * @param {Entity} entity Entity representing the element + * @returns The rendered elements + */ + getElements(entity) { + let aDiv = document.createElement('div'); + aDiv.innerHTML = this.render(entity); + return aDiv.childNodes + } +} + +class BlueprintTemplate extends Template { + header(element) { + return ` +
+
1:1
+
+ ` + } + + overlay() { + return ` +
+ ` + } + + /** + * + * @param {import("../Blueprint").Blueprint} element + * @returns + */ + viewport(element) { + return ` +
+
+
+
+
+ ` + } + + /** + * Computes the html content of the target element. + * @param {HTMLElement} element Target element + * @returns The computed html + */ + render(element) { + return ` + ${this.header(element)} + ${this.overlay(element)} + ${this.viewport(element)} + ` + } +} + +class Context { + + constructor(target, blueprint, options) { + /** @type {HTMLElement} */ + this.target = target; + /** @type {import("../Blueprint").default}" */ + this.blueprint = blueprint; + this.options = options; + if (options?.wantsFocusCallback ?? false) { + let self = this; + this.blueprintfocusHandler = _ => self.blueprintFocused(); + this.blueprintunfocusHandler = _ => self.blueprintUnfocused(); + this.blueprint.addEventListener("blueprintfocus", this.blueprintfocusHandler); + this.blueprint.addEventListener("blueprintunfocus", this.blueprintunfocusHandler); + } + } + + unlistenDOMElement() { + this.blueprint.removeEventListener("blueprintfocus", this.blueprintfocusHandler); + this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler); + } + + blueprintFocused() { + } + + blueprintUnfocused() { + } +} + class Primitive { toString() { @@ -139,717 +255,6 @@ class Utility { } } -class Entity { - - constructor(options = {}) { - /** - * - * @param {String[]} prefix - * @param {Object} target - * @param {Object} properties - */ - const defineAllAttributes = (prefix, target, properties) => { - let fullKey = prefix.concat(""); - const last = fullKey.length - 1; - for (let property in properties) { - fullKey[last] = property; - // Not instanceof because all objects are instenceof Object, exact match needed - if (properties[property]?.constructor === Object) { - target[property] = {}; - defineAllAttributes(fullKey, target[property], properties[property]); - continue - } - /* - * The value can either be: - * - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World") - * - TypeInitialization: contains the maximum amount of information about the attribute. - * - A type: the default value will be default constructed object without arguments. - * - A proper value. - */ - const value = Utility.objectGet(options, fullKey); - if (value !== null) { - target[property] = value; - continue - } - let defaultValue = properties[property]; - if (defaultValue instanceof TypeInitialization) { - if (!defaultValue.showDefault) { - continue - } - defaultValue = defaultValue.value; - } - if (defaultValue instanceof Array) { - target[property] = []; - continue - } - if (defaultValue instanceof Function) { - defaultValue = TypeInitialization.sanitize(new defaultValue()); - } - target[property] = defaultValue; - } - }; - defineAllAttributes([], this, this.getAttributes()); - } -} - -class ObjectReferenceEntity extends Entity { - - static attributes = { - type: String, - path: String - } - - getAttributes() { - return ObjectReferenceEntity.attributes - } -} - -class FunctionReferenceEntity extends Entity { - - static attributes = { - MemberParent: ObjectReferenceEntity, - 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() - } -} - -class LocalizedTextEntity extends Entity { - - static attributes = { - namespace: String, - key: String, - value: String - } - - getAttributes() { - return LocalizedTextEntity.attributes - } -} - -class PathSymbol extends Primitive { - - constructor(value) { - super(); - this.value = new String(value).valueOf(); - } - - valueOf() { - this.value; - } - - toString() { - return this.value - } -} - -class PinReferenceEntity extends Entity { - - static attributes = { - objectName: PathSymbol, - pinGuid: Guid - } - - getAttributes() { - return PinReferenceEntity.attributes - } -} - -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: ObjectReferenceEntity, - PinSubCategoryMemberReference: null, - PinValueType: null, - ContainerType: ObjectReferenceEntity, - 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 - } - - /** - * - * @returns {String} - */ - getPinDisplayName() { - return this.PinName - } - - isOutput() { - if (this.Direction === "EGPD_Output") { - return true - } - } -} - -class VariableReferenceEntity extends Entity { - - static attributes = { - MemberName: String, - MemberGuid: Guid, - bSelfContext: false - } - - getAttributes() { - return VariableReferenceEntity.attributes - } -} - -class ObjectEntity extends Entity { - - static attributes = { - Class: ObjectReferenceEntity, - Name: "", - bIsPureFunc: new TypeInitialization(Boolean, false, false), - VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), - FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), - TargetType: new TypeInitialization(ObjectReferenceEntity, false, null), - NodePosX: 0, - NodePosY: 0, - NodeGuid: Guid, - CustomProperties: [PinEntity] - } - - getAttributes() { - return ObjectEntity.attributes - } - - /** - * - * @returns {String} The name of the node - */ - getNodeDisplayName() { - return this.Name - } -} - -var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - -function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; -} - -var parsimmon_umd_min = {exports: {}}; - -(function (module, exports) { -!function(n,t){module.exports=t();}("undefined"!=typeof self?self:commonjsGlobal,function(){return function(n){var t={};function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{}};return n[e].call(u.exports,u,u.exports,r),u.l=!0,u.exports}return r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e});},r.r=function(n){Object.defineProperty(n,"__esModule",{value:!0});},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},r.p="",r(r.s=0)}([function(n,t,r){function e(n){if(!(this instanceof e))return new e(n);this._=n;}var u=e.prototype;function o(n,t){for(var r=0;r>7),buf:function(n){var t=i(function(n,t,r,e){return n.concat(r===e.length-1?Buffer.from([t,0]).readUInt16BE(0):e.readUInt16BE(r))},[],n);return Buffer.from(f(function(n){return (n<<1&65535)>>8},t))}(r.buf)};}),r}function c(){return "undefined"!=typeof Buffer}function s(){if(!c())throw new Error("Buffer global does not exist; please use webpack if you need to parse Buffers in the browser.")}function l(n){s();var t=i(function(n,t){return n+t},0,n);if(t%8!=0)throw new Error("The bits ["+n.join(", ")+"] add up to "+t+" which is not an even number of bytes; the total should be divisible by 8");var r,u=t/8,o=(r=function(n){return n>48},i(function(n,t){return n||(r(t)?t:n)},null,n));if(o)throw new Error(o+" bit range requested exceeds 48 bit (6 byte) Number max.");return new e(function(t,r){var e=u+r;return e>t.length?x(r,u.toString()+" bytes"):b(e,i(function(n,t){var r=a(t,n.buf);return {coll:n.coll.concat(r.v),buf:r.buf}},{coll:[],buf:t.slice(r,e)},n).coll)})}function p(n,t){return new e(function(r,e){return s(),e+t>r.length?x(e,t+" bytes for "+n):b(e+t,r.slice(e,e+t))})}function h(n,t){if("number"!=typeof(r=t)||Math.floor(r)!==r||t<0||t>6)throw new Error(n+" requires integer length in range [0, 6].");var r;}function d(n){return h("uintBE",n),p("uintBE("+n+")",n).map(function(t){return t.readUIntBE(0,n)})}function v(n){return h("uintLE",n),p("uintLE("+n+")",n).map(function(t){return t.readUIntLE(0,n)})}function g(n){return h("intBE",n),p("intBE("+n+")",n).map(function(t){return t.readIntBE(0,n)})}function m(n){return h("intLE",n),p("intLE("+n+")",n).map(function(t){return t.readIntLE(0,n)})}function y(n){return n instanceof e}function E(n){return "[object Array]"==={}.toString.call(n)}function w(n){return c()&&Buffer.isBuffer(n)}function b(n,t){return {status:!0,index:n,value:t,furthest:-1,expected:[]}}function x(n,t){return E(t)||(t=[t]),{status:!1,index:-1,value:null,furthest:n,expected:t}}function B(n,t){if(!t)return n;if(n.furthest>t.furthest)return n;var r=n.furthest===t.furthest?function(n,t){if(function(){if(void 0!==e._supportsSet)return e._supportsSet;var n="undefined"!=typeof Set;return e._supportsSet=n,n}()&&Array.from){for(var r=new Set(n),u=0;u=0;){if(i in r){e=r[i].line,0===o&&(o=r[i].lineStart);break}"\n"===n.charAt(i)&&(u++,0===o&&(o=i+1)),i--;}var f=e+u,a=t-o;return r[t]={line:f,lineStart:o},{offset:t,line:f+1,column:a+1}}function _(n){if(!y(n))throw new Error("not a parser: "+n)}function L(n,t){return "string"==typeof n?n.charAt(t):n[t]}function O(n){if("number"!=typeof n)throw new Error("not a number: "+n)}function k(n){if("function"!=typeof n)throw new Error("not a function: "+n)}function P(n){if("string"!=typeof n)throw new Error("not a string: "+n)}var q=2,A=3,I=8,F=5*I,M=4*I,z=" ";function R(n,t){return new Array(t+1).join(n)}function U(n,t,r){var e=t-n.length;return e<=0?n:R(r,e)+n}function W(n,t,r,e){return {from:n-t>0?n-t:0,to:n+r>e?e:n+r}}function D(n,t){var r,e,u,o,a,c=t.index,s=c.offset,l=1;if(s===n.length)return "Got the end of the input";if(w(n)){var p=s-s%I,h=s-p,d=W(p,F,M+I,n.length),v=f(function(n){return f(function(n){return U(n.toString(16),2,"0")},n)},function(n,t){var r=n.length,e=[],u=0;if(r<=t)return [n.slice()];for(var o=0;o=4&&(r+=1),l=2,u=f(function(n){return n.length<=4?n.join(" "):n.slice(0,4).join(" ")+" "+n.slice(4).join(" ")},v),(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2);}else {var g=n.split(/\r\n|[\n\r\u2028\u2029]/);r=c.column-1,e=c.line-1,o=W(e,q,A,g.length),u=g.slice(o.from,o.to),a=o.to.toString().length;}var m=e-o.from;return w(n)&&(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2),i(function(t,e,u){var i,f=u===m,c=f?"> ":z;return i=w(n)?U((8*(o.from+u)).toString(16),a,"0"):U((o.from+u+1).toString(),a," "),[].concat(t,[c+i+" | "+e],f?[z+R(" ",a)+" | "+U("",r," ")+R("^",l)]:[])},[],u).join("\n")}function N(n,t){return ["\n","-- PARSING FAILED "+R("-",50),"\n\n",D(n,t),"\n\n",(r=t.expected,1===r.length?"Expected:\n\n"+r[0]:"Expected one of the following: \n\n"+r.join(", ")),"\n"].join("");var r;}function G(n){return void 0!==n.flags?n.flags:[n.global?"g":"",n.ignoreCase?"i":"",n.multiline?"m":"",n.unicode?"u":"",n.sticky?"y":""].join("")}function C(){for(var n=[].slice.call(arguments),t=n.length,r=0;r=2?O(t):t=0;var r=function(n){return RegExp("^(?:"+n.source+")",G(n))}(n),u=""+n;return e(function(n,e){var o=r.exec(n.slice(e));if(o){if(0<=t&&t<=o.length){var i=o[0],f=o[t];return b(e+i.length,f)}return x(e,"valid match group (0 to "+o.length+") in "+u)}return x(e,u)})}function X(n){return e(function(t,r){return b(r,n)})}function Y(n){return e(function(t,r){return x(r,n)})}function Z(n){if(y(n))return e(function(t,r){var e=n._(t,r);return e.index=r,e.value="",e});if("string"==typeof n)return Z(K(n));if(n instanceof RegExp)return Z(Q(n));throw new Error("not a string, regexp, or parser: "+n)}function $(n){return _(n),e(function(t,r){var e=n._(t,r),u=t.slice(r,e.index);return e.status?x(r,'not "'+u+'"'):b(r,null)})}function nn(n){return k(n),e(function(t,r){var e=L(t,r);return r=n.length?x(t,"any character/byte"):b(t+1,L(n,t))}),on=e(function(n,t){return b(n.length,n.slice(t))}),fn=e(function(n,t){return t=0}).desc(t)},e.optWhitespace=pn,e.Parser=e,e.range=function(n,t){return nn(function(r){return n<=r&&r<=t}).desc(n+"-"+t)},e.regex=Q,e.regexp=Q,e.sepBy=V,e.sepBy1=H,e.seq=C,e.seqMap=J,e.seqObj=function(){for(var n,t={},r=0,u=(n=arguments,Array.prototype.slice.call(n)),o=u.length,i=0;i255)throw new Error("Value specified to byte constructor ("+n+"=0x"+n.toString(16)+") is larger in value than a single byte.");var t=(n>15?"0x":"0x0")+n.toString(16);return e(function(r,e){var u=L(r,e);return u===n?b(e+1,u):x(e,t)})},buffer:function(n){return p("buffer",n).map(function(n){return Buffer.from(n)})},encodedString:function(n,t){return p("string",t).map(function(t){return t.toString(n)})},uintBE:d,uint8BE:d(1),uint16BE:d(2),uint32BE:d(4),uintLE:v,uint8LE:v(1),uint16LE:v(2),uint32LE:v(4),intBE:g,int8BE:g(1),int16BE:g(2),int32BE:g(4),intLE:m,int8LE:m(1),int16LE:m(2),int32LE:m(4),floatBE:p("floatBE",4).map(function(n){return n.readFloatBE(0)}),floatLE:p("floatLE",4).map(function(n){return n.readFloatLE(0)}),doubleBE:p("doubleBE",8).map(function(n){return n.readDoubleBE(0)}),doubleLE:p("doubleLE",8).map(function(n){return n.readDoubleLE(0)})},n.exports=e;}])}); -}(parsimmon_umd_min)); - -var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports); - -let P = Parsimmon; - -class Grammar { - // General - InlineWhitespace = _ => P.regex(/[^\S\n]+/).desc("inline whitespace") - InlineOptWhitespace = _ => P.regex(/[^\S\n]*/).desc("inline optional whitespace") - WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") - Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") - None = _ => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") - Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False") - Number = _ => P.regex(/[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") - Integer = _ => P.regex(/[0-9]+/).map(v => new 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_]+/).map(v => new PathSymbol(v)) - ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) - .tie() - .atLeast(2) - .tie() - .desc('a path (words with possibly underscore, separated by ".", separated by "/")') - Reference = r => P.alt( - r.None, - r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path })), - P.seqMap( - r.Word, - P.optWhitespace, - P.alt(P.string(`"`), P.string(`'"`)).chain( - result => r.ReferencePath.skip( - P.string(result.split("").reverse().join("")) - ) - ), - (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) - ) - ) - AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') - AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText) - LocalizedText = r => P.seqMap( - P.string("NSLOCTEXT").skip(P.optWhitespace).skip(P.string("(")), - r.String.trim(P.optWhitespace), // namespace - P.string(","), - r.String.trim(P.optWhitespace), // key - P.string(","), - r.String.trim(P.optWhitespace), // value - P.string(")"), - (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ - namespace: namespace, - key: key, - value: value - }) - ) - PinReference = r => P.seqMap( - r.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 ObjectReferenceEntity: - return r.Reference - case LocalizedTextEntity: - return r.LocalizedText - case PinReferenceEntity: - return r.PinReference - case FunctionReferenceEntity: - return r.FunctionReference - case PinEntity: - return r.Pin - case Array: - return P.seqMap( - P.string("("), - attributeType - .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) - .reduce((accum, cur) => - !cur || accum === r.AttributeAnyValue - ? r.AttributeAnyValue - : accum.or(cur) - ) - .trim(P.optWhitespace) - .sepBy(P.string(",")) - .skip(P.regex(/,?\s*/)), - P.string(")"), - (_, grammar, __) => grammar - ) - default: - return defaultGrammar - } - } - // Meta grammar - static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) => - attributeGrammar.skip(valueSeparator) - .chain(attributeName => { - const attributeKey = attributeName.split("."); - const attribute = attributeSupplier(attributeKey); - let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue); - return attributeValueGrammar.map(attributeValue => - entity => Utility.objectSet(entity, attributeKey, attributeValue, true) - ) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value - }) - // Meta grammar - static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) => - /** - * Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)' - * Then it populates an object of type EntityType with the attribute values found inside the parentheses. - */ - P.seqMap( - P.seq(keyGrammar, P.optWhitespace, P.string("(")), - Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier) - .trim(P.optWhitespace) - .sepBy(P.string(",")) - .skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma - P.string(')'), - (_, attributes, __) => { - let result = new entityType(); - attributes.forEach(attributeSetter => attributeSetter(result)); - return result - }) - FunctionReference = r => Grammar.CreateMultiAttributeGrammar( - r, - P.succeed(), - FunctionReferenceEntity, - attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey) - ) - Pin = r => Grammar.CreateMultiAttributeGrammar( - r, - P.string("Pin"), - PinEntity, - attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey) - ) - CustomProperties = r => - P.string("CustomProperties") - .then(P.whitespace) - .then(r.Pin) - .map(pin => entity => { - /** @type {Array} */ - let properties = Utility.objectGet(entity, ["CustomProperties"], []); - properties.push(pin); - Utility.objectSet(entity, ["CustomProperties"], properties, true); - }) - - Object = r => P.seqMap( - P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace), - P - .alt( - r.CustomProperties, - Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey)) - ) - .sepBy1(P.whitespace), - P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")), - (_, attributes, __) => { - let result = new ObjectEntity(); - attributes.forEach(attributeSetter => attributeSetter(result)); - return result - } - ) - MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) -} - -class 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, attributeValueConjunctionSign, attributeKeyPrinter) { - this.entityType = entityType; - this.prefix = prefix ?? ""; - this.separator = separator ?? ","; - this.trailingSeparator = trailingSeparator ?? false; - this.attributeValueConjunctionSign = attributeValueConjunctionSign ?? "="; - this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join(".")); - } - - writeValue(value) { - if (value === null) { - return "()" - } - const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v); - // This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically) - switch (value?.constructor) { - case Function: - return this.writeValue(value()) - case Boolean: - return Utility.FirstCapital(value.toString()) - case Number: - return value.toString() - case String: - return `"${value}"` - } - if (value instanceof Array) { - return `(${value.map(v => serialize(v) + ",")})` - } - if (value instanceof Entity) { - return serialize(value) - } - 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 += (result.length ? this.separator : "") - + this.subWrite(fullKey, value); - } else if (this.showProperty(fullKey, value)) { - result += (result.length ? this.separator : "") - + this.prefix - + this.attributeKeyPrinter(fullKey) - + this.attributeValueConjunctionSign - + this.writeValue(value); - } - } - if (this.trailingSeparator && result.length && fullKey.length === 0) { - // append separator at the end if asked and there was printed content - result += this.separator; - } - return result - } - - showProperty(attributeKey, attributeValue) { - const attributes = this.entityType.attributes; - const attribute = Utility.objectGet(attributes, attributeKey); - if (attribute instanceof TypeInitialization) { - return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault - } - return true - } -} - -class GeneralSerializer extends Serializer { - - constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { - super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter); - this.wrap = wrap ?? (v => `(${v})`); - } - - read(value) { - let grammar = Grammar.getGrammarForType(Serializer.grammar, this.entityType); - const parseResult = grammar.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the entity " + this.entityType.prototype.constructor.name); - return parseResult - } - return parseResult.value - } - - write(object) { - let result = this.wrap(this.subWrite([], object)); - return result - } -} - -class ObjectSerializer extends Serializer { - - constructor() { - super(ObjectEntity, " ", "\n", false); - } - - showProperty(attributeKey, attributeValue) { - switch (attributeKey.toString()) { - case "Class": - case "Name": - case "CustomProperties": - // Serielized separately - return false - } - return super.showProperty(attributeKey, attributeValue) - } - - read(value) { - const parseResult = Serializer.grammar.Object.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the object."); - return parseResult - } - return parseResult.value - } - - /** - * - * @param {String} value - * @returns {ObjectEntity[]} - */ - readMultiple(value) { - const parseResult = Serializer.grammar.MultipleObject.parse(value); - if (!parseResult.status) { - console.error("Error when trying to parse the object."); - return parseResult - } - return parseResult.value - } - - /** - * - * @param {ObjectEntity} object - * @returns - */ - write(object) { - let result = `Begin Object Class=${this.writeValue(object.Class)} Name=${this.writeValue(object.Name)} -${this.subWrite([], object) - + object - .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin)) - .join("")} -End Object`; - return result - } -} - -/** @typedef {import("./graph/GraphNode").default} GraphNode */ -class BlueprintData { - - constructor() { - /** @type {GraphNode[]}" */ - this.nodes = new Array(); - this.expandGridSize = 400; - /** @type {number[]} */ - this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]; - /** @type {number[]} */ - this.translateValue = /*[this.expandGridSize, this.expandGridSize]*/[0, 0]; - /** @type {number[]} */ - this.mousePosition = [0, 0]; - } -} - -/** - * @typedef {import(""../entity/Entity"").default} Entity - */ -class Template { - - /** - * Computes the html content of the target element. - * @param {Entity} entity Entity representing the element - * @returns The computed html - */ - render(entity) { - return "" - } - - /** - * Returns the html elements rendered by this template. - * @param {Entity} entity Entity representing the element - * @returns The rendered elements - */ - getElements(entity) { - let aDiv = document.createElement('div'); - aDiv.innerHTML = this.render(entity); - return aDiv.childNodes - } -} - -class BlueprintTemplate extends Template { - header(element) { - return ` -
-
1:1
-
- ` - } - - overlay() { - return ` -
- ` - } - - /** - * - * @param {import("../Blueprint").Blueprint} element - * @returns - */ - viewport(element) { - return ` -
-
-
-
-
- ` - } - - /** - * Computes the html content of the target element. - * @param {HTMLElement} element Target element - * @returns The computed html - */ - render(element) { - return ` - ${this.header(element)} - ${this.overlay(element)} - ${this.viewport(element)} - ` - } -} - -class Context { - - constructor(target, blueprint, options) { - /** @type {HTMLElement} */ - this.target = target; - /** @type {import("../Blueprint").default}" */ - this.blueprint = blueprint; - this.options = options; - if (options?.wantsFocusCallback ?? false) { - let self = this; - this.blueprintfocusHandler = _ => self.blueprintFocused(); - this.blueprintunfocusHandler = _ => self.blueprintUnfocused(); - this.blueprint.addEventListener("blueprintfocus", this.blueprintfocusHandler); - this.blueprint.addEventListener("blueprintunfocus", this.blueprintunfocusHandler); - } - } - - unlistenDOMElement() { - this.blueprint.removeEventListener("blueprintfocus", this.blueprintfocusHandler); - this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler); - } - - blueprintFocused() { - } - - blueprintUnfocused() { - } -} - class Pointing extends Context { constructor(target, blueprint, options) { @@ -1392,6 +797,193 @@ class MouseTracking extends Pointing { } } +class Entity { + + constructor(options = {}) { + /** + * + * @param {String[]} prefix + * @param {Object} target + * @param {Object} properties + */ + const defineAllAttributes = (prefix, target, properties) => { + let fullKey = prefix.concat(""); + const last = fullKey.length - 1; + for (let property in properties) { + fullKey[last] = property; + // Not instanceof because all objects are instenceof Object, exact match needed + if (properties[property]?.constructor === Object) { + target[property] = {}; + defineAllAttributes(fullKey, target[property], properties[property]); + continue + } + /* + * The value can either be: + * - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World") + * - TypeInitialization: contains the maximum amount of information about the attribute. + * - A type: the default value will be default constructed object without arguments. + * - A proper value. + */ + const value = Utility.objectGet(options, fullKey); + if (value !== null) { + target[property] = value; + continue + } + let defaultValue = properties[property]; + if (defaultValue instanceof TypeInitialization) { + if (!defaultValue.showDefault) { + continue + } + defaultValue = defaultValue.value; + } + if (defaultValue instanceof Array) { + target[property] = []; + continue + } + if (defaultValue instanceof Function) { + defaultValue = TypeInitialization.sanitize(new defaultValue()); + } + target[property] = defaultValue; + } + }; + defineAllAttributes([], this, this.getAttributes()); + } +} + +class 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 LocalizedTextEntity extends Entity { + + static attributes = { + namespace: String, + key: String, + value: String + } + + getAttributes() { + return LocalizedTextEntity.attributes + } +} + +class ObjectReferenceEntity extends Entity { + + static attributes = { + type: String, + path: String + } + + getAttributes() { + return ObjectReferenceEntity.attributes + } +} + +class PathSymbolEntity extends Entity { + + static attributes = { + value: String + } + + getAttributes() { + return PathSymbolEntity.attributes + } + + toString() { + return this.value + } +} + +class PinReferenceEntity extends Entity { + + static attributes = { + objectName: PathSymbolEntity, + pinGuid: Guid + } + + getAttributes() { + return PinReferenceEntity.attributes + } +} + +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: ObjectReferenceEntity, + PinSubCategoryMemberReference: null, + PinValueType: null, + ContainerType: ObjectReferenceEntity, + 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 + } + + /** + * + * @returns {String} + */ + getPinDisplayName() { + return this.PinName + } + + isOutput() { + if (this.Direction === "EGPD_Output") { + return true + } + } +} + /** * @typedef {import("../entity/ObjectEntity").default} ObjectEntity */ @@ -1462,6 +1054,59 @@ class NodeTemplate extends Template { } } +class FunctionReferenceEntity extends Entity { + + static attributes = { + MemberParent: ObjectReferenceEntity, + MemberName: "" + } + + getAttributes() { + return FunctionReferenceEntity.attributes + } +} + +class VariableReferenceEntity extends Entity { + + static attributes = { + MemberName: String, + MemberGuid: Guid, + bSelfContext: false + } + + getAttributes() { + return VariableReferenceEntity.attributes + } +} + +class ObjectEntity extends Entity { + + static attributes = { + Class: ObjectReferenceEntity, + Name: "", + bIsPureFunc: new TypeInitialization(Boolean, false, false), + VariableReference: new TypeInitialization(VariableReferenceEntity, false, null), + FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,), + TargetType: new TypeInitialization(ObjectReferenceEntity, false, null), + NodePosX: 0, + NodePosY: 0, + NodeGuid: Guid, + CustomProperties: [PinEntity] + } + + getAttributes() { + return ObjectEntity.attributes + } + + /** + * + * @returns {String} The name of the node + */ + getNodeDisplayName() { + return this.Name + } +} + class Drag extends MouseClickDrag { constructor(target, blueprint, options) { @@ -1570,6 +1215,19 @@ class SelectableDraggable extends GraphElement { } } +class SerializerFactory { + + static #serializers = new Map() + + static registerSerializer(entity, object) { + SerializerFactory.#serializers.set(entity, object); + } + + static getSerializer(entity) { + return SerializerFactory.#serializers.get(Utility.getType(entity)) + } +} + class DragLink extends MouseClickDrag { constructor(target, blueprint, options) { @@ -1637,6 +1295,324 @@ class GraphNode extends SelectableDraggable { customElements.define('u-node', GraphNode); +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +var parsimmon_umd_min = {exports: {}}; + +(function (module, exports) { +!function(n,t){module.exports=t();}("undefined"!=typeof self?self:commonjsGlobal,function(){return function(n){var t={};function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{}};return n[e].call(u.exports,u,u.exports,r),u.l=!0,u.exports}return r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e});},r.r=function(n){Object.defineProperty(n,"__esModule",{value:!0});},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},r.p="",r(r.s=0)}([function(n,t,r){function e(n){if(!(this instanceof e))return new e(n);this._=n;}var u=e.prototype;function o(n,t){for(var r=0;r>7),buf:function(n){var t=i(function(n,t,r,e){return n.concat(r===e.length-1?Buffer.from([t,0]).readUInt16BE(0):e.readUInt16BE(r))},[],n);return Buffer.from(f(function(n){return (n<<1&65535)>>8},t))}(r.buf)};}),r}function c(){return "undefined"!=typeof Buffer}function s(){if(!c())throw new Error("Buffer global does not exist; please use webpack if you need to parse Buffers in the browser.")}function l(n){s();var t=i(function(n,t){return n+t},0,n);if(t%8!=0)throw new Error("The bits ["+n.join(", ")+"] add up to "+t+" which is not an even number of bytes; the total should be divisible by 8");var r,u=t/8,o=(r=function(n){return n>48},i(function(n,t){return n||(r(t)?t:n)},null,n));if(o)throw new Error(o+" bit range requested exceeds 48 bit (6 byte) Number max.");return new e(function(t,r){var e=u+r;return e>t.length?x(r,u.toString()+" bytes"):b(e,i(function(n,t){var r=a(t,n.buf);return {coll:n.coll.concat(r.v),buf:r.buf}},{coll:[],buf:t.slice(r,e)},n).coll)})}function p(n,t){return new e(function(r,e){return s(),e+t>r.length?x(e,t+" bytes for "+n):b(e+t,r.slice(e,e+t))})}function h(n,t){if("number"!=typeof(r=t)||Math.floor(r)!==r||t<0||t>6)throw new Error(n+" requires integer length in range [0, 6].");var r;}function d(n){return h("uintBE",n),p("uintBE("+n+")",n).map(function(t){return t.readUIntBE(0,n)})}function v(n){return h("uintLE",n),p("uintLE("+n+")",n).map(function(t){return t.readUIntLE(0,n)})}function g(n){return h("intBE",n),p("intBE("+n+")",n).map(function(t){return t.readIntBE(0,n)})}function m(n){return h("intLE",n),p("intLE("+n+")",n).map(function(t){return t.readIntLE(0,n)})}function y(n){return n instanceof e}function E(n){return "[object Array]"==={}.toString.call(n)}function w(n){return c()&&Buffer.isBuffer(n)}function b(n,t){return {status:!0,index:n,value:t,furthest:-1,expected:[]}}function x(n,t){return E(t)||(t=[t]),{status:!1,index:-1,value:null,furthest:n,expected:t}}function B(n,t){if(!t)return n;if(n.furthest>t.furthest)return n;var r=n.furthest===t.furthest?function(n,t){if(function(){if(void 0!==e._supportsSet)return e._supportsSet;var n="undefined"!=typeof Set;return e._supportsSet=n,n}()&&Array.from){for(var r=new Set(n),u=0;u=0;){if(i in r){e=r[i].line,0===o&&(o=r[i].lineStart);break}"\n"===n.charAt(i)&&(u++,0===o&&(o=i+1)),i--;}var f=e+u,a=t-o;return r[t]={line:f,lineStart:o},{offset:t,line:f+1,column:a+1}}function _(n){if(!y(n))throw new Error("not a parser: "+n)}function L(n,t){return "string"==typeof n?n.charAt(t):n[t]}function O(n){if("number"!=typeof n)throw new Error("not a number: "+n)}function k(n){if("function"!=typeof n)throw new Error("not a function: "+n)}function P(n){if("string"!=typeof n)throw new Error("not a string: "+n)}var q=2,A=3,I=8,F=5*I,M=4*I,z=" ";function R(n,t){return new Array(t+1).join(n)}function U(n,t,r){var e=t-n.length;return e<=0?n:R(r,e)+n}function W(n,t,r,e){return {from:n-t>0?n-t:0,to:n+r>e?e:n+r}}function D(n,t){var r,e,u,o,a,c=t.index,s=c.offset,l=1;if(s===n.length)return "Got the end of the input";if(w(n)){var p=s-s%I,h=s-p,d=W(p,F,M+I,n.length),v=f(function(n){return f(function(n){return U(n.toString(16),2,"0")},n)},function(n,t){var r=n.length,e=[],u=0;if(r<=t)return [n.slice()];for(var o=0;o=4&&(r+=1),l=2,u=f(function(n){return n.length<=4?n.join(" "):n.slice(0,4).join(" ")+" "+n.slice(4).join(" ")},v),(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2);}else {var g=n.split(/\r\n|[\n\r\u2028\u2029]/);r=c.column-1,e=c.line-1,o=W(e,q,A,g.length),u=g.slice(o.from,o.to),a=o.to.toString().length;}var m=e-o.from;return w(n)&&(a=(8*(o.to>0?o.to-1:o.to)).toString(16).length)<2&&(a=2),i(function(t,e,u){var i,f=u===m,c=f?"> ":z;return i=w(n)?U((8*(o.from+u)).toString(16),a,"0"):U((o.from+u+1).toString(),a," "),[].concat(t,[c+i+" | "+e],f?[z+R(" ",a)+" | "+U("",r," ")+R("^",l)]:[])},[],u).join("\n")}function N(n,t){return ["\n","-- PARSING FAILED "+R("-",50),"\n\n",D(n,t),"\n\n",(r=t.expected,1===r.length?"Expected:\n\n"+r[0]:"Expected one of the following: \n\n"+r.join(", ")),"\n"].join("");var r;}function G(n){return void 0!==n.flags?n.flags:[n.global?"g":"",n.ignoreCase?"i":"",n.multiline?"m":"",n.unicode?"u":"",n.sticky?"y":""].join("")}function C(){for(var n=[].slice.call(arguments),t=n.length,r=0;r=2?O(t):t=0;var r=function(n){return RegExp("^(?:"+n.source+")",G(n))}(n),u=""+n;return e(function(n,e){var o=r.exec(n.slice(e));if(o){if(0<=t&&t<=o.length){var i=o[0],f=o[t];return b(e+i.length,f)}return x(e,"valid match group (0 to "+o.length+") in "+u)}return x(e,u)})}function X(n){return e(function(t,r){return b(r,n)})}function Y(n){return e(function(t,r){return x(r,n)})}function Z(n){if(y(n))return e(function(t,r){var e=n._(t,r);return e.index=r,e.value="",e});if("string"==typeof n)return Z(K(n));if(n instanceof RegExp)return Z(Q(n));throw new Error("not a string, regexp, or parser: "+n)}function $(n){return _(n),e(function(t,r){var e=n._(t,r),u=t.slice(r,e.index);return e.status?x(r,'not "'+u+'"'):b(r,null)})}function nn(n){return k(n),e(function(t,r){var e=L(t,r);return r=n.length?x(t,"any character/byte"):b(t+1,L(n,t))}),on=e(function(n,t){return b(n.length,n.slice(t))}),fn=e(function(n,t){return t=0}).desc(t)},e.optWhitespace=pn,e.Parser=e,e.range=function(n,t){return nn(function(r){return n<=r&&r<=t}).desc(n+"-"+t)},e.regex=Q,e.regexp=Q,e.sepBy=V,e.sepBy1=H,e.seq=C,e.seqMap=J,e.seqObj=function(){for(var n,t={},r=0,u=(n=arguments,Array.prototype.slice.call(n)),o=u.length,i=0;i255)throw new Error("Value specified to byte constructor ("+n+"=0x"+n.toString(16)+") is larger in value than a single byte.");var t=(n>15?"0x":"0x0")+n.toString(16);return e(function(r,e){var u=L(r,e);return u===n?b(e+1,u):x(e,t)})},buffer:function(n){return p("buffer",n).map(function(n){return Buffer.from(n)})},encodedString:function(n,t){return p("string",t).map(function(t){return t.toString(n)})},uintBE:d,uint8BE:d(1),uint16BE:d(2),uint32BE:d(4),uintLE:v,uint8LE:v(1),uint16LE:v(2),uint32LE:v(4),intBE:g,int8BE:g(1),int16BE:g(2),int32BE:g(4),intLE:m,int8LE:m(1),int16LE:m(2),int32LE:m(4),floatBE:p("floatBE",4).map(function(n){return n.readFloatBE(0)}),floatLE:p("floatLE",4).map(function(n){return n.readFloatLE(0)}),doubleBE:p("doubleBE",8).map(function(n){return n.readDoubleBE(0)}),doubleLE:p("doubleLE",8).map(function(n){return n.readDoubleLE(0)})},n.exports=e;}])}); +}(parsimmon_umd_min)); + +var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports); + +let P = Parsimmon; + +class Grammar { + // General + InlineWhitespace = _ => P.regex(/[^\S\n]+/).desc("inline whitespace") + InlineOptWhitespace = _ => P.regex(/[^\S\n]*/).desc("inline optional whitespace") + WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline") + Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()") + None = _ => P.string("None").map(_ => new ObjectReferenceEntity({ type: "None", path: "" })).desc("none") + Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False") + Number = _ => P.regex(/[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number") + Integer = _ => P.regex(/[0-9]+/).map(v => new 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") + PathSymbolEntity = _ => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) + ReferencePath = r => P.seq(P.string("/"), r.PathSymbolEntity.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) + .tie() + .atLeast(2) + .tie() + .desc('a path (words with possibly underscore, separated by ".", separated by "/")') + Reference = r => P.alt( + r.None, + r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path })), + P.seqMap( + r.Word, + P.optWhitespace, + P.alt(P.string(`"`), P.string(`'"`)).chain( + result => r.ReferencePath.skip( + P.string(result.split("").reverse().join("")) + ) + ), + (referenceType, _, referencePath) => new ObjectReferenceEntity({ type: referenceType, path: referencePath }) + ) + ) + AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""') + AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText) + LocalizedText = r => P.seqMap( + P.string("NSLOCTEXT").skip(P.optWhitespace).skip(P.string("(")), + r.String.trim(P.optWhitespace), // namespace + P.string(","), + r.String.trim(P.optWhitespace), // key + P.string(","), + r.String.trim(P.optWhitespace), // value + P.string(")"), + (_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity({ + namespace: namespace, + key: key, + value: value + }) + ) + PinReference = r => P.seqMap( + r.PathSymbolEntity, + P.whitespace, + r.Guid, + (objectName, _, pinGuid) => new PinReferenceEntity({ + objectName: objectName, + pinGuid: pinGuid + }) + ) + static getGrammarForType(r, attributeType, defaultGrammar) { + switch (Utility.getType(attributeType)) { + case Boolean: + return r.Boolean + case Number: + return r.Number + case Integer: + return r.Integer + case String: + return r.String + case Guid: + return r.Guid + case ObjectReferenceEntity: + return r.Reference + case LocalizedTextEntity: + return r.LocalizedText + case PinReferenceEntity: + return r.PinReference + case FunctionReferenceEntity: + return r.FunctionReference + case PinEntity: + return r.Pin + case Array: + return P.seqMap( + P.string("("), + attributeType + .map(v => Grammar.getGrammarForType(r, Utility.getType(v))) + .reduce((accum, cur) => + !cur || accum === r.AttributeAnyValue + ? r.AttributeAnyValue + : accum.or(cur) + ) + .trim(P.optWhitespace) + .sepBy(P.string(",")) + .skip(P.regex(/,?\s*/)), + P.string(")"), + (_, grammar, __) => grammar + ) + default: + return defaultGrammar + } + } + // Meta grammar + static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) => + attributeGrammar.skip(valueSeparator) + .chain(attributeName => { + const attributeKey = attributeName.split("."); + const attribute = attributeSupplier(attributeKey); + let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue); + return attributeValueGrammar.map(attributeValue => + entity => Utility.objectSet(entity, attributeKey, attributeValue, true) + ) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value + }) + // Meta grammar + static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) => + /** + * Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)' + * Then it populates an object of type EntityType with the attribute values found inside the parentheses. + */ + P.seqMap( + P.seq(keyGrammar, P.optWhitespace, P.string("(")), + Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier) + .trim(P.optWhitespace) + .sepBy(P.string(",")) + .skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma + P.string(')'), + (_, attributes, __) => { + let result = new entityType(); + attributes.forEach(attributeSetter => attributeSetter(result)); + return result + }) + FunctionReference = r => Grammar.CreateMultiAttributeGrammar( + r, + P.succeed(), + FunctionReferenceEntity, + attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey) + ) + Pin = r => Grammar.CreateMultiAttributeGrammar( + r, + P.string("Pin"), + PinEntity, + attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey) + ) + CustomProperties = r => + P.string("CustomProperties") + .then(P.whitespace) + .then(r.Pin) + .map(pin => entity => { + /** @type {Array} */ + let properties = Utility.objectGet(entity, ["CustomProperties"], []); + properties.push(pin); + Utility.objectSet(entity, ["CustomProperties"], properties, true); + }) + + Object = r => P.seqMap( + P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace), + P + .alt( + r.CustomProperties, + Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey)) + ) + .sepBy1(P.whitespace), + P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")), + (_, attributes, __) => { + let result = new ObjectEntity(); + attributes.forEach(attributeSetter => attributeSetter(result)); + return result + } + ) + MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace) +} + +class Serializer { + + static grammar = Parsimmon.createLanguage(new Grammar()) + + constructor(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { + this.entityType = entityType; + this.prefix = prefix ?? ""; + this.separator = separator ?? ","; + this.trailingSeparator = trailingSeparator ?? false; + this.attributeValueConjunctionSign = attributeValueConjunctionSign ?? "="; + this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join(".")); + } + + writeValue(value) { + if (value === null) { + return "()" + } + const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v); + // This is an exact match (and not instanceof) to hit also primitive types (by accessing value.constructor they are converted to objects automatically) + switch (value?.constructor) { + case Function: + return this.writeValue(value()) + case Boolean: + return Utility.FirstCapital(value.toString()) + case Number: + return value.toString() + case String: + return `"${value}"` + } + if (value instanceof Array) { + return `(${value.map(v => serialize(v) + ",")})` + } + if (value instanceof Entity) { + return serialize(value) + } + 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 += (result.length ? this.separator : "") + + this.subWrite(fullKey, value); + } else if (this.showProperty(fullKey, value)) { + result += (result.length ? this.separator : "") + + this.prefix + + this.attributeKeyPrinter(fullKey) + + this.attributeValueConjunctionSign + + this.writeValue(value); + } + } + if (this.trailingSeparator && result.length && fullKey.length === 0) { + // append separator at the end if asked and there was printed content + result += this.separator; + } + return result + } + + showProperty(attributeKey, attributeValue) { + const attributes = this.entityType.attributes; + const attribute = Utility.objectGet(attributes, attributeKey); + if (attribute instanceof TypeInitialization) { + return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault + } + return true + } +} + +class ObjectSerializer extends Serializer { + + constructor() { + super(ObjectEntity, " ", "\n", false); + } + + showProperty(attributeKey, attributeValue) { + switch (attributeKey.toString()) { + case "Class": + case "Name": + case "CustomProperties": + // Serielized separately + return false + } + return super.showProperty(attributeKey, attributeValue) + } + + read(value) { + const parseResult = Serializer.grammar.Object.parse(value); + if (!parseResult.status) { + console.error("Error when trying to parse the object."); + return parseResult + } + return parseResult.value + } + + /** + * + * @param {String} value + * @returns {ObjectEntity[]} + */ + readMultiple(value) { + const parseResult = Serializer.grammar.MultipleObject.parse(value); + if (!parseResult.status) { + console.error("Error when trying to parse the object."); + return parseResult + } + return parseResult.value + } + + /** + * + * @param {ObjectEntity} object + * @returns + */ + write(object) { + let result = `Begin Object Class=${this.writeValue(object.Class)} Name=${this.writeValue(object.Name)} +${this.subWrite([], object) + + object + .CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin)) + .join("")} +End Object`; + return result + } +} + class Paste extends Context { constructor(target, blueprint, options = {}) { @@ -2142,6 +2118,42 @@ class Blueprint extends GraphElement { customElements.define('u-blueprint', Blueprint); +class GeneralSerializer extends Serializer { + + constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) { + super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter); + this.wrap = wrap ?? (v => `(${v})`); + } + + read(value) { + let grammar = Grammar.getGrammarForType(Serializer.grammar, this.entityType); + const parseResult = grammar.parse(value); + if (!parseResult.status) { + console.error("Error when trying to parse the entity " + this.entityType.prototype.constructor.name); + return parseResult + } + return parseResult.value + } + + write(object) { + let result = this.wrap(this.subWrite([], object)); + return result + } +} + +class CustomSerializer extends GeneralSerializer { + + constructor(objectWriter, entityType) { + super(undefined, entityType); + this.objectWriter = objectWriter; + } + + write(object) { + let result = this.objectWriter(object); + return result + } +} + class GraphLink extends GraphElement { /** @@ -2170,15 +2182,14 @@ class GraphLink extends GraphElement { customElements.define('u-link', GraphLink); -class CustomSerializer extends GeneralSerializer { +class ToStringSerializer extends GeneralSerializer { - constructor(objectWriter, entityType) { + constructor(entityType) { super(undefined, entityType); - this.objectWriter = objectWriter; } write(object) { - let result = this.objectWriter(object); + let result = object.toString(); return result } } @@ -2212,6 +2223,7 @@ SerializerFactory.registerSerializer( ? objectReference.type ? `'"${objectReference.path}"'` : objectReference.path : "" )) -); +); +SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity)); export { Blueprint, GraphLink, GraphNode }; diff --git a/ueblueprint.html b/index.html similarity index 100% rename from ueblueprint.html rename to index.html diff --git a/js/entity/PathSymbolEntity.js b/js/entity/PathSymbolEntity.js new file mode 100644 index 0000000..a902c9b --- /dev/null +++ b/js/entity/PathSymbolEntity.js @@ -0,0 +1,16 @@ +import Entity from "./Entity" + +export default class PathSymbolEntity extends Entity { + + static attributes = { + value: String + } + + getAttributes() { + return PathSymbolEntity.attributes + } + + toString() { + return this.value + } +} \ No newline at end of file diff --git a/js/entity/PinReferenceEntity.js b/js/entity/PinReferenceEntity.js index 2ab33e6..e071185 100755 --- a/js/entity/PinReferenceEntity.js +++ b/js/entity/PinReferenceEntity.js @@ -1,6 +1,6 @@ import Entity from "./Entity" import Guid from "./primitive/Guid" -import PathSymbol from "./primitive/PathSymbol" +import PathSymbol from "./PathSymbolEntity" export default class PinReferenceEntity extends Entity { diff --git a/js/entity/primitive/PathSymbol.js b/js/entity/primitive/PathSymbol.js deleted file mode 100644 index 2516bda..0000000 --- a/js/entity/primitive/PathSymbol.js +++ /dev/null @@ -1,17 +0,0 @@ -import Primitive from "./Primitive" - -export default class PathSymbol extends Primitive { - - constructor(value) { - super() - this.value = new String(value).valueOf() - } - - valueOf() { - this.value - } - - toString() { - return this.value - } -} \ No newline at end of file diff --git a/js/export.js b/js/export.js index b9d5d09..d5ef9fc 100755 --- a/js/export.js +++ b/js/export.js @@ -1,16 +1,18 @@ +import Blueprint from "./Blueprint" +import CustomSerializer from "./serialization/CustomSerializer" import FunctionReferenceEntity from "./entity/FunctionReferenceEntity" import GeneralSerializer from "./serialization/GeneralSerializer" +import GraphLink from "./graph/GraphLink" +import GraphNode from "./graph/GraphNode" import LocalizedTextEntity from "./entity/LocalizedTextEntity" import ObjectEntity from "./entity/ObjectEntity" -import ObjectSerializer from "./serialization/ObjectSerializer" -import PinEntity from "./entity/PinEntity" -import SerializerFactory from "./serialization/SerializerFactory" -import Blueprint from "./Blueprint" -import GraphNode from "./graph/GraphNode" -import GraphLink from "./graph/GraphLink" -import PinReferenceEntity from "./entity/PinReferenceEntity" import ObjectReferenceEntity from "./entity/ObjectReferenceEntity" -import CustomSerializer from "./serialization/CustomSerializer" +import ObjectSerializer from "./serialization/ObjectSerializer" +import PathSymbolEntity from "./entity/PathSymbolEntity" +import PinEntity from "./entity/PinEntity" +import PinReferenceEntity from "./entity/PinReferenceEntity" +import SerializerFactory from "./serialization/SerializerFactory" +import ToStringSerializer from "./serialization/ToStringSerializer" SerializerFactory.registerSerializer( ObjectEntity, @@ -42,5 +44,6 @@ SerializerFactory.registerSerializer( : "" )) ) +SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity)) export { Blueprint as Blueprint, GraphNode as GraphNode, GraphLink as GraphLink } \ No newline at end of file diff --git a/js/serialization/Grammar.js b/js/serialization/Grammar.js index 00fae05..74a3ec7 100755 --- a/js/serialization/Grammar.js +++ b/js/serialization/Grammar.js @@ -8,7 +8,7 @@ import Parsimmon from "parsimmon" import PinEntity from "../entity/PinEntity" import PinReferenceEntity from "../entity/PinReferenceEntity" import Utility from "../Utility" -import PathSymbol from "../entity/primitive/PathSymbol" +import PathSymbolEntity from "../entity/PathSymbolEntity" let P = Parsimmon @@ -25,8 +25,8 @@ export default class Grammar { 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_]+/).map(v => new PathSymbol(v)) - ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) + PathSymbolEntity = _ => P.regex(/[0-9a-zA-Z_]+/).map(v => new PathSymbolEntity({ value: v })) + ReferencePath = r => P.seq(P.string("/"), r.PathSymbolEntity.map(v => v.toString()).sepBy1(P.string(".")).tieWith(".")) .tie() .atLeast(2) .tie() @@ -62,7 +62,7 @@ export default class Grammar { }) ) PinReference = r => P.seqMap( - r.PathSymbol, + r.PathSymbolEntity, P.whitespace, r.Guid, (objectName, _, pinGuid) => new PinReferenceEntity({ diff --git a/js/serialization/ToStringSerializer.js b/js/serialization/ToStringSerializer.js new file mode 100644 index 0000000..6d55258 --- /dev/null +++ b/js/serialization/ToStringSerializer.js @@ -0,0 +1,13 @@ +import GeneralSerializer from "./GeneralSerializer" + +export default class ToStringSerializer extends GeneralSerializer { + + constructor(entityType) { + super(undefined, entityType) + } + + write(object) { + let result = object.toString() + return result + } +} \ No newline at end of file