Merge remote-tracking branch 'origin/parse-performance'

This commit is contained in:
barsdeveloper
2023-03-02 19:44:53 +01:00
17 changed files with 5037 additions and 4905 deletions

7
.vscode/launch.json vendored
View File

@@ -4,13 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch index.html",
"type": "firefox",
"request": "launch",
"reAttach": true,
"file": "${workspaceFolder}/index.html"
},
{
"name": "Launch localhost",
"type": "firefox",

View File

@@ -3,7 +3,7 @@ Getting started with the development of this application is very easy because it
Before starting, the gentle reader might want to make sure to be familiar with the [Lit](https://lit.dev/) library and its element [lifecycle](https://lit.dev/docs/components/lifecycle/). This library is used extensively throught the application to keep the HTML elements in sync with the data and avoid updating the DOM too often. The original author is aware that there are way more popular alternatives out there like React, Vue and Svelte, but the design of Lit fits very well into the original design of this application: vanilla JavaScript and object-oriented. This allowed the introduction of Lit with a relatively small amount of changes to the existing code, yes because the decision to use Lit was made at a later point in time. One important detail is that it does not make use of the shadow DOM (part of the Web Components), the real reason is that the development started without using Lit but it is still nice to be able to have a global CSS style (which wouldn't be possibile with a shadow root) so that restyling the library is just a matter of adding another CSS file and rewrite a few properties.
The only other external library that is used here is the awesome [Parsimmon](https://github.com/jneen/parsimmon): a very small but capable text parsing library used to deserialize the text produced by the Unreal Engine Blueprint Editor.
The only other external library that is used here is the awesome [Parsimmon](https://github.com/jneen/parsimmon): a very small but capable text parsing library used to deserialize the text produced by the UE Blueprint Editor.
## Setup

View File

@@ -1,6 +1,6 @@
# UEBlueprint
A stand alone editor implementation of the Unreal Engine's Blueprint visual language. (WIP)
A stand alone editor implementation of the UE's Blueprint visual language. (WIP)
## Features:

View File

@@ -17,80 +17,92 @@ describe("Serializer", () => {
context("Boolean", () => {
let serializer = SerializerFactory.getSerializer(Boolean)
it("parses true", () => expect(serializer.deserialize("true")).to.be.true)
it("parses True", () => expect(serializer.deserialize("True")).to.be.true)
it("parses false", () => expect(serializer.deserialize("false")).to.be.false)
it("parses False", () => expect(serializer.deserialize("False")).to.be.false)
it("Parses true", () => expect(serializer.deserialize("true")).to.be.true)
it("Parses True", () => expect(serializer.deserialize("True")).to.be.true)
it("Parses false", () => expect(serializer.deserialize("false")).to.be.false)
it("Parses False", () => expect(serializer.deserialize("False")).to.be.false)
})
context("Integer", () => {
let serializer = SerializerFactory.getSerializer(IntegerEntity)
it("parses 0", () => expect(serializer.deserialize("0"))
it("Parses 0", () => expect(serializer.deserialize("0"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(0)
)
it("parses +0", () => expect(serializer.deserialize("+0"))
it("Parses +0", () => expect(serializer.deserialize("+0"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(0)
)
it("parses -0", () => expect(serializer.deserialize("-0"))
it("Parses -0", () => expect(serializer.deserialize("-0"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(0)
)
it("parses 99", () => expect(serializer.deserialize("99"))
it("Parses 99", () => expect(serializer.deserialize("99"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(99)
)
it("parses -8685", () => expect(serializer.deserialize("-8685"))
it("Parses -8685", () => expect(serializer.deserialize("-8685"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(-8685)
)
it("parses +555", () => expect(serializer.deserialize("+555"))
it("Parses +555", () => expect(serializer.deserialize("+555"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(555)
)
it("parses 1000000000", () => expect(serializer.deserialize("1000000000"))
it("Parses 1000000000", () => expect(serializer.deserialize("1000000000"))
.to.be.instanceOf(IntegerEntity)
.and.property("value").to.be.equal(1000000000)
)
it("throws when not an integer", () => expect(() => serializer.deserialize("1.2").value).to.throw())
it("Throws when not an integer", () => expect(() => serializer.deserialize("1.2").value).to.throw())
})
context("Number", () => {
let serializer = SerializerFactory.getSerializer(Number)
it("parses 0", () => expect(serializer.deserialize("0")).to.be.approximately(0, 0.00001))
it("parses +0", () => expect(serializer.deserialize("+0")).to.be.approximately(0, 0.00001))
it("parses -0", () => expect(serializer.deserialize("-0")).to.be.approximately(0, 0.00001))
it("parses 5", () => expect(serializer.deserialize("5")).to.be.approximately(5, 0.00001))
it("parses 0.05", () => expect(serializer.deserialize("0.05")).to.be.approximately(0.05, 0.00001))
it("parses -999.666", () => expect(serializer.deserialize("-999.666")).to.be.approximately(-999.666, 0.001))
it("parses +45.4545", () => expect(serializer.deserialize("+45.4545")).to.be.approximately(45.4545, 0.001))
it("parses +1000000000", () => expect(serializer.deserialize("+1000000000")).to.be.approximately(1E9, 0.1))
it("throws when not numeric", () => expect(() => serializer.deserialize("alpha")).to.throw())
it("Parses 0", () => expect(serializer.deserialize("0")).to.be.approximately(0, 0.00001))
it("Parses +0", () => expect(serializer.deserialize("+0")).to.be.approximately(0, 0.00001))
it("Parses -0", () => expect(serializer.deserialize("-0")).to.be.approximately(0, 0.00001))
it("Parses 5", () => expect(serializer.deserialize("5")).to.be.approximately(5, 0.00001))
it("Parses 0.05", () => expect(serializer.deserialize("0.05")).to.be.approximately(0.05, 0.00001))
it("Parses -999.666", () => expect(serializer.deserialize("-999.666")).to.be.approximately(-999.666, 0.001))
it("Parses +45.4545", () => expect(serializer.deserialize("+45.4545")).to.be.approximately(45.4545, 0.001))
it("Parses +1000000000", () => expect(serializer.deserialize("+1000000000")).to.be.approximately(1E9, 0.1))
it("Throws when not numeric", () => expect(() => serializer.deserialize("alpha")).to.throw())
})
context("String", () => {
let serializer = SerializerFactory.getSerializer(String)
it('Parses ""', () => expect(serializer.deserialize('""')).to.be.equal(""))
it('Parses "hello"', () => expect(serializer.deserialize('"hello"')).to.be.equal("hello"))
it('Parses "hello world 123 - éèàò@ç ^ ^^^"', () =>
expect(serializer.deserialize('"hello world 123 - éèàò@ç ^ ^^^"'))
.to.be.equal("hello world 123 - éèàò@ç ^ ^^^")
)
it(String.raw`Parses "\""`, () => expect(serializer.deserialize(String.raw`"\""`)).to.be.equal('"'))
})
context("KeyBindingEntity", () => {
let serializer = SerializerFactory.getSerializer(KeyBindingEntity)
it("parses A", () =>
it("Parses A", () =>
expect(serializer.deserialize("A"))
.to.be.instanceOf(KeyBindingEntity)
.and.to.deep.contain({ Key: { value: "A" } })
)
it("parses (bCtrl=True,Key=A)", () =>
it("Parses (bCtrl=True,Key=A)", () =>
expect(serializer.deserialize("(bCtrl=True,Key=A)"))
.to.be.instanceOf(KeyBindingEntity)
.and.to.deep.contain({ Key: { value: "A" }, bCtrl: true })
)
it("parses (bCtrl=false,bShift=false,bCmd=false,bAlt=false,Key=X)", () =>
it("Parses (bCtrl=false,bShift=false,bCmd=false,bAlt=false,Key=X)", () =>
expect(serializer.deserialize("(bCtrl=false,bShift=false,bCmd=true,bAlt=false,Key=X)"))
.to.be.instanceOf(KeyBindingEntity)
.and.to.deep.contain({ Key: { value: "X" }, bAlt: false, bCtrl: false, bCmd: true })
)
it("parses spaces correctly", () =>
it("Parses spaces correctly", () =>
expect(serializer.deserialize("( bCtrl= false \n, Key \n\n\n =Y ,bAlt=true )"))
.to.be.instanceOf(KeyBindingEntity)
.and.to.deep.contain({ Key: { value: "Y" }, bAlt: true, bCtrl: false })
@@ -100,30 +112,30 @@ describe("Serializer", () => {
context("Guid", () => {
let serializer = SerializerFactory.getSerializer(GuidEntity)
it("parses 0556a3ecabf648d0a5c07b2478e9dd32", () =>
it("Parses 0556a3ecabf648d0a5c07b2478e9dd32", () =>
expect(serializer.deserialize("0556a3ecabf648d0a5c07b2478e9dd32"))
.to.be.instanceOf(GuidEntity)
.and.property("value").to.be.equal("0556a3ecabf648d0a5c07b2478e9dd32")
)
it("parses 64023BC344E0453DBB583FAC411489BC", () =>
it("Parses 64023BC344E0453DBB583FAC411489BC", () =>
expect(serializer.deserialize("64023BC344E0453DBB583FAC411489BC"))
.to.be.instanceOf(GuidEntity)
.and.property("value").to.be.equal("64023BC344E0453DBB583FAC411489BC")
)
it("parses 6edC4a425ca948da8bC78bA52DED6C6C", () =>
it("Parses 6edC4a425ca948da8bC78bA52DED6C6C", () =>
expect(serializer.deserialize("6edC4a425ca948da8bC78bA52DED6C6C"))
.to.be.instanceOf(GuidEntity)
.and.property("value").to.be.equal("6edC4a425ca948da8bC78bA52DED6C6C")
)
it("throws when finding space", () =>
it("Throws when finding space", () =>
expect(() => serializer.deserialize("172087193 9B04362973544B3564FDB2C"))
.to.throw()
)
it("throws when shorter by 1", () =>
it("Throws when shorter by 1", () =>
expect(() => serializer.deserialize("E25F14F8F3E9441AB07153E7DA2BA2B"))
.to.throw()
)
it("throws when longer by 1", () =>
it("Throws when longer by 1", () =>
expect(() => serializer.deserialize("A78988B0097E48418C8CB87EC5A67ABF7"))
.to.throw()
)
@@ -132,21 +144,21 @@ describe("Serializer", () => {
context("Vector", () => {
let serializer = SerializerFactory.getSerializer(VectorEntity)
it("parses simple vector", () => expect(serializer.deserialize("(X=1,Y=2,Z=3.5)"))
it("Parses simple vector", () => expect(serializer.deserialize("(X=1,Y=2,Z=3.5)"))
.to.be.deep.equal({
X: 1,
Y: 2,
Z: 3.5,
})
)
it("parses trailing comma", () => expect(serializer.deserialize("(X=10,Y=+20.88,Z=-30.54,)"))
it("Parses trailing comma", () => expect(serializer.deserialize("(X=10,Y=+20.88,Z=-30.54,)"))
.to.be.deep.equal({
X: 10,
Y: 20.88,
Z: -30.54,
})
)
it("parses weird spaces", () => expect(serializer.deserialize(`(
it("Parses weird spaces", () => expect(serializer.deserialize(`(
Z = -3.66 ,
X
@@ -162,13 +174,13 @@ describe("Serializer", () => {
Z: -3.66,
})
)
it("throws when unexpected types", () => expect(() => serializer.deserialize("(X=1,Y=\"2\",Z=3)"))
it("Throws when unexpected types", () => expect(() => serializer.deserialize("(X=1,Y=\"2\",Z=3)"))
.to.throw()
)
it("throws when missing a key", () => expect(() => serializer.deserialize("(X=1,Z=3)"))
it("Throws when missing a key", () => expect(() => serializer.deserialize("(X=1,Z=3)"))
.to.throw()
)
it("throws when finding unexpected keys", () => expect(() => serializer.deserialize("(X=1,Y=2,Unexpected=6,Z=3.5)"))
it("Throws when finding unexpected keys", () => expect(() => serializer.deserialize("(X=1,Y=2,Unexpected=6,Z=3.5)"))
.to.throw()
)
})
@@ -176,19 +188,19 @@ describe("Serializer", () => {
context("Vector2D", () => {
let serializer = SerializerFactory.getSerializer(Vector2DEntity)
it("parses simple vector", () => expect(serializer.deserialize("(X=78,Y=56.3)"))
it("Parses simple vector", () => expect(serializer.deserialize("(X=78,Y=56.3)"))
.to.be.deep.equal({
X: 78,
Y: 56.3,
})
)
it("parses trailing comma", () => expect(serializer.deserialize("(X=+4.5,Y=-8.88,)"))
it("Parses trailing comma", () => expect(serializer.deserialize("(X=+4.5,Y=-8.88,)"))
.to.be.deep.equal({
X: 4.5,
Y: -8.88,
})
)
it("parses weird spaces", () => expect(serializer.deserialize(`(
it("Parses weird spaces", () => expect(serializer.deserialize(`(
Y = +93.004 ,
X
@@ -199,13 +211,13 @@ describe("Serializer", () => {
Y: 93.004,
})
)
it("throws on unexpected type", () => expect(() => serializer.deserialize("(X=1,Y=\"2\")"))
it("Throws on unexpected type", () => expect(() => serializer.deserialize("(X=1,Y=\"2\")"))
.to.throw()
)
it("throws when missing a key", () => expect(() => serializer.deserialize("(X=1)"))
it("Throws when missing a key", () => expect(() => serializer.deserialize("(X=1)"))
.to.throw()
)
it("throws when finding unexpected keys", () => expect(() => serializer.deserialize("(X=777, Y=555, Unexpected=6, HH=2)"))
it("Throws when finding unexpected keys", () => expect(() => serializer.deserialize("(X=777, Y=555, Unexpected=6, HH=2)"))
.to.throw()
)
})
@@ -220,28 +232,28 @@ describe("Serializer", () => {
expect(result.toNumber()).to.be.equal(-1)
expect(result.toHSVA()).to.be.deep.equal([0, 0, 1, 1])
})
it("parses red color", () => {
it("Parses red color", () => {
const result = serializer.deserialize("(R=1,G=0,B=0)")
expect(result.toRGBA()).to.be.deep.equal([255, 0, 0, 255])
expect(result.toRGBAString()).to.be.equal("FF0000FF")
expect(result.toNumber()).to.be.equal(-16776961)
expect(result.toHSVA()).to.be.deep.equal([0, 1, 1, 1])
})
it("parses simple color", () => {
it("Parses simple color", () => {
const result = serializer.deserialize("(R=0.000000,G=0.660000,B=1.000000,A=1.000000)")
expect(result.toRGBA()).to.be.deep.equal([0, 168, 255, 255])
expect(result.toRGBAString()).to.be.equal("00A8FFFF")
expect(result.toNumber()).to.be.equal(11075583)
expect(result.toHSVA()).to.be.deep.equal([0.55666666666666666666, 1, 1, 1])
})
it("parses wrong order keys", () => {
it("Parses wrong order keys", () => {
const result = serializer.deserialize("(B=0.04394509003266556,G=0.026789300067696642,A=0.83663232408635,R=0.6884158028074934,)")
expect(result.toRGBA()).to.be.deep.equal([176, 7, 11, 213])
expect(result.toRGBAString()).to.be.equal("B0070BD5")
expect(result.toNumber()).to.be.equal(-1341715499)
expect(result.toHSVA().map(v => Utility.roundDecimals(v, 3))).to.be.deep.equal([0.996, 0.961, 0.688, 0.837])
})
it("parses weird spaces", () => {
it("Parses weird spaces", () => {
const result = serializer.deserialize(`(
A = 0.327 ,
R=0.530 , G = 0.685
@@ -252,10 +264,10 @@ describe("Serializer", () => {
expect(result.toNumber()).to.be.equal(-2018515373)
expect(result.toHSVA().map(v => Utility.roundDecimals(v, 3))).to.be.deep.equal([0.597, 0.411, 0.9, 0.327])
})
it("throws when missing an expected key", () => expect(() => serializer.deserialize("(R=0.000000,G=0.660000,A=1.000000)"))
it("Throws when missing an expected key", () => expect(() => serializer.deserialize("(R=0.000000,G=0.660000,A=1.000000)"))
.to.throw()
)
it("throws when unexpected types", () => expect(() => serializer.deserialize("(R=0.000000,G=\"hello\",A=1.000000)"))
it("Throws when unexpected types", () => expect(() => serializer.deserialize("(R=0.000000,G=\"hello\",A=1.000000)"))
.to.throw()
)
})

View File

@@ -144,5 +144,50 @@ describe("Utility class", () => {
expect(Utility.range(0, -3)).to.be.deep.equal([0, -1, -2])
expect(Utility.range(7, -7, -4)).to.be.deep.equal([7, 3, -1, -5])
})
it("String escaping methods test", () => {
expect(Utility.escapeString("")).to.be.equal("")
expect(Utility.unescapeString("")).to.be.equal("")
expect(Utility.escapeString(String.raw`\"`)).to.be.equal(String.raw`\\\"`)
expect(Utility.unescapeString(String.raw`\"`)).to.be.equal('"')
expect(Utility.escapeString(String.raw`Hello \"World\"`)).to.be.equal(String.raw`Hello \\\"World\\\"`)
expect(Utility.unescapeString(String.raw`Hello \"World\"`)).to.be.equal('Hello "World"')
expect(Utility.escapeString(String.raw`Those "\\" are two backslash`))
.to.be.equal(String.raw`Those \"\\\\\" are two backslash`)
expect(Utility.unescapeString(String.raw`Those "\\" are two backslash`))
.to.be.equal(String.raw`Those "\" are two backslash`)
expect(Utility.escapeString(String.raw`Alpha\Beta`)).to.be.equal(String.raw`Alpha\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\Beta`)).to.be.equal(String.raw`Alpha\Beta`)
expect(Utility.escapeString(String.raw`Alpha\\Beta`)).to.be.equal(String.raw`Alpha\\\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\\Beta`)).to.be.equal(String.raw`Alpha\Beta`)
expect(Utility.escapeString(String.raw`Alpha\\\Beta`)).to.be.equal(String.raw`Alpha\\\\\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\\\Beta`)).to.be.equal(String.raw`Alpha\\Beta`)
expect(Utility.escapeString(String.raw`Alpha\\\\Beta`)).to.be.equal(String.raw`Alpha\\\\\\\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\\\\Beta`)).to.be.equal(String.raw`Alpha\\Beta`)
expect(Utility.escapeString(String.raw`Alpha\\\\\Beta`)).to.be.equal(String.raw`Alpha\\\\\\\\\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\\\\\Beta`)).to.be.equal(String.raw`Alpha\\\Beta`)
expect(Utility.escapeString(String.raw`Alpha\\\\\\Beta`)).to.be.equal(String.raw`Alpha\\\\\\\\\\\\Beta`)
expect(Utility.unescapeString(String.raw`Alpha\\\\\\Beta`)).to.be.equal(String.raw`Alpha\\\Beta`)
expect(Utility.escapeString(String.raw`Alpha \"Beta\"`)).to.be.equal(String.raw`Alpha \\\"Beta\\\"`)
expect(Utility.unescapeString(String.raw`Alpha \"Beta\"`)).to.be.equal(String.raw`Alpha "Beta"`)
expect(Utility.escapeString(String.raw`Alpha \\"Beta\\"`)).to.be.equal(String.raw`Alpha \\\\\"Beta\\\\\"`)
expect(Utility.unescapeString(String.raw`Alpha \\"Beta\\"`)).to.be.equal(String.raw`Alpha \"Beta\"`)
expect(Utility.escapeString('Alpha\nBravo\\Charlie\n"Delta"'))
.to.equal(String.raw`Alpha\nBravo\\Charlie\n\"Delta\"`)
expect(Utility.unescapeString(String.raw`Alpha\nBravo\\Charlie\n\"Delta\"`)).to.equal(
`Alpha\nBravo\\Charlie\n"Delta"`
)
})
})
})

9556
dist/ueblueprint.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<title>Unreal Engine Blueprint</title>
<title>UE Blueprint</title>
<link rel="stylesheet" href="dist/css/ueblueprint-node-value-type-color.css">
<link rel="stylesheet" href="dist/css/ueb-style.css">
<style>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<title>Unreal Engine Blueprint</title>
<title>UE Blueprint</title>
<link rel="stylesheet" href="dist/css/ueblueprint-node-value-type-color.css">
<link rel="stylesheet" href="dist/css/ueb-style.css">
<style>

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<title>Unreal Engine Blueprint</title>
<title>UE Blueprint</title>
<link rel="stylesheet" href="dist/css/ueblueprint-node-value-type-color.css">
<link rel="stylesheet" href="dist/css/ueb-style.css">
<style>

View File

@@ -18,6 +18,7 @@ export default class Configuration {
static colorDragEventName = "ueb-color-drag"
static colorPickEventName = "ueb-color-pick"
static colorWindowEventName = "ueb-color-window"
static colorWindowName = "Color Picker"
static defaultCommentHeight = 96
static defaultCommentWidth = 400
static deleteNodesKeyboardKey = "Delete"
@@ -159,7 +160,9 @@ export default class Configuration {
end: "ueb-tracking-mouse-end",
}
static windowApplyEventName = "ueb-window-apply"
static windowApplyButtonText = "OK"
static windowCancelEventName = "ueb-window-cancel"
static windowCancelButtonText = "Cancel"
static windowCloseEventName = "ueb-window-close"
static ModifierKeys = [
"Ctrl",

View File

@@ -293,17 +293,21 @@ export default class Utility {
}
/** @param {String} value */
static escapeString(value, input = false) {
static escapeString(value) {
return value
.replaceAll('\\', '\\\\') // Escape \
.replaceAll('"', '\\"') // Escape "
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t") // Replace tab with \t
}
/** @param {String} value */
static unescapeString(value, input = false) {
static unescapeString(value) {
return value
.replaceAll('\\"', '"')
.replaceAll("\\n", "\n")
.replaceAll("\\t", "\t") // Replace tab with \t
.replaceAll("\\n", "\n") // Replace newline with \n
.replaceAll('\\"', '"') // Escape "
.replaceAll('\\\\', '\\') // Escape \
}
/** @param {String} value */

View File

@@ -0,0 +1,21 @@
import IEntity from "./IEntity"
import InvariantTextEntity from "./InvariantTextEntity"
import LocalizedTextEntity from "./LocalizedTextEntity"
import UnionType from "./UnionType"
export default class FormatTextEntity extends IEntity {
static lookbehind = "LOCGEN_FORMAT_NAMED"
static attributes = {
value: [new UnionType(LocalizedTextEntity, InvariantTextEntity, FormatTextEntity)],
}
static {
this.cleanupAttributes(this.attributes)
}
constructor(values) {
super(values)
/** @type {String} */ this.value
}
} 1

View File

@@ -585,7 +585,7 @@ export default class ObjectEntity extends IEntity {
if (hidValue.includes("Mouse")) {
return SVGIcon.mouse
} else if (hidValue.includes("Gamepad_Special")) {
return SVGIcon.keyboard // This is called Touchpad in Unreal Engine
return SVGIcon.keyboard // This is called Touchpad in UE
} else if (hidValue.includes("Gamepad") || hidValue.includes("Steam")) {
return SVGIcon.gamepad
} else if (hidValue.includes("Touch")) {

View File

@@ -40,8 +40,53 @@ let P = Parsimmon
export default class Grammar {
static Regex = {
ByteInteger: /0*(?:25[0-5]|2[0-4]\d|1?\d?\d|)(?!\d)/, // A integer between 0 and 255
HexDigit: /[0-9a-fA-F]/,
InlineOptWhitespace: /[^\S\n]*/,
InlineWhitespace: /[^\S\n]+/,
InsideString: /(?:[^"\\]|\\.)*/,
Integer: /[\-\+]?\d+/,
MultilineWhitespace: /\s*\n\s*/,
Number: /[-\+]?\d+(?:\.\d+)?/,
RealUnit: /^\+?(?:0(?:\.\d+)?|1(?:\.0+)?)(?![\.\d])/, // A number between 0 and 1 included
Symbol: /[a-zA-Z_]\w*/,
Word: /[a-zA-Z_]+/,
}
/* --- Factory --- */
/**
* @template T
* @param {RegExp} re
* @param {(execResult) => T} mapper
*/
static regexMap(re, mapper) {
const anchored = RegExp("^(?:" + re.source + ")", re.flags)
const expected = "" + re
/** @param {Grammar} r */
return P((input, i) => {
const match = anchored.exec(input.slice(i))
if (match) {
return P.makeSuccess(i + match[0].length, mapper(match))
}
return P.makeFailure(i, expected)
})
}
/** @param {String} str */
static getStringParser(str) {
return P((input, i) => {
var j = i + str.length
var head = input.slice(i, j)
if (head === str) {
return makeSuccess(j, head)
} else {
return makeFailure(i, expected)
}
})
}
/** @param {Grammar} r */
static getGrammarForType(r, attribute, defaultGrammar = r.AttributeAnyValue) {
if (attribute.constructor === Object) {
@@ -92,6 +137,8 @@ export default class Grammar {
return r.Integer
case InvariantTextEntity:
return r.InvariantText
case KeyBindingEntity:
return r.KeyBinding
case LinearColorEntity:
return r.LinearColor
case LocalizedTextEntity:
@@ -218,56 +265,36 @@ export default class Grammar {
/* --- General --- */
/** @param {Grammar} r */
InlineWhitespace = r => P.regex(/[^\S\n]+/).desc("single line whitespace")
Null = r => P.regex(new RegExp(String.raw`\(${Grammar.Regex.InlineOptWhitespace.source}\)`)).map(() => null).desc("null: ()")
/** @param {Grammar} r */
InlineOptWhitespace = r => P.regex(/[^\S\n]*/).desc("single line optional whitespace")
Boolean = r => Grammar.regexMap(/(true)|false/i, v => v[1] ? true : false).desc("either True or False")
/** @param {Grammar} r */
MultilineWhitespace = r => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline")
Number = r => P.regex(Grammar.Regex.Number).map(Number).desc("a number")
/** @param {Grammar} r */
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(() => null).desc("null: ()")
BigInt = r => P.regex(Grammar.Regex.Integer).map(v => BigInt(v)).desc("a big integer")
/** @param {Grammar} r */
Boolean = r => P.alt(
P.string("True"),
P.string("true"),
P.string("False"),
P.string("false"),
).map(v => v.toLocaleLowerCase() === "true" ? true : false)
.desc("either True or False")
RealUnit = r => P.regex(Grammar.Regex.RealUnit).map(Number).desc("a number between 0 and 1")
/** @param {Grammar} r */
HexDigit = r => P.regex(/[0-9a-fA-f]/).desc("hexadecimal digit")
NaturalNumber = r => Grammar.regexMap(/\d+/, v => parseInt(v[0])).desc("a natural number")
/** @param {Grammar} r */
Number = r => P.regex(/[-\+]?[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number")
ColorNumber = r => P.regexp(Grammar.Regex.ByteInteger).desc("a number between 0 and 255")
/** @param {Grammar} r */
BigInt = r => P.regex(/[\-\+]?[0-9]+/).map(v => BigInt(v)).desc("a big integer")
Word = r => P.regexp(Grammar.Regex.Word).desc("a word")
/** @param {Grammar} r */
RealNumber = r => P.regex(/[-\+]?[0-9]+\.[0-9]+/).map(Number).desc("a number written as real")
/** @param {Grammar} r */
RealUnit = r => P.regex(/\+?[0-9]+(?:\.[0-9]+)?/).map(Number).assert(v => v >= 0 && v <= 1).desc("a number between 0 and 1")
/** @param {Grammar} r */
NaturalNumber = r => P.regex(/0|[1-9]\d*/).map(Number).desc("a natural number")
/** @param {Grammar} r */
ColorNumber = r => r.NaturalNumber.assert(n => 0 <= n && n < 256, "the color must be between 0 and 256 excluded")
/** @param {Grammar} r */
Word = r => P.regex(/[a-zA-Z_]+/).desc("a word")
/** @param {Grammar} r */
String = r => P.regex(/(?:[^"\\]|\\.)*/).wrap(P.string('"'), P.string('"')).map(Utility.unescapeString)
String = r => P.regexp(new RegExp(`"(${Grammar.Regex.InsideString.source})"`), 1).map(Utility.unescapeString)
.desc('string (with possibility to escape the quote using \")')
/** @param {Grammar} r */
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc("dot-separated words")
AttributeName = r => P.regexp(new RegExp(String.raw`(?:(?:^|(?<!^)\.)${Grammar.Regex.Word.source})+`))
.desc("dot-separated words")
/* --- Entity --- */
@@ -278,32 +305,30 @@ export default class Grammar {
Integer64 = r => r.BigInt.map(v => new Integer64Entity(v)).desc("an integer64")
/** @param {Grammar} r */
Integer = r => P.regex(/[\-\+]?[0-9]+/).map(v => new IntegerEntity(v)).desc("an integer")
Integer = r => P.regex(Grammar.Regex.Integer).map(v => new IntegerEntity(v)).desc("an integer")
/** @param {Grammar} r */
Byte = r => P.regex(/\+?[0-9]+/)
.map(v => parseInt(v))
.assert(v => v >= 0 && v < 1 << 8)
.map(v => new ByteEntity(v))
.desc("a Byte")
Byte = r => P.regex(Grammar.Regex.ByteInteger).map(v => new ByteEntity(parseInt(v))).desc("a Byte")
/** @param {Grammar} r */
Guid = r => r.HexDigit.times(32).tie().map(v => new GuidEntity({ value: v })).desc("32 digit hexadecimal value")
Guid = r => P.regexp(new RegExp(`${Grammar.Regex.HexDigit.source}{32}`))
.map(v => new GuidEntity({ value: v }))
.desc("32 digit hexadecimal value")
/** @param {Grammar} r */
Identifier = r => P.regex(/\w+/).map(v => new IdentifierEntity(v))
/** @param {Grammar} r */
PathSymbol = r => P.regex(/[0-9\w]+/).map(v => new PathSymbolEntity({ value: v }))
PathSymbol = r => P.regex(/\w+/).map(v => new PathSymbolEntity({ value: v }))
/** @param {Grammar} r */
PathSymbolOptSpaces = r => P.regex(/[0-9\w]+(?: [0-9\w]+)+|[0-9\w]+/).map(v => new PathSymbolEntity({ value: v }))
PathSymbolOptSpaces = r => P.regex(/(?:(?:^|(?<!^) )\w+)+/).map(v => new PathSymbolEntity({ value: v }))
/** @param {Grammar} r */
Symbol = r => P.regex(/[a-zA-Z_]\w*/).map(v => new SymbolEntity({ value: v }))
Symbol = r => P.regex(Grammar.Regex.Symbol).map(v => new SymbolEntity({ value: v }))
/** @param {Grammar} r */
Enum = r => P.regex(/[a-zA-Z_]\w*/).map(v => new EnumEntity({ value: v }))
Enum = r => P.regex(Grammar.Regex.Symbol).map(v => new EnumEntity({ value: v }))
/** @param {Grammar} r */
ObjectReference = r => P.alt(
@@ -329,27 +354,30 @@ export default class Grammar {
)
/** @param {Grammar} r */
LocalizedText = r => P.seqMap(
P.string(LocalizedTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")), // Goes into _0 (ignored)
r.String.trim(P.optWhitespace), // Goes into namespace
P.string(","), // Goes into _2 (ignored)
r.String.trim(P.optWhitespace), // Goes into key
P.string(","), // Goes into _4 (ignored)
r.String.trim(P.optWhitespace), // Goes into value
P.string(")"), // Goes into _6 (ignored)
(_0, namespace, _2, key, _4, value, _6) => new LocalizedTextEntity({
namespace: namespace,
key: key,
value: value
})
)
LocalizedText = r =>
Grammar.regexMap(
new RegExp(
String.raw`${LocalizedTextEntity.lookbehind}\s*\(`
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(${Grammar.Regex.InsideString.source})"\s*\)`
),
matchResult => new LocalizedTextEntity({
namespace: matchResult[1],
key: matchResult[2],
value: matchResult[3]
}
)
)
/** @param {Grammar} r */
InvariantText = r => r.String.trim(P.optWhitespace).wrap(
P.string(InvariantTextEntity.lookbehind).skip(P.optWhitespace).skip(P.string("(")),
P.string(")")
)
.map(value => new InvariantTextEntity({ value: value }))
InvariantText = r =>
Grammar.regexMap(
new RegExp(
String.raw`${InvariantTextEntity.lookbehind}\s*\("(${Grammar.Regex.InsideString.source})"\)`,
matchResult => new InvariantTextEntity({ value: matchResult[1] })
)
)
/** @param {Grammar} r */
AttributeAnyValue = r => P.alt(
@@ -458,8 +486,7 @@ export default class Grammar {
/** @param {Grammar} r */
CustomProperties = r =>
P.string("CustomProperties")
.then(P.whitespace)
P.regex(/CustomProperties\s+/)
.then(r.Pin)
.map(pin => entity => {
/** @type {Array} */
@@ -470,14 +497,14 @@ export default class Grammar {
/** @param {Grammar} r */
Object = r => P.seqMap(
P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace),
P.regexp(/Begin\s+Object\s+/),
P
.alt(
r.CustomProperties,
Grammar.createAttributeGrammar(r, ObjectEntity)
)
.sepBy1(P.whitespace),
P.seq(r.MultilineWhitespace, P.string("End"), P.whitespace, P.string("Object")),
P.regexp(/\s+End\s+Object/),
(_0, attributes, _2) => {
let values = {}
attributes.forEach(attributeSetter => attributeSetter(values))

View File

@@ -346,14 +346,18 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
</div>
</div>
<div class="ueb-buttons">
<div class="ueb-color-picker-ok ueb-button" @click="${() => this.apply()}">OK</div>
<div class="ueb-color-picker-cancel ueb-button" @click="${() => this.cancel()}">Cancel</div>
<div class="ueb-color-picker-ok ueb-button" @click="${() => this.apply()}">
${Configuration.windowApplyButtonText}
</div>
<div class="ueb-color-picker-cancel ueb-button" @click="${() => this.cancel()}">
${Configuration.windowCancelButtonText}
</div>
</div>
</div>
`
}
renderWindowName() {
return html`Color Picker`
return html`${Configuration.colorWindowName}`
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "ueblueprint",
"version": "1.0.0",
"description": "Unreal Engine's Blueprint visualisation library",
"description": "UE's Blueprint visualisation library",
"main": "ueblueprint.js",
"scripts": {
"build": "rollup --config && sass scss/export.scss:dist/css/ueb-style.css && sass scss/export.scss:dist/css/ueb-style.min.css --style=compressed",
@@ -27,6 +27,7 @@
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-terser": "^0.1.0",
"@types/parsimmon": "^1.10.6",
"concurrently": "^7.6.0",
"cypress": "^12.1.0",
"http-server": "^14.1.1",