New LinearColorEntity, string serialization fixed

This commit is contained in:
barsdeveloper
2022-05-11 21:01:54 +02:00
parent 3c643f0c6a
commit e416591784
20 changed files with 707 additions and 125 deletions

454
dist/ueblueprint.js vendored
View File

@@ -214,6 +214,33 @@ class IInput {
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
* @typedef {(new (object?: Object) => IEntity) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @typedef {Constructor|Constructor[]} AcceptedType
*/
class SerializedType {
/** @type {(Constructor|Array<Constructor>)[]} */
#types
get types() {
return this.#types
}
set types(v) {
this.#types = v;
}
/**
* @param {...AcceptedType} acceptedTypes
*/
constructor(...acceptedTypes) {
this.#types = acceptedTypes;
}
}
// @ts-check
/**
* @template T
*/
@@ -249,21 +276,21 @@ class TypeInitialization {
if (targetType === undefined) {
targetType = value?.constructor;
}
let wrongType = false;
if (targetType && value?.constructor !== targetType && !(value instanceof targetType)) {
wrongType = true;
if (
targetType
&& targetType !== SerializedType
&& !(value?.constructor === targetType || value instanceof targetType)
) {
value = new targetType(value);
}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
value = value.valueOf(); // Get the relative primitive value
}
if (wrongType) {
return new targetType(value)
}
return value
}
/**
* @typedef {(new () => T) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @typedef {(new () => T) | SerializedType | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @param {Constructor|Array<Constructor>} type
* @param {Boolean} showDefault
* @param {any} value
@@ -272,6 +299,8 @@ class TypeInitialization {
if (value === undefined) {
if (type instanceof Array) {
value = [];
} else if (type instanceof SerializedType) {
value = "";
} else {
value = TypeInitialization.sanitize(new type());
}
@@ -487,13 +516,30 @@ class Utility {
}
}
class ISerializable {
#showAsString = false
isShownAsString() {
return this.#showAsString
}
/**
* @param {Boolean} v
*/
setShowAsString(v) {
this.#showAsString = v;
}
}
// @ts-check
class IEntity {
class IEntity extends ISerializable {
static attributes = {}
constructor(values) {
super();
/**
* @param {Object} target
* @param {Object} properties
@@ -532,6 +578,7 @@ class IEntity {
const value = Utility.objectGet(values, [property]);
if (value !== undefined) {
target[property] = TypeInitialization.sanitize(value, defaultType);
// We have a value, need nothing more
continue
}
if (defaultValue instanceof TypeInitialization) {
@@ -722,6 +769,26 @@ class KeyBindingEntity extends IEntity {
// @ts-check
class LinearColorEntity extends IEntity {
static attributes = {
R: Number,
G: Number,
B: Number,
A: Number,
}
constructor(options = {}) {
super(options);
/** @type {Number} */ this.R;
/** @type {Number} */ this.G;
/** @type {Number} */ this.B;
/** @type {Number} */ this.A;
}
}
// @ts-check
class LocalizedTextEntity extends IEntity {
static lookbehind = "NSLOCTEXT"
@@ -802,7 +869,7 @@ class PinEntity extends IEntity {
bSerializeAsSinglePrecisionFloat: false,
},
LinkedTo: new TypeInitialization([PinReferenceEntity], false),
DefaultValue: new TypeInitialization(String, false),
DefaultValue: new TypeInitialization(new SerializedType(LinearColorEntity, String), false),
AutogeneratedDefaultValue: new TypeInitialization(String, false),
DefaultObject: new TypeInitialization(ObjectReferenceEntity, false, null),
PersistentGuid: GuidEntity,
@@ -912,6 +979,14 @@ class PinEntity extends IEntity {
getType() {
return this.PinType.PinCategory
}
getSubCategory() {
return this.PinType.PinSubCategoryObject.path
}
getColorValue() {
if (this.PinType.PinSubCategoryObject.path == "/Script/CoreUObject.LinearColor") ;
}
}
// @ts-check
@@ -1023,6 +1098,10 @@ var Parsimmon = /*@__PURE__*/getDefaultExportFromCjs(parsimmon_umd_min.exports);
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
let P = Parsimmon;
class Grammar {
@@ -1032,6 +1111,27 @@ class Grammar {
static getGrammarForType(r, attributeType, defaultGrammar) {
if (attributeType instanceof TypeInitialization) {
attributeType = attributeType.type;
return Grammar.getGrammarForType(r, attributeType, defaultGrammar)
}
if (attributeType instanceof SerializedType) {
const noStringTypes = attributeType.types.filter(t => t !== String);
let result = P.alt(
...noStringTypes.map(t =>
Grammar.getGrammarForType(r, t).wrap(P.string('"'), P.string('"')).map(
/**
* @param {IEntity} entity
*/
entity => {
entity.setShowAsString(true); // Showing as string because it is inside a SerializedType
return entity
}
)
)
);
if (noStringTypes.length < attributeType.types.length) {
result = result.or(r.String); // Separated because it cannot be wrapped into " and "
}
return result
}
switch (Utility.getType(attributeType)) {
case Boolean:
@@ -1054,6 +1154,8 @@ class Grammar {
return r.InvariantText
case PinReferenceEntity:
return r.PinReference
case LinearColorEntity:
return r.LinearColor
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
@@ -1170,7 +1272,7 @@ class Grammar {
r.None,
...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))]
.flatMap(referencePath => [
referencePath, // version having just path
referencePath, // Version having just path
referencePath.trim(P.string('"')) // Version having path surround with double quotes
]),
P.seqMap(
@@ -1229,6 +1331,8 @@ class Grammar {
})
)
LinearColor = r => Grammar.createMultiAttributeGrammar(r, LinearColorEntity)
FunctionReference = r => Grammar.createMultiAttributeGrammar(r, FunctionReferenceEntity)
KeyBinding = r => P.alt(
@@ -1289,6 +1393,9 @@ class SerializerFactory {
// @ts-check
/**
* @template {IEntity} T
*/
class ISerializer {
static grammar = Parsimmon.createLanguage(new Grammar())
@@ -1302,21 +1409,66 @@ class ISerializer {
this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join("."));
}
writeValue(value, fullKey = undefined) {
/**
* @param {String} value
* @returns {T}
*/
deserialize(value) {
return this.read(value)
}
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
serialize(object, insideString) {
insideString ||= object.isShownAsString();
let result = this.write(object, insideString);
if (object.isShownAsString()) {
result = `"${result}"`;
}
return result
}
/**
* @param {String} value
* @returns {T}
*/
read(value) {
throw new Error("Not implemented")
}
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString) {
throw new Error("Not implemented")
}
/**
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value === null) {
return "()"
}
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v);
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).serialize(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(), fullKey)
return this.writeValue(value(), fullKey, insideString)
case Boolean:
return Utility.FirstCapital(value.toString())
case Number:
return value.toString()
case String:
return `"${Utility.encodeString(value)}"`
return insideString
? `\\"${Utility.encodeString(value)}\\"`
: `"${Utility.encodeString(value)}"`
}
if (value instanceof Array) {
return `(${value.map(v => serialize(v) + ",").join("")})`
@@ -1329,25 +1481,26 @@ class ISerializer {
/**
* @param {String[]} key
* @param {Object} object
* @param {Boolean} insideString
* @returns {String}
*/
subWrite(key, object) {
subWrite(key, object, insideString) {
let result = "";
let fullKey = key.concat("");
const last = fullKey.length - 1;
for (const property of Object.getOwnPropertyNames(object)) {
fullKey[last] = property;
const value = object[property];
if (object[property]?.constructor === Object) {
if (value?.constructor === Object) {
// Recursive call when finding an object
result += (result.length ? this.separator : "")
+ this.subWrite(fullKey, value);
+ this.subWrite(fullKey, value, insideString);
} else if (value !== undefined && this.showProperty(object, fullKey, value)) {
result += (result.length ? this.separator : "")
+ this.prefix
+ this.attributeKeyPrinter(fullKey)
+ this.attributeValueConjunctionSign
+ this.writeValue(value, fullKey);
+ this.writeValue(value, fullKey, insideString);
}
}
if (this.trailingSeparator && result.length && fullKey.length === 1) {
@@ -1386,6 +1539,9 @@ class ObjectSerializer extends ISerializer {
return super.showProperty(object, attributeKey, attributeValue)
}
/**
* @param {String} value
*/
read(value) {
const parseResult = ISerializer.grammar.Object.parse(value);
if (!parseResult.status) {
@@ -1396,7 +1552,6 @@ class ObjectSerializer extends ISerializer {
/**
* @param {String} value
* @returns {ObjectEntity[]}
*/
readMultiple(value) {
const parseResult = ISerializer.grammar.MultipleObject.parse(value);
@@ -1408,16 +1563,17 @@ class ObjectSerializer extends ISerializer {
/**
* @param {ObjectEntity} object
* @param {Boolean} insideString
*/
write(object) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, "Name")}
${this.subWrite([], object)
write(object, insideString) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, ["Name"], insideString)}
${this.subWrite([], object, insideString)
+ object
.CustomProperties.map(pin =>
this.separator
+ this.prefix
+ "CustomProperties "
+ SerializerFactory.getSerializer(PinEntity).write(pin)
+ SerializerFactory.getSerializer(PinEntity).serialize(pin)
)
.join("")}
End Object\n`;
@@ -1450,7 +1606,7 @@ class Copy extends IInput {
}
copied() {
const value = this.blueprint.getNodes(true).map(node => this.serializer.write(node.entity)).join("\n");
const value = this.blueprint.getNodes(true).map(node => this.serializer.serialize(node.entity)).join("\n");
navigator.clipboard.writeText(value);
}
}
@@ -3101,6 +3257,65 @@ class BoolPinTemplate extends IInputPinTemplate {
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
class ColorPinTemplate extends IInputPinTemplate {
/** @type {HTMLInputElement} */
#input
/**
* @param {PinElement} pin
*/
setup(pin) {
super.setup(pin);
this.#input = pin.querySelector(".ueb-pin-input");
let self = this;
this.onChangeHandler = _ => pin.entity.DefaultValue = self.#input.checked ? "true" : "false";
this.#input.addEventListener("change", this.onChangeHandler);
}
/**
* @param {PinElement} pin
*/
cleanup(pin) {
super.cleanup(pin);
this.#input.removeEventListener("change", this.onChangeHandler);
}
/**
* @param {PinElement} pin
*/
getInputs(pin) {
return [this.#input.checked ? "true" : "false"]
}
/**
* @param {PinElement} pin
* @param {String[]?} value
*/
setInputs(pin, value = []) {
pin.entity.DefaultValue = value.length ? value[0] : this.getInput(pin);
this.#input.checked = pin.entity.DefaultValue == "true";
}
/**
* @param {PinElement} pin
*/
renderInput(pin) {
if (pin.isInput()) {
return html`
<span class="ueb-pin-input" ${pin.entity.DefaultValue == "true" ? "checked" : ""}></span>
`
}
return super.renderInput(pin)
}
}
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
@@ -3133,51 +3348,6 @@ class ExecPinTemplate extends PinTemplate {
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
class RealPinTemplate extends IInputPinTemplate {
/**
* @param {PinElement} pin
* @param {String[]?} values
*/
setInputs(pin, values = []) {
let num = parseFloat(values.length ? values[0] : this.getInput(pin));
let updateDefaultValue = true;
if (isNaN(num)) {
num = parseFloat(pin.entity.DefaultValue != ""
? pin.entity.DefaultValue
: pin.entity.AutogeneratedDefaultValue);
}
if (isNaN(num)) {
num = 0;
updateDefaultValue = false;
}
values[0] = Utility.minDecimals(num);
super.setInputs(pin, values, updateDefaultValue);
}
}
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
class StringPinTemplate extends IInputPinTemplate {
/**
* @param {PinElement} pin
*/
setup(pin) {
super.setup(pin);
}
}
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
@@ -3230,6 +3400,51 @@ class NamePinTemplate extends IInputPinTemplate {
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
class RealPinTemplate extends IInputPinTemplate {
/**
* @param {PinElement} pin
* @param {String[]?} values
*/
setInputs(pin, values = []) {
let num = parseFloat(values.length ? values[0] : this.getInput(pin));
let updateDefaultValue = true;
if (isNaN(num)) {
num = parseFloat(pin.entity.DefaultValue != ""
? pin.entity.DefaultValue
: pin.entity.AutogeneratedDefaultValue);
}
if (isNaN(num)) {
num = 0;
updateDefaultValue = false;
}
values[0] = Utility.minDecimals(num);
super.setInputs(pin, values, updateDefaultValue);
}
}
// @ts-check
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
class StringPinTemplate extends IInputPinTemplate {
/**
* @param {PinElement} pin
*/
setup(pin) {
super.setup(pin);
}
}
// @ts-check
/**
* @typedef {import("../entity/GuidEntity").default} GuidEntity
* @typedef {import("../entity/PinEntity").default} PinEntity
@@ -3248,6 +3463,21 @@ class PinElement extends IElement {
"name": NamePinTemplate,
"real": RealPinTemplate,
"string": StringPinTemplate,
"struct": {
"/Script/CoreUObject.LinearColor": ColorPinTemplate,
}
}
/**
* @param {PinEntity} pinEntity
* @return {PinTemplate}
*/
static getTypeTemplate(pinEntity) {
let result = PinElement.#typeTemplateMap[pinEntity.getType()];
if (result.constructor === Object) {
result = result[pinEntity.getSubCategory()];
}
return result ?? PinTemplate
}
#color = ""
@@ -3266,7 +3496,8 @@ class PinElement extends IElement {
constructor(entity) {
super(
entity,
new (PinElement.#typeTemplateMap[entity.getType()] ?? PinTemplate)()
// @ts-expect-error
new (PinElement.getTypeTemplate(entity))()
);
}
@@ -3636,7 +3867,7 @@ class NodeElement extends ISelectableDraggableElement {
*/
static fromSerializedObject(str) {
str = str.trim();
let entity = SerializerFactory.getSerializer(ObjectEntity).read(str);
let entity = SerializerFactory.getSerializer(ObjectEntity).deserialize(str);
return new NodeElement(entity)
}
@@ -4854,8 +5085,18 @@ customElements.define("ueb-blueprint", Blueprint);
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
class GeneralSerializer extends ISerializer {
/**
* @param {new () => T} entityType
*/
constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) {
wrap = wrap ?? (v => `(${v})`);
super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter);
@@ -4863,7 +5104,6 @@ class GeneralSerializer extends ISerializer {
}
/**
* @template T
* @param {String} value
* @returns {T}
*/
@@ -4877,27 +5117,42 @@ class GeneralSerializer extends ISerializer {
}
/**
* @template T
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object) {
let result = this.wrap(this.subWrite([], object));
write(object, insideString = false) {
let result = this.wrap(this.subWrite([], object, insideString));
return result
}
}
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
class CustomSerializer extends GeneralSerializer {
/**
* @param {new () => T} entityType
*/
constructor(objectWriter, entityType) {
super(undefined, entityType);
this.objectWriter = objectWriter;
}
write(object) {
let result = this.objectWriter(object);
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString = false) {
let result = this.objectWriter(object, insideString);
return result
}
}
@@ -4910,25 +5165,45 @@ class PinSerializer extends GeneralSerializer {
super(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true);
}
writeValue(value, fullKey) {
if (value?.constructor === String && fullKey == "DefaultValue") {
/**
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value?.constructor === String && fullKey.length == 1 && fullKey[0] == "DefaultValue") {
// @ts-expect-error
return `"${Utility.encodeInputString(value)}"`
}
return super.writeValue(value, fullKey)
return super.writeValue(value, fullKey, insideString)
}
}
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
class ToStringSerializer extends GeneralSerializer {
/**
* @param {new () => T} entityType
*/
constructor(entityType) {
super(undefined, entityType);
}
write(object) {
let result = object.toString();
/**
* @param {T} object
* @param {Boolean} insideString
*/
write(object, insideString) {
let result = insideString || object.isShownAsString()
? `"${object.toString().replaceAll('"', '\\"')}"`
: object.toString();
return result
}
}
@@ -4937,6 +5212,13 @@ class ToStringSerializer extends GeneralSerializer {
function initializeSerializerFactory() {
const bracketsWrapped = v => `(${v})`;
SerializerFactory.registerSerializer(
LinearColorEntity,
new GeneralSerializer(bracketsWrapped, LinearColorEntity)
);
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
@@ -4949,12 +5231,12 @@ function initializeSerializerFactory() {
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false)
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
);
SerializerFactory.registerSerializer(
KeyBindingEntity,
new GeneralSerializer(v => `(${v})`, KeyBindingEntity, "", ",", false)
new GeneralSerializer(bracketsWrapped, KeyBindingEntity)
);
SerializerFactory.registerSerializer(
@@ -4980,7 +5262,9 @@ function initializeSerializerFactory() {
objectReference.path
? objectReference.type ? `'"${objectReference.path}"'` : `"${objectReference.path}"`
: ""
))
),
ObjectReferenceEntity
)
);
SerializerFactory.registerSerializer(IdentifierEntity, new ToStringSerializer(IdentifierEntity));

View File

@@ -28,7 +28,7 @@ export default class NodeElement extends ISelectableDraggableElement {
*/
static fromSerializedObject(str) {
str = str.trim()
let entity = SerializerFactory.getSerializer(ObjectEntity).read(str)
let entity = SerializerFactory.getSerializer(ObjectEntity).deserialize(str)
return new NodeElement(entity)
}

View File

@@ -1,14 +1,15 @@
// @ts-check
import BoolPinTemplate from "../template/BoolPinTemplate"
import ColorPinTemplate from "../template/ColorPinTemplate"
import ExecPinTemplate from "../template/ExecPinTemplate"
import IElement from "./IElement"
import LinkElement from "./LinkElement"
import NamePinTemplate from "../template/NamePinTemplate"
import PinTemplate from "../template/PinTemplate"
import RealPinTemplate from "../template/RealPinTemplate"
import StringPinTemplate from "../template/StringPinTemplate"
import Utility from "../Utility"
import NamePinTemplate from "../template/NamePinTemplate"
/**
* @typedef {import("../entity/GuidEntity").default} GuidEntity
@@ -28,6 +29,21 @@ export default class PinElement extends IElement {
"name": NamePinTemplate,
"real": RealPinTemplate,
"string": StringPinTemplate,
"struct": {
"/Script/CoreUObject.LinearColor": ColorPinTemplate,
}
}
/**
* @param {PinEntity} pinEntity
* @return {PinTemplate}
*/
static getTypeTemplate(pinEntity) {
let result = PinElement.#typeTemplateMap[pinEntity.getType()]
if (result.constructor === Object) {
result = result[pinEntity.getSubCategory()]
}
return result ?? PinTemplate
}
#color = ""
@@ -46,7 +62,8 @@ export default class PinElement extends IElement {
constructor(entity) {
super(
entity,
new (PinElement.#typeTemplateMap[entity.getType()] ?? PinTemplate)()
// @ts-expect-error
new (PinElement.getTypeTemplate(entity))()
)
}

View File

@@ -1,13 +1,15 @@
// @ts-check
import ISerializable from "./ISerializable"
import TypeInitialization from "./TypeInitialization"
import Utility from "../Utility"
export default class IEntity {
export default class IEntity extends ISerializable {
static attributes = {}
constructor(values) {
super()
/**
* @param {Object} target
* @param {Object} properties
@@ -46,6 +48,7 @@ export default class IEntity {
const value = Utility.objectGet(values, [property])
if (value !== undefined) {
target[property] = TypeInitialization.sanitize(value, defaultType)
// We have a value, need nothing more
continue
}
if (defaultValue instanceof TypeInitialization) {

View File

@@ -0,0 +1,15 @@
export default class ISerializable {
#showAsString = false
isShownAsString() {
return this.#showAsString
}
/**
* @param {Boolean} v
*/
setShowAsString(v) {
this.#showAsString = v
}
}

View File

@@ -0,0 +1,21 @@
// @ts-check
import IEntity from "./IEntity"
export default class LinearColorEntity extends IEntity {
static attributes = {
R: Number,
G: Number,
B: Number,
A: Number,
}
constructor(options = {}) {
super(options)
/** @type {Number} */ this.R
/** @type {Number} */ this.G
/** @type {Number} */ this.B
/** @type {Number} */ this.A
}
}

View File

@@ -2,9 +2,11 @@
import GuidEntity from "./GuidEntity"
import IEntity from "./IEntity"
import LinearColorEntity from "./LinearColorEntity"
import LocalizedTextEntity from "./LocalizedTextEntity"
import ObjectReferenceEntity from "./ObjectReferenceEntity"
import PinReferenceEntity from "./PinReferenceEntity"
import SerializedType from "./SerializedType"
import TypeInitialization from "./TypeInitialization"
export default class PinEntity extends IEntity {
@@ -30,7 +32,7 @@ export default class PinEntity extends IEntity {
bSerializeAsSinglePrecisionFloat: false,
},
LinkedTo: new TypeInitialization([PinReferenceEntity], false),
DefaultValue: new TypeInitialization(String, false),
DefaultValue: new TypeInitialization(new SerializedType(LinearColorEntity, String), false),
AutogeneratedDefaultValue: new TypeInitialization(String, false),
DefaultObject: new TypeInitialization(ObjectReferenceEntity, false, null),
PersistentGuid: GuidEntity,
@@ -140,4 +142,14 @@ export default class PinEntity extends IEntity {
getType() {
return this.PinType.PinCategory
}
getSubCategory() {
return this.PinType.PinSubCategoryObject.path
}
getColorValue() {
if (this.PinType.PinSubCategoryObject.path == "/Script/CoreUObject.LinearColor") {
}
}
}

View File

@@ -0,0 +1,26 @@
// @ts-check
/**
* @typedef {import("../entity/IEntity").default} IEntity
* @typedef {(new (object?: Object) => IEntity) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @typedef {Constructor|Constructor[]} AcceptedType
*/
export default class SerializedType {
/** @type {(Constructor|Array<Constructor>)[]} */
#types
get types() {
return this.#types
}
set types(v) {
this.#types = v
}
/**
* @param {...AcceptedType} acceptedTypes
*/
constructor(...acceptedTypes) {
this.#types = acceptedTypes
}
}

View File

@@ -1,5 +1,7 @@
// @ts-check
import SerializedType from "./SerializedType"
/**
* @template T
*/
@@ -36,20 +38,21 @@ export default class TypeInitialization {
targetType = value?.constructor
}
let wrongType = false
if (targetType && value?.constructor !== targetType && !(value instanceof targetType)) {
wrongType = true
if (
targetType
&& targetType !== SerializedType
&& !(value?.constructor === targetType || value instanceof targetType)
) {
value = new targetType(value)
}
if (value instanceof Boolean || value instanceof Number || value instanceof String) {
value = value.valueOf() // Get the relative primitive value
}
if (wrongType) {
return new targetType(value)
}
return value
}
/**
* @typedef {(new () => T) | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @typedef {(new () => T) | SerializedType | StringConstructor | NumberConstructor | BooleanConstructor} Constructor
* @param {Constructor|Array<Constructor>} type
* @param {Boolean} showDefault
* @param {any} value
@@ -58,6 +61,8 @@ export default class TypeInitialization {
if (value === undefined) {
if (type instanceof Array) {
value = []
} else if (type instanceof SerializedType) {
value = ""
} else {
value = TypeInitialization.sanitize(new type())
}

View File

@@ -26,7 +26,7 @@ export default class Copy extends IInput {
}
copied() {
const value = this.blueprint.getNodes(true).map(node => this.serializer.write(node.entity)).join("\n")
const value = this.blueprint.getNodes(true).map(node => this.serializer.serialize(node.entity, false)).join("\n")
navigator.clipboard.writeText(value)
}
}

View File

@@ -2,15 +2,30 @@
import GeneralSerializer from "./GeneralSerializer"
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
export default class CustomSerializer extends GeneralSerializer {
/**
* @param {new () => T} entityType
*/
constructor(objectWriter, entityType) {
super(undefined, entityType)
this.objectWriter = objectWriter
}
write(object) {
let result = this.objectWriter(object)
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString = false) {
let result = this.objectWriter(object, insideString)
return result
}
}

View File

@@ -3,8 +3,18 @@
import Grammar from "./Grammar"
import ISerializer from "./ISerializer"
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
export default class GeneralSerializer extends ISerializer {
/**
* @param {new () => T} entityType
*/
constructor(wrap, entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter) {
wrap = wrap ?? (v => `(${v})`)
super(entityType, prefix, separator, trailingSeparator, attributeValueConjunctionSign, attributeKeyPrinter)
@@ -12,7 +22,6 @@ export default class GeneralSerializer extends ISerializer {
}
/**
* @template T
* @param {String} value
* @returns {T}
*/
@@ -26,12 +35,12 @@ export default class GeneralSerializer extends ISerializer {
}
/**
* @template T
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object) {
let result = this.wrap(this.subWrite([], object))
write(object, insideString = false) {
let result = this.wrap(this.subWrite([], object, insideString))
return result
}
}

View File

@@ -6,6 +6,7 @@ import IdentifierEntity from "../entity/IdentifierEntity"
import IntegerEntity from "../entity/IntegerEntity"
import InvariantTextEntity from "../entity/InvariantTextEntity"
import KeyBindingEntity from "../entity/KeyBindingEntity"
import LinearColorEntity from "../entity/LinearColorEntity"
import LocalizedTextEntity from "../entity/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
@@ -13,9 +14,14 @@ import Parsimmon from "parsimmon"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import SerializedType from "../entity/SerializedType"
import TypeInitialization from "../entity/TypeInitialization"
import Utility from "../Utility"
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
let P = Parsimmon
export default class Grammar {
@@ -25,6 +31,27 @@ export default class Grammar {
static getGrammarForType(r, attributeType, defaultGrammar) {
if (attributeType instanceof TypeInitialization) {
attributeType = attributeType.type
return Grammar.getGrammarForType(r, attributeType, defaultGrammar)
}
if (attributeType instanceof SerializedType) {
const noStringTypes = attributeType.types.filter(t => t !== String)
let result = P.alt(
...noStringTypes.map(t =>
Grammar.getGrammarForType(r, t).wrap(P.string('"'), P.string('"')).map(
/**
* @param {IEntity} entity
*/
entity => {
entity.setShowAsString(true) // Showing as string because it is inside a SerializedType
return entity
}
)
)
)
if (noStringTypes.length < attributeType.types.length) {
result = result.or(r.String) // Separated because it cannot be wrapped into " and "
}
return result
}
switch (Utility.getType(attributeType)) {
case Boolean:
@@ -47,6 +74,8 @@ export default class Grammar {
return r.InvariantText
case PinReferenceEntity:
return r.PinReference
case LinearColorEntity:
return r.LinearColor
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
@@ -163,7 +192,7 @@ export default class Grammar {
r.None,
...[r.ReferencePath.map(path => new ObjectReferenceEntity({ type: "", path: path }))]
.flatMap(referencePath => [
referencePath, // version having just path
referencePath, // Version having just path
referencePath.trim(P.string('"')) // Version having path surround with double quotes
]),
P.seqMap(
@@ -222,6 +251,8 @@ export default class Grammar {
})
)
LinearColor = r => Grammar.createMultiAttributeGrammar(r, LinearColorEntity)
FunctionReference = r => Grammar.createMultiAttributeGrammar(r, FunctionReferenceEntity)
KeyBinding = r => P.alt(

View File

@@ -7,6 +7,9 @@ import SerializerFactory from "./SerializerFactory"
import TypeInitialization from "../entity/TypeInitialization"
import Utility from "../Utility"
/**
* @template {IEntity} T
*/
export default class ISerializer {
static grammar = Parsimmon.createLanguage(new Grammar())
@@ -20,21 +23,66 @@ export default class ISerializer {
this.attributeKeyPrinter = attributeKeyPrinter ?? (k => k.join("."))
}
writeValue(value, fullKey = undefined) {
/**
* @param {String} value
* @returns {T}
*/
deserialize(value) {
return this.read(value)
}
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
serialize(object, insideString) {
insideString ||= object.isShownAsString()
let result = this.write(object, insideString)
if (object.isShownAsString()) {
result = `"${result}"`
}
return result
}
/**
* @param {String} value
* @returns {T}
*/
read(value) {
throw new Error("Not implemented")
}
/**
* @param {T} object
* @param {Boolean} insideString
* @returns {String}
*/
write(object, insideString) {
throw new Error("Not implemented")
}
/**
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value === null) {
return "()"
}
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).write(v)
const serialize = v => SerializerFactory.getSerializer(Utility.getType(v)).serialize(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(), fullKey)
return this.writeValue(value(), fullKey, insideString)
case Boolean:
return Utility.FirstCapital(value.toString())
case Number:
return value.toString()
case String:
return `"${Utility.encodeString(value)}"`
return insideString
? `\\"${Utility.encodeString(value)}\\"`
: `"${Utility.encodeString(value)}"`
}
if (value instanceof Array) {
return `(${value.map(v => serialize(v) + ",").join("")})`
@@ -47,25 +95,26 @@ export default class ISerializer {
/**
* @param {String[]} key
* @param {Object} object
* @param {Boolean} insideString
* @returns {String}
*/
subWrite(key, object) {
subWrite(key, object, insideString) {
let result = ""
let fullKey = key.concat("")
const last = fullKey.length - 1
for (const property of Object.getOwnPropertyNames(object)) {
fullKey[last] = property
const value = object[property]
if (object[property]?.constructor === Object) {
if (value?.constructor === Object) {
// Recursive call when finding an object
result += (result.length ? this.separator : "")
+ this.subWrite(fullKey, value)
+ this.subWrite(fullKey, value, insideString)
} else if (value !== undefined && this.showProperty(object, fullKey, value)) {
result += (result.length ? this.separator : "")
+ this.prefix
+ this.attributeKeyPrinter(fullKey)
+ this.attributeValueConjunctionSign
+ this.writeValue(value, fullKey)
+ this.writeValue(value, fullKey, insideString)
}
}
if (this.trailingSeparator && result.length && fullKey.length === 1) {

View File

@@ -22,6 +22,9 @@ export default class ObjectSerializer extends ISerializer {
return super.showProperty(object, attributeKey, attributeValue)
}
/**
* @param {String} value
*/
read(value) {
const parseResult = ISerializer.grammar.Object.parse(value)
if (!parseResult.status) {
@@ -32,7 +35,6 @@ export default class ObjectSerializer extends ISerializer {
/**
* @param {String} value
* @returns {ObjectEntity[]}
*/
readMultiple(value) {
const parseResult = ISerializer.grammar.MultipleObject.parse(value)
@@ -44,16 +46,17 @@ export default class ObjectSerializer extends ISerializer {
/**
* @param {ObjectEntity} object
* @param {Boolean} insideString
*/
write(object) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, "Name")}
${this.subWrite([], object)
write(object, insideString) {
let result = `Begin Object Class=${object.Class.path} Name=${this.writeValue(object.Name, ["Name"], insideString)}
${this.subWrite([], object, insideString)
+ object
.CustomProperties.map(pin =>
this.separator
+ this.prefix
+ "CustomProperties "
+ SerializerFactory.getSerializer(PinEntity).write(pin)
+ SerializerFactory.getSerializer(PinEntity).serialize(pin)
)
.join("")}
End Object\n`

View File

@@ -10,11 +10,15 @@ export default class PinSerializer extends GeneralSerializer {
super(v => `${PinEntity.lookbehind} (${v})`, PinEntity, "", ",", true)
}
writeValue(value, fullKey) {
if (value?.constructor === String && fullKey == "DefaultValue") {
/**
* @param {String[]} fullKey
* @param {Boolean} insideString
*/
writeValue(value, fullKey, insideString) {
if (value?.constructor === String && fullKey.length == 1 && fullKey[0] == "DefaultValue") {
// @ts-expect-error
return `"${Utility.encodeInputString(value)}"`
}
return super.writeValue(value, fullKey)
return super.writeValue(value, fullKey, insideString)
}
}

View File

@@ -2,14 +2,30 @@
import GeneralSerializer from "./GeneralSerializer"
/**
* @typedef {import("../entity/IEntity").default} IEntity
*/
/**
* @template {IEntity} T
*/
export default class ToStringSerializer extends GeneralSerializer {
/**
* @param {new () => T} entityType
*/
constructor(entityType) {
super(undefined, entityType)
}
write(object) {
let result = object.toString()
/**
* @param {T} object
* @param {Boolean} insideString
*/
write(object, insideString) {
let result = insideString || object.isShownAsString()
? `"${object.toString().replaceAll('"', '\\"')}"`
: object.toString()
return result
}
}

View File

@@ -8,6 +8,7 @@ import IdentifierEntity from "../entity/IdentifierEntity"
import IntegerEntity from "../entity/IntegerEntity"
import InvariantTextEntity from "../entity/InvariantTextEntity"
import KeyBindingEntity from "../entity/KeyBindingEntity"
import LinearColorEntity from "../entity/LinearColorEntity"
import LocalizedTextEntity from "../entity/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
@@ -21,6 +22,13 @@ import ToStringSerializer from "./ToStringSerializer"
export default function initializeSerializerFactory() {
const bracketsWrapped = v => `(${v})`
SerializerFactory.registerSerializer(
LinearColorEntity,
new GeneralSerializer(bracketsWrapped, LinearColorEntity)
)
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
@@ -33,12 +41,12 @@ export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false)
new GeneralSerializer(bracketsWrapped, FunctionReferenceEntity)
)
SerializerFactory.registerSerializer(
KeyBindingEntity,
new GeneralSerializer(v => `(${v})`, KeyBindingEntity, "", ",", false)
new GeneralSerializer(bracketsWrapped, KeyBindingEntity)
)
SerializerFactory.registerSerializer(
@@ -64,7 +72,9 @@ export default function initializeSerializerFactory() {
objectReference.path
? objectReference.type ? `'"${objectReference.path}"'` : `"${objectReference.path}"`
: ""
))
),
ObjectReferenceEntity
)
)
SerializerFactory.registerSerializer(IdentifierEntity, new ToStringSerializer(IdentifierEntity))

View File

@@ -0,0 +1,61 @@
// @ts-check
import html from "./html"
import IInputPinTemplate from "./IInputPinTemplate"
/**
* @typedef {import("../element/PinElement").default} PinElement
*/
export default class ColorPinTemplate extends IInputPinTemplate {
/** @type {HTMLInputElement} */
#input
/**
* @param {PinElement} pin
*/
setup(pin) {
super.setup(pin)
this.#input = pin.querySelector(".ueb-pin-input")
let self = this
this.onChangeHandler = _ => pin.entity.DefaultValue = self.#input.checked ? "true" : "false"
this.#input.addEventListener("change", this.onChangeHandler)
}
/**
* @param {PinElement} pin
*/
cleanup(pin) {
super.cleanup(pin)
this.#input.removeEventListener("change", this.onChangeHandler)
}
/**
* @param {PinElement} pin
*/
getInputs(pin) {
return [this.#input.checked ? "true" : "false"]
}
/**
* @param {PinElement} pin
* @param {String[]?} value
*/
setInputs(pin, value = []) {
pin.entity.DefaultValue = value.length ? value[0] : this.getInput(pin)
this.#input.checked = pin.entity.DefaultValue == "true"
}
/**
* @param {PinElement} pin
*/
renderInput(pin) {
if (pin.isInput()) {
return html`
<span class="ueb-pin-input" ${pin.entity.DefaultValue == "true" ? "checked" : ""}></span>
`
}
return super.renderInput(pin)
}
}

View File

@@ -32,6 +32,7 @@
"terser": "^5.9.0"
},
"dependencies": {
"@easylogic/colorpicker": "^1.10.11",
"parsimmon": "^1.18.0"
}
}