Various fixes

This commit is contained in:
barsdeveloper
2021-10-27 19:27:19 +02:00
parent 56c23fc192
commit 418630255e
41 changed files with 2401 additions and 2337 deletions

1262
dist/ueblueprint.js vendored

File diff suppressed because it is too large Load Diff

194
js/Utility.js Normal file → Executable file
View File

@@ -1,97 +1,97 @@
import Integer from "./entity/primitive/Integer"
import TypeInitialization from "./entity/TypeInitialization"
export default class Utility {
static clamp(val, min, max) {
return Math.min(Math.max(val, min), max)
}
static getScale(element) {
return getComputedStyle(element).getPropertyValue('--ueb-scale')
}
/**
* Sets a value in an object
* @param {String[]} keys The chained keys to access from object in order to set the value
* @param {any} value Value to be set
* @param {Object} target Object holding the data
* @param {Boolean} create Whether to create or not the key in case it doesn't exist
* @returns {Boolean} Returns true on succes, false otherwise
*/
static objectSet(target, keys, value, create = false) {
if (keys.constructor != Array) {
console.error("Expected keys to be an array.")
}
if (keys.length == 1) {
if (create || keys[0] in target) {
target[keys[0]] = value
return true
}
} else if (keys.length > 0) {
return Utility.objectSet(target[keys[0]], keys.slice(1), value, create)
}
return false
}
/**
* Gets a value from an object, gives defaultValue in case of failure
* @param {Object} source Object holding the data
* @param {String[]} keys The chained keys to access from object in order to get the value
* @param {any} defaultValue Value to return in case from doesn't have it
* @returns {any} The value in from corresponding to the keys or defaultValue otherwise
*/
static objectGet(source, keys, defaultValue = null) {
if (keys.constructor != Array) {
console.error("Expected keys to be an array.")
}
if (keys.length == 0 || !(keys[0] in source)) {
return defaultValue
}
if (keys.length == 1) {
return source[keys[0]]
}
return Utility.objectGet(source[keys[0]], keys.slice(1), defaultValue)
}
static sanitize(value) {
if (!(value instanceof Object)) {
return value // Is already primitive
}
if (value instanceof Boolean || value instanceof Integer || value instanceof Number) {
return value.valueOf()
}
if (value instanceof String) {
return value.toString()
}
return value
}
static equals(a, b) {
a = Utility.sanitize(a)
b = Utility.sanitize(b)
return a === b
}
/**
*
* @param {String} value
*/
static FirstCapital(value) {
return value.charAt(0).toUpperCase() + value.substring(1)
}
static getType(value) {
let constructor = value?.constructor
switch (constructor) {
case TypeInitialization:
return value.type
case Function:
return value
default:
return constructor
}
}
}
import Integer from "./entity/primitive/Integer"
import TypeInitialization from "./entity/TypeInitialization"
export default class Utility {
static clamp(val, min, max) {
return Math.min(Math.max(val, min), max)
}
static getScale(element) {
return getComputedStyle(element).getPropertyValue('--ueb-scale')
}
/**
* Sets a value in an object
* @param {String[]} keys The chained keys to access from object in order to set the value
* @param {any} value Value to be set
* @param {Object} target Object holding the data
* @param {Boolean} create Whether to create or not the key in case it doesn't exist
* @returns {Boolean} Returns true on succes, false otherwise
*/
static objectSet(target, keys, value, create = false) {
if (keys.constructor != Array) {
console.error("Expected keys to be an array.")
}
if (keys.length == 1) {
if (create || keys[0] in target) {
target[keys[0]] = value
return true
}
} else if (keys.length > 0) {
return Utility.objectSet(target[keys[0]], keys.slice(1), value, create)
}
return false
}
/**
* Gets a value from an object, gives defaultValue in case of failure
* @param {Object} source Object holding the data
* @param {String[]} keys The chained keys to access from object in order to get the value
* @param {any} defaultValue Value to return in case from doesn't have it
* @returns {any} The value in from corresponding to the keys or defaultValue otherwise
*/
static objectGet(source, keys, defaultValue = null) {
if (keys.constructor != Array) {
console.error("Expected keys to be an array.")
}
if (keys.length == 0 || !(keys[0] in source)) {
return defaultValue
}
if (keys.length == 1) {
return source[keys[0]]
}
return Utility.objectGet(source[keys[0]], keys.slice(1), defaultValue)
}
static sanitize(value) {
if (!(value instanceof Object)) {
return value // Is already primitive
}
if (value instanceof Boolean || value instanceof Integer || value instanceof Number) {
return value.valueOf()
}
if (value instanceof String) {
return value.toString()
}
return value
}
static equals(a, b) {
a = Utility.sanitize(a)
b = Utility.sanitize(b)
return a === b
}
/**
*
* @param {String} value
*/
static FirstCapital(value) {
return value.charAt(0).toUpperCase() + value.substring(1)
}
static getType(value) {
let constructor = value?.constructor
switch (constructor) {
case TypeInitialization:
return value.type
case Function:
return value
default:
return constructor
}
}
}

108
js/entity/Entity.js Normal file → Executable file
View File

@@ -1,54 +1,54 @@
import TypeInitialization from "./TypeInitialization"
import Utility from "../Utility"
export default class Entity {
constructor(options = {}) {
/**
*
* @param {String[]} prefix
* @param {Object} target
* @param {Object} properties
*/
const defineAllAttributes = (prefix, target, properties) => {
let fullKey = prefix.concat("")
const last = fullKey.length - 1
for (let property in properties) {
fullKey[last] = property
// Not instanceof because all objects are instenceof Object, exact match needed
if (properties[property]?.constructor === Object) {
target[property] = {}
defineAllAttributes(fullKey, target[property], properties[property])
continue
}
/*
* The value can either be:
* - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World")
* - TypeInitialization: contains the maximum amount of information about the attribute.
* - A type: the default value will be default constructed object without arguments.
* - A proper value.
*/
const value = Utility.objectGet(options, fullKey)
if (value !== null) {
target[property] = value
continue
}
let defaultValue = properties[property]
if (defaultValue instanceof TypeInitialization) {
if (!defaultValue.showDefault) {
continue
}
defaultValue = defaultValue.value
}
if (defaultValue instanceof Array) {
target[property] = []
continue
}
if (defaultValue instanceof Function) {
defaultValue = Utility.sanitize(new defaultValue())
}
target[property] = defaultValue
}
}
defineAllAttributes([], this, this.getAttributes())
}
}
import TypeInitialization from "./TypeInitialization"
import Utility from "../Utility"
export default class Entity {
constructor(options = {}) {
/**
*
* @param {String[]} prefix
* @param {Object} target
* @param {Object} properties
*/
const defineAllAttributes = (prefix, target, properties) => {
let fullKey = prefix.concat("")
const last = fullKey.length - 1
for (let property in properties) {
fullKey[last] = property
// Not instanceof because all objects are instenceof Object, exact match needed
if (properties[property]?.constructor === Object) {
target[property] = {}
defineAllAttributes(fullKey, target[property], properties[property])
continue
}
/*
* The value can either be:
* - Array: can contain multiple values, its property is assigned multiple times like (X=1, X=4, X="Hello World")
* - TypeInitialization: contains the maximum amount of information about the attribute.
* - A type: the default value will be default constructed object without arguments.
* - A proper value.
*/
const value = Utility.objectGet(options, fullKey)
if (value !== null) {
target[property] = value
continue
}
let defaultValue = properties[property]
if (defaultValue instanceof TypeInitialization) {
if (!defaultValue.showDefault) {
continue
}
defaultValue = defaultValue.value
}
if (defaultValue instanceof Array) {
target[property] = []
continue
}
if (defaultValue instanceof Function) {
defaultValue = Utility.sanitize(new defaultValue())
}
target[property] = defaultValue
}
}
defineAllAttributes([], this, this.getAttributes())
}
}

26
js/entity/FunctionReferenceEntity.js Normal file → Executable file
View File

@@ -1,13 +1,13 @@
import Entity from "./Entity"
import ObjectReference from "./primitive/ObjectReference"
export default class FunctionReferenceEntity extends Entity {
static attributes = {
MemberParent: ObjectReference,
MemberName: ""
}
getAttributes() {
return FunctionReferenceEntity.attributes
}
}
import Entity from "./Entity"
import ObjectReference from "./primitive/ObjectReference"
export default class FunctionReferenceEntity extends Entity {
static attributes = {
MemberParent: ObjectReference,
MemberName: ""
}
getAttributes() {
return FunctionReferenceEntity.attributes
}
}

54
js/entity/ObjectEntity.js Normal file → Executable file
View File

@@ -1,27 +1,27 @@
import Entity from "./Entity"
import FunctionReferenceEntity from "./FunctionReferenceEntity"
import Guid from "./primitive/Guid"
import ObjectReference from "./primitive/ObjectReference"
import PinEntity from "./PinEntity"
import TypeInitialization from "./TypeInitialization"
import VariableReferenceEntity from "./VariableReferenceEntity"
export default class ObjectEntity extends Entity {
static attributes = {
Class: ObjectReference,
Name: "",
bIsPureFunc: new TypeInitialization(Boolean, false, false),
VariableReference: new TypeInitialization(VariableReferenceEntity, false, null),
FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,),
TargetType: new TypeInitialization(ObjectReference, false, null),
NodePosX: 0,
NodePosY: 0,
NodeGuid: Guid,
CustomProperties: [PinEntity]
}
getAttributes() {
return ObjectEntity.attributes
}
}
import Entity from "./Entity"
import FunctionReferenceEntity from "./FunctionReferenceEntity"
import Guid from "./primitive/Guid"
import ObjectReference from "./primitive/ObjectReference"
import PinEntity from "./PinEntity"
import TypeInitialization from "./TypeInitialization"
import VariableReferenceEntity from "./VariableReferenceEntity"
export default class ObjectEntity extends Entity {
static attributes = {
Class: ObjectReference,
Name: "",
bIsPureFunc: new TypeInitialization(Boolean, false, false),
VariableReference: new TypeInitialization(VariableReferenceEntity, false, null),
FunctionReference: new TypeInitialization(FunctionReferenceEntity, false, null,),
TargetType: new TypeInitialization(ObjectReference, false, null),
NodePosX: 0,
NodePosY: 0,
NodeGuid: Guid,
CustomProperties: [PinEntity]
}
getAttributes() {
return ObjectEntity.attributes
}
}

98
js/entity/PinEntity.js Normal file → Executable file
View File

@@ -1,49 +1,49 @@
import Entity from "./Entity"
import Guid from "./primitive/Guid"
import LocalizedTextEntity from "./primitive/LocalizedTextEntity"
import ObjectReference from "./primitive/ObjectReference"
import PinReferenceEntity from "./PinReferenceEntity"
import TypeInitialization from "./TypeInitialization"
export default class PinEntity extends Entity {
static attributes = {
PinId: Guid,
PinName: "",
PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null),
PinToolTip: "",
Direction: new TypeInitialization(String, false, ""),
PinType: {
PinCategory: "",
PinSubCategory: "",
PinSubCategoryObject: ObjectReference,
PinSubCategoryMemberReference: null,
PinValueType: null,
ContainerType: ObjectReference,
bIsReference: false,
bIsConst: false,
bIsWeakPointer: false,
bIsUObjectWrapper: false
},
LinkedTo: [new TypeInitialization(PinReferenceEntity, false, null)],
DefaultValue: "",
AutogeneratedDefaultValue: "",
PersistentGuid: Guid,
bHidden: false,
bNotConnectable: false,
bDefaultValueIsReadOnly: false,
bDefaultValueIsIgnored: false,
bAdvancedView: false,
bOrphanedPin: false,
}
getAttributes() {
return PinEntity.attributes
}
isOutput() {
if (this.Direction === "EGPD_Output") {
return true
}
}
}
import Entity from "./Entity"
import Guid from "./primitive/Guid"
import LocalizedTextEntity from "./primitive/LocalizedTextEntity"
import ObjectReference from "./primitive/ObjectReference"
import PinReferenceEntity from "./PinReferenceEntity"
import TypeInitialization from "./TypeInitialization"
export default class PinEntity extends Entity {
static attributes = {
PinId: Guid,
PinName: "",
PinFriendlyName: new TypeInitialization(LocalizedTextEntity, false, null),
PinToolTip: "",
Direction: new TypeInitialization(String, false, ""),
PinType: {
PinCategory: "",
PinSubCategory: "",
PinSubCategoryObject: ObjectReference,
PinSubCategoryMemberReference: null,
PinValueType: null,
ContainerType: ObjectReference,
bIsReference: false,
bIsConst: false,
bIsWeakPointer: false,
bIsUObjectWrapper: false
},
LinkedTo: [PinReferenceEntity],
DefaultValue: "",
AutogeneratedDefaultValue: "",
PersistentGuid: Guid,
bHidden: false,
bNotConnectable: false,
bDefaultValueIsReadOnly: false,
bDefaultValueIsIgnored: false,
bAdvancedView: false,
bOrphanedPin: false,
}
getAttributes() {
return PinEntity.attributes
}
isOutput() {
if (this.Direction === "EGPD_Output") {
return true
}
}
}

28
js/entity/PinReferenceEntity.js Normal file → Executable file
View File

@@ -1,14 +1,14 @@
import Entity from "./Entity"
import Guid from "./primitive/Guid"
export default class PinReferenceEntity extends Entity {
static attributes = {
objectName: String,
pinGuid: Guid
}
getAttributes() {
return PinReferenceEntity.attributes
}
}
import Entity from "./Entity"
import Guid from "./primitive/Guid"
export default class PinReferenceEntity extends Entity {
static attributes = {
objectName: String,
pinGuid: Guid
}
getAttributes() {
return PinReferenceEntity.attributes
}
}

38
js/entity/TypeInitialization.js Normal file → Executable file
View File

@@ -1,19 +1,19 @@
import Utility from "../Utility"
export default class TypeInitialization {
/**
*
* @param {typeof Object} type
* @param {boolean} showDefault
* @param {*} value
*/
constructor(type, showDefault = true, value = undefined) {
if (value === undefined) {
value = Utility.sanitize(new type())
}
this.value = value
this.showDefault = showDefault
this.type = type
}
}
import Utility from "../Utility"
export default class TypeInitialization {
/**
*
* @param {typeof Object} type
* @param {boolean} showDefault
* @param {*} value
*/
constructor(type, showDefault = true, value = undefined) {
if (value === undefined) {
value = Utility.sanitize(new type())
}
this.value = value
this.showDefault = showDefault
this.type = type
}
}

30
js/entity/VariableReferenceEntity.js Normal file → Executable file
View File

@@ -1,15 +1,15 @@
import Entity from "./Entity"
import Guid from "./primitive/Guid"
export default class VariableReferenceEntity extends Entity {
static attributes = {
MemberName: String,
MemberGuid: Guid,
bSelfContext: false
}
getAttributes() {
return VariableReferenceEntity.attributes
}
}
import Entity from "./Entity"
import Guid from "./primitive/Guid"
export default class VariableReferenceEntity extends Entity {
static attributes = {
MemberName: String,
MemberGuid: Guid,
bSelfContext: false
}
getAttributes() {
return VariableReferenceEntity.attributes
}
}

64
js/entity/primitive/Guid.js Normal file → Executable file
View File

@@ -1,32 +1,32 @@
import Primitive from "./Primitive";
export default class Guid extends Primitive {
static generateGuid(random) {
let values = new Uint32Array(4);
if (random === true) {
crypto.getRandomValues(values)
}
let result = ""
values.forEach(n => {
result += ('00000000' + n.toString(16).toUpperCase()).slice(-8)
})
return result
}
constructor(guid) {
super()
// Using constructor equality and not instanceof in order to consider both primitives and objects
if (guid?.constructor === Boolean) {
guid = Guid.generateGuid(guid == true)
}
if (guid instanceof Guid) {
guid = guid.value
}
this.value = guid
}
toString() {
return this.value.toString()
}
}
import Primitive from "./Primitive"
export default class Guid extends Primitive {
static generateGuid(random) {
let values = new Uint32Array(4);
if (random === true) {
crypto.getRandomValues(values)
}
let result = ""
values.forEach(n => {
result += ('00000000' + n.toString(16).toUpperCase()).slice(-8)
})
return result
}
constructor(guid) {
super()
// Using constructor equality and not instanceof in order to consider both primitives and objects
if (guid?.constructor === Boolean) {
guid = Guid.generateGuid(guid == true)
}
if (guid instanceof Guid) {
guid = guid.value
}
this.value = guid
}
toString() {
return this.value.toString()
}
}

50
js/entity/primitive/Integer.js Normal file → Executable file
View File

@@ -1,25 +1,25 @@
import Primitive from "./Primitive"
export default class Integer extends Primitive {
constructor(value) {
super()
// Using constructor equality and not instanceof in order to consider both primitives and objects
if (value?.constructor === String) {
value = Number(value)
}
if (value?.constructor === Number) {
value = Math.round(value)
}
/** @type {number} */
this.value = value
}
valueOf() {
this.value
}
toString() {
return this.value.toString()
}
}
import Primitive from "./Primitive"
export default class Integer extends Primitive {
constructor(value) {
super()
// Using constructor equality and not instanceof in order to consider both primitives and objects
if (value?.constructor === String) {
value = Number(value)
}
if (value?.constructor === Number) {
value = Math.round(value)
}
/** @type {number} */
this.value = value
}
valueOf() {
this.value
}
toString() {
return this.value.toString()
}
}

44
js/entity/primitive/LocalizedTextEntity.js Normal file → Executable file
View File

@@ -1,22 +1,22 @@
import Primitive from "./Primitive"
export default class LocalizedTextEntity extends Primitive {
/**
*
* @param {String} namespace
* @param {String} key
* @param {String} value
*/
constructor(namespace, key, value) {
super()
this.namespace = namespace
this.key = key
this.value = value
}
toString() {
"NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")"
}
}
import Primitive from "./Primitive"
export default class LocalizedTextEntity extends Primitive {
/**
*
* @param {String} namespace
* @param {String} key
* @param {String} value
*/
constructor(namespace, key, value) {
super()
this.namespace = namespace
this.key = key
this.value = value
}
toString() {
"NSLOCTEXT(" + `"${this.namespace}"` + ", " + `"${this.key}"` + ", " + `"${this.value}"` + ")"
}
}

46
js/entity/primitive/ObjectReference.js Normal file → Executable file
View File

@@ -1,23 +1,23 @@
import Primitive from "./Primitive"
export default class ObjectReference extends Primitive {
/**
*
* @param {String} type
* @param {String} path
*/
constructor(type, path) {
super()
this.type = type
this.path = path
}
toString() {
return (this.type ?? "") + (
this.path
? this.type ? `'"${this.path}"'` : this.path
: ""
)
}
}
import Primitive from "./Primitive"
export default class ObjectReference extends Primitive {
/**
*
* @param {String} type
* @param {String} path
*/
constructor(type, path) {
super()
this.type = type
this.path = path
}
toString() {
return (this.type ?? "") + (
this.path
? this.type ? `'"${this.path}"'` : this.path
: ""
)
}
}

10
js/entity/primitive/Primitive.js Normal file → Executable file
View File

@@ -1,5 +1,5 @@
export default class Primitive {
toString() {
return "Unimplemented for " + this.constructor.name
}
}
export default class Primitive {
toString() {
return "Unimplemented for " + this.constructor.name
}
}

15
js/export.js Normal file → Executable file
View File

@@ -1,3 +1,12 @@
import PinSerializer from "./serialization/PinSerializer"
export { PinSerializer as PinSerializer }
import GeneralSerializer from "./serialization/GeneralSerializer"
import ObjectEntity from "./entity/ObjectEntity"
import ObjectSerializer from "./serialization/ObjectSerializer"
import PinEntity from "./entity/PinEntity"
import SerializerFactory from "./serialization/SerializerFactory"
import FunctionReferenceEntity from "./entity/FunctionReferenceEntity"
SerializerFactory.registerSerializer(ObjectEntity, new ObjectSerializer())
SerializerFactory.registerSerializer(PinEntity, new GeneralSerializer("Pin ", PinEntity, "", ",", true))
SerializerFactory.registerSerializer(FunctionReferenceEntity, new GeneralSerializer("", FunctionReferenceEntity, "", ",", false))
export { SerializerFactory as SerializerFactory, ObjectEntity as ObjectEntity }

44
js/graph/GraphEntity.js Normal file → Executable file
View File

@@ -1,22 +1,22 @@
export default class GraphEntity extends HTMLElement {
/**
*
* @param {import("../template/Template").default} template The template to render this node
*/
constructor(template) {
super()
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = null
this.template = template
}
connectedCallback() {
this.blueprint = this.closest('u-blueprint')
this.append(...this.template.getElements(this))
}
// Subclasses want to rewrite this
render() {
return ''
}
}
export default class GraphEntity extends HTMLElement {
/**
*
* @param {import("../template/Template").default} template The template to render this node
*/
constructor(template) {
super()
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = null
this.template = template
}
connectedCallback() {
this.blueprint = this.closest('u-blueprint')
this.append(...this.template.getElements(this))
}
// Subclasses want to rewrite this
render() {
return ''
}
}

58
js/graph/GraphLink.js Normal file → Executable file
View File

@@ -1,29 +1,29 @@
import UBlueprintEntity from "./UBlueprintEntity"
export default class GraphLink extends UBlueprintEntity {
/**
*
* @typedef {{
* node: String,
* pin: String
* }} PinReference
* @param {?PinReference} source
* @param {?PinReference} destination
*/
constructor(source, destination) {
super()
this.source = source
this.destination = destination
}
render() {
return `
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
}
customElements.define('u-link', GraphLink)
import UBlueprintEntity from "./UBlueprintEntity"
export default class GraphLink extends UBlueprintEntity {
/**
*
* @typedef {{
* node: String,
* pin: String
* }} PinReference
* @param {?PinReference} source
* @param {?PinReference} destination
*/
constructor(source, destination) {
super()
this.source = source
this.destination = destination
}
render() {
return `
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
}
customElements.define('u-link', GraphLink)

50
js/graph/GraphNode.js Normal file → Executable file
View File

@@ -1,25 +1,25 @@
import SelectableDraggable from "./SelectableDraggable"
import NodeTemplate from "../template/NodeTemplate"
export default class GraphNode extends SelectableDraggable {
constructor() {
super(new NodeTemplate())
this.graphNodeName = 'n/a'
this.inputs = []
this.outputs = []
}
connectedCallback() {
const type = this.getAttribute('type')?.trim()
super.connectedCallback()
this.classList.add('ueb-node')
if (this.selected) {
this.classList.add('ueb-selected')
}
this.style.setProperty('--ueb-position-x', this.location[0])
this.style.setProperty('--ueb-position-y', this.location[1])
}
}
customElements.define('u-node', GraphNode)
import NodeTemplate from "../template/NodeTemplate"
import SelectableDraggable from "./SelectableDraggable"
export default class GraphNode extends SelectableDraggable {
constructor() {
super(new NodeTemplate())
this.graphNodeName = 'n/a'
this.inputs = []
this.outputs = []
}
connectedCallback() {
const type = this.getAttribute('type')?.trim()
super.connectedCallback()
this.classList.add('ueb-node')
if (this.selected) {
this.classList.add('ueb-selected')
}
this.style.setProperty('--ueb-position-x', this.location[0])
this.style.setProperty('--ueb-position-y', this.location[1])
}
}
customElements.define('u-node', GraphNode)

108
js/graph/GraphSelector.js Normal file → Executable file
View File

@@ -1,54 +1,54 @@
import FastSelectionModel from "../selection/FastSelectionModel";
import GraphEntity from "./GraphEntity";
import Template from "../template/Template";
export default class GraphSelector extends GraphEntity {
constructor() {
super(new Template())
/**
* @type {import("./GraphSelector").default}
*/
this.selectionModel = null
}
connectedCallback() {
super.connectedCallback()
this.classList.add('ueb-selector')
this.dataset.selecting = "false"
}
/**
* Create a selection rectangle starting from the specified position
* @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element)
*/
startSelecting(initialPosition) {
initialPosition = this.blueprint.compensateTranslation(initialPosition)
// Set initial position
this.style.setProperty('--ueb-select-from-x', initialPosition[0])
this.style.setProperty('--ueb-select-from-y', initialPosition[1])
// Final position coincide with the initial position, at the beginning of selection
this.style.setProperty('--ueb-select-to-x', initialPosition[0])
this.style.setProperty('--ueb-select-to-y', initialPosition[1])
this.dataset.selecting = "true"
this.selectionModel = new FastSelectionModel(initialPosition, this.blueprint.nodes, this.blueprint.nodeBoundariesSupplier, this.blueprint.nodeSelectToggleFunction)
}
/**
* Move selection rectagle to the specified final position. The initial position was specified by startSelecting()
* @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element)
*/
doSelecting(finalPosition) {
finalPosition = this.blueprint.compensateTranslation(finalPosition)
this.style.setProperty('--ueb-select-to-x', finalPosition[0])
this.style.setProperty('--ueb-select-to-y', finalPosition[1])
this.selectionModel.selectTo(finalPosition)
}
finishSelecting() {
this.dataset.selecting = "false"
this.selectionModel = null
}
}
customElements.define('u-selector', GraphSelector)
import FastSelectionModel from "../selection/FastSelectionModel"
import GraphEntity from "./GraphEntity"
import Template from "../template/Template"
export default class GraphSelector extends GraphEntity {
constructor() {
super(new Template())
/**
* @type {import("./GraphSelector").default}
*/
this.selectionModel = null
}
connectedCallback() {
super.connectedCallback()
this.classList.add('ueb-selector')
this.dataset.selecting = "false"
}
/**
* Create a selection rectangle starting from the specified position
* @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element)
*/
startSelecting(initialPosition) {
initialPosition = this.blueprint.compensateTranslation(initialPosition)
// Set initial position
this.style.setProperty('--ueb-select-from-x', initialPosition[0])
this.style.setProperty('--ueb-select-from-y', initialPosition[1])
// Final position coincide with the initial position, at the beginning of selection
this.style.setProperty('--ueb-select-to-x', initialPosition[0])
this.style.setProperty('--ueb-select-to-y', initialPosition[1])
this.dataset.selecting = "true"
this.selectionModel = new FastSelectionModel(initialPosition, this.blueprint.nodes, this.blueprint.nodeBoundariesSupplier, this.blueprint.nodeSelectToggleFunction)
}
/**
* Move selection rectagle to the specified final position. The initial position was specified by startSelecting()
* @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element)
*/
doSelecting(finalPosition) {
finalPosition = this.blueprint.compensateTranslation(finalPosition)
this.style.setProperty('--ueb-select-to-x', finalPosition[0])
this.style.setProperty('--ueb-select-to-y', finalPosition[1])
this.selectionModel.selectTo(finalPosition)
}
finishSelecting() {
this.dataset.selecting = "false"
this.selectionModel = null
}
}
customElements.define('u-selector', GraphSelector)

140
js/graph/SelectableDraggable.js Normal file → Executable file
View File

@@ -1,70 +1,70 @@
import Drag from "../input/Drag"
import GraphEntity from "./GraphEntity"
export default class SelectableDraggable extends GraphEntity {
constructor(template) {
super(template)
this.dragObject = null
this.location = [0, 0]
this.selected = false
let self = this
this.dragHandler = (e) => {
self.addLocation(e.detail.value)
}
}
connectedCallback() {
super.connectedCallback()
this.dragObject = new Drag(this, null, { // UDrag doesn't need blueprint
looseTarget: true
})
}
disconnectedCallback() {
this.dragObject.unlistenDOMElement()
}
setLocation(value = [0, 0]) {
this.location = value
this.style.setProperty('--ueb-position-x', this.location[0])
this.style.setProperty('--ueb-position-y', this.location[1])
}
addLocation(value) {
this.setLocation([this.location[0] + value[0], this.location[1] + value[1]])
}
dragDispatch(value) {
if (!this.selected) {
this.blueprint.unselectAll()
this.setSelected(true)
}
let dragEvent = new CustomEvent('uDragSelected', {
detail: {
instigator: this,
value: value
},
bubbles: false,
cancelable: true,
composed: false,
})
this.blueprint.dispatchEvent(dragEvent)
}
setSelected(value = true) {
if (this.selected == value) {
return
}
this.selected = value
if (this.selected) {
this.classList.add('ueb-selected')
this.blueprint.addEventListener('uDragSelected', this.dragHandler)
} else {
this.classList.remove('ueb-selected')
this.blueprint.removeEventListener('uDragSelected', this.dragHandler)
}
}
}
import Drag from "../input/Drag"
import GraphEntity from "./GraphEntity"
export default class SelectableDraggable extends GraphEntity {
constructor(template) {
super(template)
this.dragObject = null
this.location = [0, 0]
this.selected = false
let self = this
this.dragHandler = (e) => {
self.addLocation(e.detail.value)
}
}
connectedCallback() {
super.connectedCallback()
this.dragObject = new Drag(this, null, { // UDrag doesn't need blueprint
looseTarget: true
})
}
disconnectedCallback() {
this.dragObject.unlistenDOMElement()
}
setLocation(value = [0, 0]) {
this.location = value
this.style.setProperty('--ueb-position-x', this.location[0])
this.style.setProperty('--ueb-position-y', this.location[1])
}
addLocation(value) {
this.setLocation([this.location[0] + value[0], this.location[1] + value[1]])
}
dragDispatch(value) {
if (!this.selected) {
this.blueprint.unselectAll()
this.setSelected(true)
}
let dragEvent = new CustomEvent('uDragSelected', {
detail: {
instigator: this,
value: value
},
bubbles: false,
cancelable: true,
composed: false,
})
this.blueprint.dispatchEvent(dragEvent)
}
setSelected(value = true) {
if (this.selected == value) {
return
}
this.selected = value
if (this.selected) {
this.classList.add('ueb-selected')
this.blueprint.addEventListener('uDragSelected', this.dragHandler)
} else {
this.classList.remove('ueb-selected')
this.blueprint.removeEventListener('uDragSelected', this.dragHandler)
}
}
}

82
js/input/Drag.js Normal file → Executable file
View File

@@ -1,41 +1,41 @@
import MouseClickDrag from "./MouseClickDrag"
export default class Drag extends MouseClickDrag {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.stepSize = parseInt(options?.stepSize)
this.mousePosition = [0, 0]
}
snapToGrid(location) {
return [
this.stepSize * Math.round(location[0] / this.stepSize),
this.stepSize * Math.round(location[1] / this.stepSize)
]
}
startDrag() {
if (isNaN(this.stepSize) || this.stepSize <= 0) {
this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'))
if (isNaN(this.stepSize) || this.stepSize <= 0) {
this.stepSize = 1
}
}
// Get the current mouse position
this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition
}
dragTo(location, movement) {
const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location
const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]]
if (d[0] == 0 && d[1] == 0) {
return
}
this.target.dragDispatch(d)
// Reassign the position of mouse
this.mousePosition = mousePosition
}
}
import MouseClickDrag from "./MouseClickDrag"
export default class Drag extends MouseClickDrag {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.stepSize = parseInt(options?.stepSize)
this.mousePosition = [0, 0]
}
snapToGrid(location) {
return [
this.stepSize * Math.round(location[0] / this.stepSize),
this.stepSize * Math.round(location[1] / this.stepSize)
]
}
startDrag() {
if (isNaN(this.stepSize) || this.stepSize <= 0) {
this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'))
if (isNaN(this.stepSize) || this.stepSize <= 0) {
this.stepSize = 1
}
}
// Get the current mouse position
this.mousePosition = this.stepSize != 1 ? this.snapToGrid(this.clickedPosition) : this.clickedPosition
}
dragTo(location, movement) {
const mousePosition = this.stepSize != 1 ? this.snapToGrid(location) : location
const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]]
if (d[0] == 0 && d[1] == 0) {
return
}
this.target.dragDispatch(d)
// Reassign the position of mouse
this.mousePosition = mousePosition
}
}

18
js/input/DragScroll.js Normal file → Executable file
View File

@@ -1,9 +1,9 @@
import MouseClickDrag from "./MouseClickDrag"
export default class DragScroll extends MouseClickDrag {
dragTo(location, movement) {
this.blueprint.scrollDelta([-movement[0], -movement[1]])
}
}
import MouseClickDrag from "./MouseClickDrag"
export default class DragScroll extends MouseClickDrag {
dragTo(location, movement) {
this.blueprint.scrollDelta([-movement[0], -movement[1]])
}
}

200
js/input/MouseClickDrag.js Normal file → Executable file
View File

@@ -1,100 +1,100 @@
import Pointing from "./Pointing"
/**
* This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
*/
export default class MouseClickDrag extends Pointing {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.clickButton = options?.clickButton ?? 0
this.exitAnyButton = options?.exitAnyButton ?? true
this.moveEverywhere = options?.moveEverywhere ?? false
this.looseTarget = options?.looseTarget ?? false
this.started = false
this.clickedPosition = [0, 0]
const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace
let self = this
this.mouseDownHandler = function (e) {
switch (e.button) {
case self.clickButton:
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
if (self.looseTarget || e.target == e.currentTarget) {
e.stopPropagation()
self.started = false
// Attach the listeners
movementListenedElement.addEventListener('mousemove', self.mouseStartedMovingHandler)
document.addEventListener('mouseup', self.mouseUpHandler)
self.clickedPosition = self.getLocation(e)
self.clicked(self.clickedPosition)
}
break
default:
if (!self.exitAnyButton) {
self.mouseUpHandler(e)
}
break
}
}
this.mouseStartedMovingHandler = function (e) {
e.preventDefault()
e.stopPropagation()
// Delegate from now on to self.mouseMoveHandler
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler)
movementListenedElement.addEventListener('mousemove', self.mouseMoveHandler)
// Do actual actions
self.startDrag()
self.started = true
}
this.mouseMoveHandler = function (e) {
e.preventDefault()
e.stopPropagation()
const location = self.getLocation(e)
const movement = [e.movementX, e.movementY]
self.dragTo(location, movement)
}
this.mouseUpHandler = function (e) {
if (!self.exitAnyButton || e.button == self.clickButton) {
// Remove the handlers of "mousemove" and "mouseup"
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler)
movementListenedElement.removeEventListener('mousemove', self.mouseMoveHandler)
document.removeEventListener('mouseup', self.mouseUpHandler)
self.endDrag()
}
}
this.target.addEventListener('mousedown', this.mouseDownHandler)
if (this.clickButton == 2) {
this.target.addEventListener('contextmenu', this.preventDefault)
}
}
preventDefault(e) {
e.preventDefault()
}
unlistenDOMElement() {
this.target.removeEventListener('mousedown', this.mouseDownHandler)
if (this.clickButton == 2) {
this.target.removeEventListener('contextmenu', this.preventDefault)
}
}
/* Subclasses will override the following methods */
clicked(location) {
}
startDrag() {
}
dragTo(location, movement) {
}
endDrag() {
}
}
import Pointing from "./Pointing"
/**
* This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
*/
export default class MouseClickDrag extends Pointing {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.clickButton = options?.clickButton ?? 0
this.exitAnyButton = options?.exitAnyButton ?? true
this.moveEverywhere = options?.moveEverywhere ?? false
this.looseTarget = options?.looseTarget ?? false
this.started = false
this.clickedPosition = [0, 0]
const movementListenedElement = this.moveEverywhere ? document.documentElement : this.movementSpace
let self = this
this.mouseDownHandler = function (e) {
switch (e.button) {
case self.clickButton:
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
if (self.looseTarget || e.target == e.currentTarget) {
e.stopPropagation()
self.started = false
// Attach the listeners
movementListenedElement.addEventListener('mousemove', self.mouseStartedMovingHandler)
document.addEventListener('mouseup', self.mouseUpHandler)
self.clickedPosition = self.getLocation(e)
self.clicked(self.clickedPosition)
}
break
default:
if (!self.exitAnyButton) {
self.mouseUpHandler(e)
}
break
}
}
this.mouseStartedMovingHandler = function (e) {
e.preventDefault()
e.stopPropagation()
// Delegate from now on to self.mouseMoveHandler
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler)
movementListenedElement.addEventListener('mousemove', self.mouseMoveHandler)
// Do actual actions
self.startDrag()
self.started = true
}
this.mouseMoveHandler = function (e) {
e.preventDefault()
e.stopPropagation()
const location = self.getLocation(e)
const movement = [e.movementX, e.movementY]
self.dragTo(location, movement)
}
this.mouseUpHandler = function (e) {
if (!self.exitAnyButton || e.button == self.clickButton) {
// Remove the handlers of "mousemove" and "mouseup"
movementListenedElement.removeEventListener('mousemove', self.mouseStartedMovingHandler)
movementListenedElement.removeEventListener('mousemove', self.mouseMoveHandler)
document.removeEventListener('mouseup', self.mouseUpHandler)
self.endDrag()
}
}
this.target.addEventListener('mousedown', this.mouseDownHandler)
if (this.clickButton == 2) {
this.target.addEventListener('contextmenu', this.preventDefault)
}
}
preventDefault(e) {
e.preventDefault()
}
unlistenDOMElement() {
this.target.removeEventListener('mousedown', this.mouseDownHandler)
if (this.clickButton == 2) {
this.target.removeEventListener('contextmenu', this.preventDefault)
}
}
/* Subclasses will override the following methods */
clicked(location) {
}
startDrag() {
}
dragTo(location, movement) {
}
endDrag() {
}
}

62
js/input/MouseWheel.js Normal file → Executable file
View File

@@ -1,31 +1,31 @@
import Pointing from "./Pointing"
export default class MouseWheel extends Pointing {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.looseTarget = options?.looseTarget ?? true
let self = this
this.mouseWheelHandler = function (e) {
e.preventDefault()
const location = self.getLocation(e)
self.wheel(Math.sign(e.deltaY), location)
}
this.movementSpace.addEventListener('wheel', this.mouseWheelHandler, false)
// Prevent movement space from being scrolled
this.movementSpace.parentElement?.addEventListener('wheel', e => e.preventDefault())
}
/* Subclasses will override the following method */
wheel(variation, location) {
}
}
import Pointing from "./Pointing"
export default class MouseWheel extends Pointing {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.looseTarget = options?.looseTarget ?? true
let self = this
this.mouseWheelHandler = function (e) {
e.preventDefault()
const location = self.getLocation(e)
self.wheel(Math.sign(e.deltaY), location)
}
this.movementSpace.addEventListener('wheel', this.mouseWheelHandler, false)
// Prevent movement space from being scrolled
this.movementSpace.parentElement?.addEventListener('wheel', e => e.preventDefault())
}
/* Subclasses will override the following method */
wheel(variation, location) {
}
}

44
js/input/Pointing.js Normal file → Executable file
View File

@@ -1,22 +1,22 @@
import Utility from "../Utility"
export default class Pointing {
constructor(target, blueprint, options) {
/** @type {HTMLElement} */
this.target = target
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = blueprint
this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement
}
getLocation(mouseEvent) {
const scaleCorrection = 1 / Utility.getScale(this.target)
const targetOffset = this.movementSpace.getBoundingClientRect()
let location = [
(mouseEvent.clientX - targetOffset.x) * scaleCorrection,
(mouseEvent.clientY - targetOffset.y) * scaleCorrection
]
return location
}
}
import Utility from "../Utility"
export default class Pointing {
constructor(target, blueprint, options) {
/** @type {HTMLElement} */
this.target = target
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = blueprint
this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement
}
getLocation(mouseEvent) {
const scaleCorrection = 1 / Utility.getScale(this.target)
const targetOffset = this.movementSpace.getBoundingClientRect()
let location = [
(mouseEvent.clientX - targetOffset.x) * scaleCorrection,
(mouseEvent.clientY - targetOffset.y) * scaleCorrection
]
return location
}
}

54
js/input/Select.js Normal file → Executable file
View File

@@ -1,27 +1,27 @@
import MouseClickDrag from "./MouseClickDrag"
export default class Select extends MouseClickDrag {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.stepSize = options?.stepSize
this.mousePosition = [0, 0]
this.selectorElement = this.blueprint.selectorElement
}
startDrag() {
this.selectorElement.startSelecting(this.clickedPosition)
}
dragTo(location, movement) {
this.selectorElement.doSelecting(location)
}
endDrag() {
if (this.started) {
this.selectorElement.finishSelecting()
} else {
this.blueprint.unselectAll()
}
}
}
import MouseClickDrag from "./MouseClickDrag"
export default class Select extends MouseClickDrag {
constructor(target, blueprint, options) {
super(target, blueprint, options)
this.stepSize = options?.stepSize
this.mousePosition = [0, 0]
this.selectorElement = this.blueprint.selectorElement
}
startDrag() {
this.selectorElement.startSelecting(this.clickedPosition)
}
dragTo(location, movement) {
this.selectorElement.doSelecting(location)
}
endDrag() {
if (this.started) {
this.selectorElement.finishSelecting()
} else {
this.blueprint.unselectAll()
}
}
}

18
js/input/Zoom.js Normal file → Executable file
View File

@@ -1,9 +1,9 @@
import MouseWheel from "./MouseWheel";
export default class Zoom extends MouseWheel {
wheel(variation, location) {
let zoomLevel = this.blueprint.getZoom()
zoomLevel -= variation
this.blueprint.setZoom(zoomLevel, location)
}
}
import MouseWheel from "./MouseWheel";
export default class Zoom extends MouseWheel {
wheel(variation, location) {
let zoomLevel = this.blueprint.getZoom()
zoomLevel -= variation
this.blueprint.setZoom(zoomLevel, location)
}
}

328
js/selection/FastSelectionModel.js Normal file → Executable file
View File

@@ -1,164 +1,164 @@
import OrderedIndexArray from "./OrderedIndexArray"
export default class FastSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {{
* primaryBoundary: number,
* secondaryBoundary: number,
* insertionPosition: number,
* rectangle: number
* onSecondaryAxis: Boolean
* }} Metadata
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectFunc) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
/** @type Metadata[] */
this.metadata = new Array(rectangles.length)
this.primaryOrder = new OrderedIndexArray((element) => this.metadata[element].primaryBoundary)
this.secondaryOrder = new OrderedIndexArray((element) => this.metadata[element].secondaryBoundary)
this.selectFunc = selectFunc
this.rectangles = rectangles
this.primaryOrder.reserve(this.rectangles.length)
this.secondaryOrder.reserve(this.rectangles.length)
rectangles.forEach((rect, index) => {
/** @type Metadata */
let rectangleMetadata = {
primaryBoundary: this.initialPosition[0],
secondaryBoundary: this.initialPosition[1],
rectangle: index, // used to move both expandings inside the this.metadata array
onSecondaryAxis: false
}
this.metadata[index] = rectangleMetadata
selectFunc(rect, false) // Initially deselected (Eventually)
const rectangleBoundaries = boundariesFunc(rect)
// Secondary axis first because it may be inserted in this.secondaryOrder during the primary axis check
if (this.initialPosition[1] < rectangleBoundaries.secondaryInf) { // Initial position is before the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondaryInf
} else if (rectangleBoundaries.secondarySup < this.initialPosition[1]) { // Initial position is after the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondarySup
} else {
rectangleMetadata.onSecondaryAxis = true
}
if (this.initialPosition[0] < rectangleBoundaries.primaryInf) { // Initial position is before the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primaryInf
this.primaryOrder.insert(index)
} else if (rectangleBoundaries.primarySup < this.initialPosition[0]) { // Initial position is after the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primarySup
this.primaryOrder.insert(index)
} else { // Initial lays inside the rectangle (considering just this axis)
// Secondary order depends on primary order, if primary boundaries are not satisfied, the element is not watched for secondary ones
if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) {
this.secondaryOrder.insert(index)
} else {
selectFunc(rect, true)
}
}
})
this.primaryOrder.currentPosition = this.primaryOrder.getPosition(this.initialPosition[0])
this.secondaryOrder.currentPosition = this.secondaryOrder.getPosition(this.initialPosition[1])
this.computeBoundaries(this.initialPosition)
}
computeBoundaries() {
this.boundaries = {
// Primary axis negative expanding
primaryN: {
v: this.primaryOrder.getPrevValue(),
i: this.primaryOrder.getPrev()
},
primaryP: {
v: this.primaryOrder.getNextValue(),
i: this.primaryOrder.getNext()
},
// Secondary axis negative expanding
secondaryN: {
v: this.secondaryOrder.getPrevValue(),
i: this.secondaryOrder.getPrev()
},
// Secondary axis positive expanding
secondaryP: {
v: this.secondaryOrder.getNextValue(),
i: this.secondaryOrder.getNext()
}
}
}
selectTo(finalPosition) {
const direction = [
Math.sign(finalPosition[0] - this.initialPosition[0]),
Math.sign(finalPosition[1] - this.initialPosition[1])
]
const primaryBoundaryCrossed = (index, added) => {
if (this.metadata[index].onSecondaryAxis) {
this.selectFunc(this.rectangles[index], added)
} else {
if (added) {
this.secondaryOrder.insert(index, finalPosition[1])
const secondaryBoundary = this.metadata[index].secondaryBoundary
if (
// If inserted before the current position
Math.sign(finalPosition[1] - secondaryBoundary) == direction[1]
// And after initial position
&& Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1]
) {
// Secondary axis is already satisfied then
this.selectFunc(this.rectangles[index], true)
}
} else {
this.selectFunc(this.rectangles[index], false)
this.secondaryOrder.remove(index)
}
}
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[0] < this.boundaries.primaryN.v) {
--this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryN.i,
this.initialPosition[0] > this.boundaries.primaryN.v && finalPosition[0] < this.initialPosition[0])
} else if (finalPosition[0] > this.boundaries.primaryP.v) {
++this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryP.i,
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0])
}
const secondaryBoundaryCrossed = (index, added) => {
this.selectFunc(this.rectangles[index], added)
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[1] < this.boundaries.secondaryN.v) {
--this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryN.i,
this.initialPosition[1] > this.boundaries.secondaryN.v && finalPosition[1] < this.initialPosition[1])
} else if (finalPosition[1] > this.boundaries.secondaryP.v) {
++this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryP.i,
this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1])
}
this.finalPosition = finalPosition
}
}
import OrderedIndexArray from "./OrderedIndexArray"
export default class FastSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {{
* primaryBoundary: number,
* secondaryBoundary: number,
* insertionPosition: number,
* rectangle: number
* onSecondaryAxis: Boolean
* }} Metadata
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectFunc) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
/** @type Metadata[] */
this.metadata = new Array(rectangles.length)
this.primaryOrder = new OrderedIndexArray((element) => this.metadata[element].primaryBoundary)
this.secondaryOrder = new OrderedIndexArray((element) => this.metadata[element].secondaryBoundary)
this.selectFunc = selectFunc
this.rectangles = rectangles
this.primaryOrder.reserve(this.rectangles.length)
this.secondaryOrder.reserve(this.rectangles.length)
rectangles.forEach((rect, index) => {
/** @type Metadata */
let rectangleMetadata = {
primaryBoundary: this.initialPosition[0],
secondaryBoundary: this.initialPosition[1],
rectangle: index, // used to move both expandings inside the this.metadata array
onSecondaryAxis: false
}
this.metadata[index] = rectangleMetadata
selectFunc(rect, false) // Initially deselected (Eventually)
const rectangleBoundaries = boundariesFunc(rect)
// Secondary axis first because it may be inserted in this.secondaryOrder during the primary axis check
if (this.initialPosition[1] < rectangleBoundaries.secondaryInf) { // Initial position is before the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondaryInf
} else if (rectangleBoundaries.secondarySup < this.initialPosition[1]) { // Initial position is after the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondarySup
} else {
rectangleMetadata.onSecondaryAxis = true
}
if (this.initialPosition[0] < rectangleBoundaries.primaryInf) { // Initial position is before the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primaryInf
this.primaryOrder.insert(index)
} else if (rectangleBoundaries.primarySup < this.initialPosition[0]) { // Initial position is after the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primarySup
this.primaryOrder.insert(index)
} else { // Initial lays inside the rectangle (considering just this axis)
// Secondary order depends on primary order, if primary boundaries are not satisfied, the element is not watched for secondary ones
if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) {
this.secondaryOrder.insert(index)
} else {
selectFunc(rect, true)
}
}
})
this.primaryOrder.currentPosition = this.primaryOrder.getPosition(this.initialPosition[0])
this.secondaryOrder.currentPosition = this.secondaryOrder.getPosition(this.initialPosition[1])
this.computeBoundaries(this.initialPosition)
}
computeBoundaries() {
this.boundaries = {
// Primary axis negative expanding
primaryN: {
v: this.primaryOrder.getPrevValue(),
i: this.primaryOrder.getPrev()
},
primaryP: {
v: this.primaryOrder.getNextValue(),
i: this.primaryOrder.getNext()
},
// Secondary axis negative expanding
secondaryN: {
v: this.secondaryOrder.getPrevValue(),
i: this.secondaryOrder.getPrev()
},
// Secondary axis positive expanding
secondaryP: {
v: this.secondaryOrder.getNextValue(),
i: this.secondaryOrder.getNext()
}
}
}
selectTo(finalPosition) {
const direction = [
Math.sign(finalPosition[0] - this.initialPosition[0]),
Math.sign(finalPosition[1] - this.initialPosition[1])
]
const primaryBoundaryCrossed = (index, added) => {
if (this.metadata[index].onSecondaryAxis) {
this.selectFunc(this.rectangles[index], added)
} else {
if (added) {
this.secondaryOrder.insert(index, finalPosition[1])
const secondaryBoundary = this.metadata[index].secondaryBoundary
if (
// If inserted before the current position
Math.sign(finalPosition[1] - secondaryBoundary) == direction[1]
// And after initial position
&& Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1]
) {
// Secondary axis is already satisfied then
this.selectFunc(this.rectangles[index], true)
}
} else {
this.selectFunc(this.rectangles[index], false)
this.secondaryOrder.remove(index)
}
}
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[0] < this.boundaries.primaryN.v) {
--this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryN.i,
this.initialPosition[0] > this.boundaries.primaryN.v && finalPosition[0] < this.initialPosition[0])
} else if (finalPosition[0] > this.boundaries.primaryP.v) {
++this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryP.i,
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0])
}
const secondaryBoundaryCrossed = (index, added) => {
this.selectFunc(this.rectangles[index], added)
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[1] < this.boundaries.secondaryN.v) {
--this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryN.i,
this.initialPosition[1] > this.boundaries.secondaryN.v && finalPosition[1] < this.initialPosition[1])
} else if (finalPosition[1] > this.boundaries.secondaryP.v) {
++this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryP.i,
this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1])
}
this.finalPosition = finalPosition
}
}

306
js/selection/OrderedIndexArray.js Normal file → Executable file
View File

@@ -1,153 +1,153 @@
export default class OrderedIndexArray {
/**
* @param {(arrayElement: number) => number} compareFunction A function that, given acouple of elements of the array telles what order are they on.
* @param {(number|array)} value Initial length or array to copy from
*/
constructor(comparisonValueSupplier = (a) => a, value = null) {
this.array = new Uint32Array(value)
this.comparisonValueSupplier = comparisonValueSupplier
this.length = 0
this.currentPosition = 0
}
/**
*
* @param {number} index The index of the value to return
* @returns The element of the array
*/
get(index) {
if (index >= 0 && index < this.length) {
return this.array[index]
}
return null
}
/**
* Returns the array used by this object.
* @returns The array.
*/
getArray() {
return this.array
}
/**
* Get the position that the value supplied should (or does) occupy in the aray.
* @param {number} value The value to look for (it doesn't have to be part of the array).
* @returns The position index.
*/
getPosition(value) {
let l = 0
let r = this.length
while (l < r) {
let m = Math.floor((l + r) / 2)
if (this.comparisonValueSupplier(this.array[m]) < value) {
l = m + 1
} else {
r = m
}
}
return l
}
reserve(length) {
if (this.array.length < length) {
let newArray = new Uint32Array(length)
newArray.set(this.array)
this.array = newArray
}
}
/**
* Inserts the element in the array.
* @param element {number} The value to insert into the array.
* @returns {number} The position into occupied by value into the array.
*/
insert(element, comparisonValue = null) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (
position < this.currentPosition
|| comparisonValue != null && position == this.currentPosition && this.comparisonValueSupplier(element) < comparisonValue) {
++this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length + 1)
newArray.set(this.array.subarray(0, position), 0)
newArray[position] = element
newArray.set(this.array.subarray(position), position + 1)
this.array = newArray
*/
this.shiftRight(position)
this.array[position] = element
++this.length
return position
}
/**
* Removes the element from the array.
* @param {number} value The value of the element to be remove.
*/
remove(element) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (this.array[position] == element) {
this.removeAt(position)
}
}
/**
* Removes the element into the specified position from the array.
* @param {number} position The index of the element to be remove.
*/
removeAt(position) {
if (position < this.currentPosition) {
--this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length - 1)
newArray.set(this.array.subarray(0, position), 0)
newArray.set(this.array.subarray(position + 1), position)
this.array = newArray
*/
this.shiftLeft(position)
--this.length
return position
}
getNext() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.get(this.currentPosition)
}
return null
}
getNextValue() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.comparisonValueSupplier(this.get(this.currentPosition))
} else {
return Number.MAX_SAFE_INTEGER
}
}
getPrev() {
if (this.currentPosition > 0) {
return this.get(this.currentPosition - 1)
}
return null
}
getPrevValue() {
if (this.currentPosition > 0) {
return this.comparisonValueSupplier(this.get(this.currentPosition - 1))
} else {
return Number.MIN_SAFE_INTEGER
}
}
shiftLeft(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit + steps), leftLimit)
}
shiftRight(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit, -steps), leftLimit + steps)
}
}
export default class OrderedIndexArray {
/**
* @param {(arrayElement: number) => number} compareFunction A function that, given acouple of elements of the array telles what order are they on.
* @param {(number|array)} value Initial length or array to copy from
*/
constructor(comparisonValueSupplier = (a) => a, value = null) {
this.array = new Uint32Array(value)
this.comparisonValueSupplier = comparisonValueSupplier
this.length = 0
this.currentPosition = 0
}
/**
*
* @param {number} index The index of the value to return
* @returns The element of the array
*/
get(index) {
if (index >= 0 && index < this.length) {
return this.array[index]
}
return null
}
/**
* Returns the array used by this object.
* @returns The array.
*/
getArray() {
return this.array
}
/**
* Get the position that the value supplied should (or does) occupy in the aray.
* @param {number} value The value to look for (it doesn't have to be part of the array).
* @returns The position index.
*/
getPosition(value) {
let l = 0
let r = this.length
while (l < r) {
let m = Math.floor((l + r) / 2)
if (this.comparisonValueSupplier(this.array[m]) < value) {
l = m + 1
} else {
r = m
}
}
return l
}
reserve(length) {
if (this.array.length < length) {
let newArray = new Uint32Array(length)
newArray.set(this.array)
this.array = newArray
}
}
/**
* Inserts the element in the array.
* @param element {number} The value to insert into the array.
* @returns {number} The position into occupied by value into the array.
*/
insert(element, comparisonValue = null) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (
position < this.currentPosition
|| comparisonValue != null && position == this.currentPosition && this.comparisonValueSupplier(element) < comparisonValue) {
++this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length + 1)
newArray.set(this.array.subarray(0, position), 0)
newArray[position] = element
newArray.set(this.array.subarray(position), position + 1)
this.array = newArray
*/
this.shiftRight(position)
this.array[position] = element
++this.length
return position
}
/**
* Removes the element from the array.
* @param {number} value The value of the element to be remove.
*/
remove(element) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (this.array[position] == element) {
this.removeAt(position)
}
}
/**
* Removes the element into the specified position from the array.
* @param {number} position The index of the element to be remove.
*/
removeAt(position) {
if (position < this.currentPosition) {
--this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length - 1)
newArray.set(this.array.subarray(0, position), 0)
newArray.set(this.array.subarray(position + 1), position)
this.array = newArray
*/
this.shiftLeft(position)
--this.length
return position
}
getNext() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.get(this.currentPosition)
}
return null
}
getNextValue() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.comparisonValueSupplier(this.get(this.currentPosition))
} else {
return Number.MAX_SAFE_INTEGER
}
}
getPrev() {
if (this.currentPosition > 0) {
return this.get(this.currentPosition - 1)
}
return null
}
getPrevValue() {
if (this.currentPosition > 0) {
return this.comparisonValueSupplier(this.get(this.currentPosition - 1))
} else {
return Number.MIN_SAFE_INTEGER
}
}
shiftLeft(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit + steps), leftLimit)
}
shiftRight(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit, -steps), leftLimit + steps)
}
}

88
js/selection/SimpleSelectionModel.js Normal file → Executable file
View File

@@ -1,44 +1,44 @@
export default class SimpleSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectToggleFunction A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
this.boundariesFunc = boundariesFunc
this.selectToggleFunction = selectToggleFunction
this.rectangles = rectangles
}
selectTo(finalPosition) {
let primaryInf = Math.min(finalPosition[0], this.initialPosition[0])
let primarySup = Math.max(finalPosition[0], this.initialPosition[0])
let secondaryInf = Math.min(finalPosition[1], this.initialPosition[1])
let secondarySup = Math.max(finalPosition[1], this.initialPosition[1])
this.finalPosition = finalPosition
this.rectangles.forEach(rect => {
let boundaries = this.boundariesFunc(rect)
if (
Math.max(boundaries.primaryInf, primaryInf) < Math.min(boundaries.primarySup, primarySup)
&& Math.max(boundaries.secondaryInf, secondaryInf) < Math.min(boundaries.secondarySup, secondarySup)
) {
this.selectToggleFunction(rect, true)
} else {
this.selectToggleFunction(rect, false)
}
})
}
}
export default class SimpleSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectToggleFunction A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
this.boundariesFunc = boundariesFunc
this.selectToggleFunction = selectToggleFunction
this.rectangles = rectangles
}
selectTo(finalPosition) {
let primaryInf = Math.min(finalPosition[0], this.initialPosition[0])
let primarySup = Math.max(finalPosition[0], this.initialPosition[0])
let secondaryInf = Math.min(finalPosition[1], this.initialPosition[1])
let secondarySup = Math.max(finalPosition[1], this.initialPosition[1])
this.finalPosition = finalPosition
this.rectangles.forEach(rect => {
let boundaries = this.boundariesFunc(rect)
if (
Math.max(boundaries.primaryInf, primaryInf) < Math.min(boundaries.primarySup, primarySup)
&& Math.max(boundaries.secondaryInf, secondaryInf) < Math.min(boundaries.secondarySup, secondarySup)
) {
this.selectToggleFunction(rect, true)
} else {
this.selectToggleFunction(rect, false)
}
})
}
}

View File

@@ -0,0 +1,25 @@
import Grammar from "./Grammar"
import Serializer from "./Serializer"
export default class GeneralSerializer extends Serializer {
constructor(keyword = "", entityType, prefix = "", separator = ",", trailingSeparator = false) {
super(entityType, prefix, separator, trailingSeparator)
this.keyword = keyword
}
read(value) {
let grammar = Grammar.getGrammarForType(Serializer.grammar, this.entityType)
const parseResult = grammar.parse(value)
if (!parseResult.status) {
console.error("Error when trying to parse the entity " + this.entityType.prototype.constructor.name)
return parseResult
}
return parseResult.value
}
write(object) {
let result = `${this.key ?? ""}(${this.subWrite([], object)})`
return result
}
}

354
js/serialization/Grammar.js Normal file → Executable file
View File

@@ -1,177 +1,177 @@
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import Guid from "../entity/primitive/Guid"
import Integer from "../entity/primitive/Integer"
import ObjectReference from "../entity/primitive/ObjectReference"
import Parsimmon from "parsimmon"
import PinEntity from "../entity/PinEntity"
import Utility from "../Utility"
import ObjectEntity from "../entity/ObjectEntity"
import LocalizedTextEntity from "../entity/primitive/LocalizedTextEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
let P = Parsimmon
export default class Grammar {
// General
InlineWhitespace = _ => P.regex(/[^\S\n]+/).desc("inline whitespace")
InlineOptWhitespace = _ => P.regex(/[^\S\n]*/).desc("inline optional whitespace")
WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline")
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
None = _ => P.string("None").map(_ => new ObjectReference({ type: "None" })).desc("none")
Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False")
Number = _ => P.regex(/[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number")
Integer = _ => P.regex(/[0-9]+/).map(v => new Integer(v)).desc("an integer")
String = _ => P.regex(/(?:[^"\\]|\\")*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")')
Word = _ => P.regex(/[a-zA-Z]+/).desc("a word")
Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value")
PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/)
ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.sepBy1(P.string(".")).tieWith("."))
.tie()
.atLeast(2)
.tie()
.desc('a path (words with possibly underscore, separated by ".", separated by "/")')
Reference = r => P.alt(
r.None,
r.ReferencePath.map(path => new ObjectReference("", path)),
P.seqMap(
r.Word,
P.optWhitespace,
P.alt(P.string(`"`), P.string(`'"`)).chain(
result => r.ReferencePath.skip(
P.string(result.split("").reverse().join(""))
)
),
(referenceType, _, referencePath) => new ObjectReference(referenceType, referencePath)
)
)
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""')
AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText)
LocalizedText = r => P.seqMap(
P.string("NSLOCTEXT").skip(P.optWhitespace).skip(P.string("(")),
r.String.trim(P.optWhitespace), // namespace
P.string(","),
r.String.trim(P.optWhitespace), // key
P.string(","),
r.String.trim(P.optWhitespace), // value
P.string(")"),
(_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity(namespace, key, value)
)
PinReference = r => P.seqMap(
r.PathSymbol,
P.whitespace,
r.Guid,
(objectName, _, pinGuid) => new PinReferenceEntity({
objectName: objectName,
pinGuid: pinGuid
})
)
static getGrammarForType(r, attributeType, defaultGrammar) {
switch (Utility.getType(attributeType)) {
case Boolean:
return r.Boolean
case Number:
return r.Number
case Integer:
return r.Integer
case String:
return r.String
case Guid:
return r.Guid
case ObjectReference:
return r.Reference
case LocalizedTextEntity:
return r.LocalizedText
case PinReferenceEntity:
return r.PinReference
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
return r.Pin
case Array:
return P.seqMap(
P.string("("),
attributeType
.map(v => Grammar.getGrammarForType(r, Utility.getType(v)))
.reduce((accum, cur) =>
!cur || accum === r.AttributeAnyValue
? r.AttributeAnyValue
: accum.or(cur)
)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?\s*/)),
P.string(")"),
(_, grammar, __) => grammar
)
default:
return defaultGrammar
}
}
// Meta grammar
static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
attributeGrammar.skip(valueSeparator)
.chain(attributeName => {
const attributeKey = attributeName.split(".")
const attribute = attributeSupplier(attributeKey)
let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue)
return attributeValueGrammar.map(attributeValue =>
entity => Utility.objectSet(entity, attributeKey, attributeValue, true)
) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value
})
// Meta grammar
static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) =>
/**
* Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)'
* Then it populates an object of type EntityType with the attribute values found inside the parentheses.
*/
P.seqMap(
P.seq(keyGrammar, P.optWhitespace, P.string("(")),
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma
P.string(')'),
(_, attributes, __) => {
let result = new entityType()
attributes.forEach(attributeSetter => attributeSetter(result))
return result
})
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(
r,
P.succeed(),
FunctionReferenceEntity,
attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey)
)
Pin = r => Grammar.CreateMultiAttributeGrammar(
r,
P.string("Pin"),
PinEntity,
attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey)
)
CustomProperties = r =>
P.string("CustomProperties")
.then(P.whitespace)
.then(r.Pin)
.map(pin => entity => {
/** @type {Array} */
let properties = Utility.objectGet(entity, ["CustomProperties"], [])
properties.push(pin)
Utility.objectSet(entity, ["CustomProperties"], properties, true)
})
Object = r => P.seqMap(
P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace),
P.alt(
r.CustomProperties,
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey))
)
.sepBy1(P.whitespace),
P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")),
(_, attributes, __) => {
let result = new ObjectEntity()
attributes.forEach(attributeSetter => attributeSetter(result))
return result
}
)
MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace)
}
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import Guid from "../entity/primitive/Guid"
import Integer from "../entity/primitive/Integer"
import LocalizedTextEntity from "../entity/primitive/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectReference from "../entity/primitive/ObjectReference"
import Parsimmon from "parsimmon"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import Utility from "../Utility"
let P = Parsimmon
export default class Grammar {
// General
InlineWhitespace = _ => P.regex(/[^\S\n]+/).desc("inline whitespace")
InlineOptWhitespace = _ => P.regex(/[^\S\n]*/).desc("inline optional whitespace")
WhitespaceNewline = _ => P.regex(/[^\S\n]*\n\s*/).desc("whitespace with at least a newline")
Null = r => P.seq(P.string("("), r.InlineOptWhitespace, P.string(")")).map(_ => null).desc("null: ()")
None = _ => P.string("None").map(_ => new ObjectReference("None", "")).desc("none")
Boolean = _ => P.alt(P.string("True"), P.string("False")).map(v => v === "True" ? true : false).desc("either True or False")
Number = _ => P.regex(/[0-9]+(?:\.[0-9]+)?/).map(Number).desc("a number")
Integer = _ => P.regex(/[0-9]+/).map(v => new Integer(v)).desc("an integer")
String = _ => P.regex(/(?:[^"\\]|\\")*/).wrap(P.string('"'), P.string('"')).desc('string (with possibility to escape the quote using \")')
Word = _ => P.regex(/[a-zA-Z]+/).desc("a word")
Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).map(v => new Guid(v)).desc("32 digit hexadecimal (accepts all the letters for safety) value")
PathSymbol = _ => P.regex(/[0-9a-zA-Z_]+/)
ReferencePath = r => P.seq(P.string("/"), r.PathSymbol.sepBy1(P.string(".")).tieWith("."))
.tie()
.atLeast(2)
.tie()
.desc('a path (words with possibly underscore, separated by ".", separated by "/")')
Reference = r => P.alt(
r.None,
r.ReferencePath.map(path => new ObjectReference("", path)),
P.seqMap(
r.Word,
P.optWhitespace,
P.alt(P.string(`"`), P.string(`'"`)).chain(
result => r.ReferencePath.skip(
P.string(result.split("").reverse().join(""))
)
),
(referenceType, _, referencePath) => new ObjectReference(referenceType, referencePath)
)
)
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".").desc('words separated by ""')
AttributeAnyValue = r => P.alt(r.Null, r.None, r.Boolean, r.Number, r.Integer, r.String, r.Guid, r.Reference, r.LocalizedText)
LocalizedText = r => P.seqMap(
P.string("NSLOCTEXT").skip(P.optWhitespace).skip(P.string("(")),
r.String.trim(P.optWhitespace), // namespace
P.string(","),
r.String.trim(P.optWhitespace), // key
P.string(","),
r.String.trim(P.optWhitespace), // value
P.string(")"),
(_, namespace, __, key, ___, value, ____) => new LocalizedTextEntity(namespace, key, value)
)
PinReference = r => P.seqMap(
r.PathSymbol,
P.whitespace,
r.Guid,
(objectName, _, pinGuid) => new PinReferenceEntity({
objectName: objectName,
pinGuid: pinGuid
})
)
static getGrammarForType(r, attributeType, defaultGrammar) {
switch (Utility.getType(attributeType)) {
case Boolean:
return r.Boolean
case Number:
return r.Number
case Integer:
return r.Integer
case String:
return r.String
case Guid:
return r.Guid
case ObjectReference:
return r.Reference
case LocalizedTextEntity:
return r.LocalizedText
case PinReferenceEntity:
return r.PinReference
case FunctionReferenceEntity:
return r.FunctionReference
case PinEntity:
return r.Pin
case Array:
return P.seqMap(
P.string("("),
attributeType
.map(v => Grammar.getGrammarForType(r, Utility.getType(v)))
.reduce((accum, cur) =>
!cur || accum === r.AttributeAnyValue
? r.AttributeAnyValue
: accum.or(cur)
)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?\s*/)),
P.string(")"),
(_, grammar, __) => grammar
)
default:
return defaultGrammar
}
}
// Meta grammar
static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
attributeGrammar.skip(valueSeparator)
.chain(attributeName => {
const attributeKey = attributeName.split(".")
const attribute = attributeSupplier(attributeKey)
let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue)
return attributeValueGrammar.map(attributeValue =>
entity => Utility.objectSet(entity, attributeKey, attributeValue, true)
) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value
})
// Meta grammar
static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) =>
/**
* Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)'
* Then it populates an object of type EntityType with the attribute values found inside the parentheses.
*/
P.seqMap(
P.seq(keyGrammar, P.optWhitespace, P.string("(")),
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma
P.string(')'),
(_, attributes, __) => {
let result = new entityType()
attributes.forEach(attributeSetter => attributeSetter(result))
return result
})
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(
r,
P.succeed(),
FunctionReferenceEntity,
attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey)
)
Pin = r => Grammar.CreateMultiAttributeGrammar(
r,
P.string("Pin"),
PinEntity,
attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey)
)
CustomProperties = r =>
P.string("CustomProperties")
.then(P.whitespace)
.then(r.Pin)
.map(pin => entity => {
/** @type {Array} */
let properties = Utility.objectGet(entity, ["CustomProperties"], [])
properties.push(pin)
Utility.objectSet(entity, ["CustomProperties"], properties, true)
})
Object = r => P.seqMap(
P.seq(P.string("Begin"), P.whitespace, P.string("Object"), P.whitespace),
P.alt(
r.CustomProperties,
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey))
)
.sepBy1(P.whitespace),
P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")),
(_, attributes, __) => {
let result = new ObjectEntity()
attributes.forEach(attributeSetter => attributeSetter(result))
return result
}
)
MultipleObject = r => r.Object.sepBy1(P.whitespace).trim(P.optWhitespace)
}

View File

@@ -1,32 +0,0 @@
import Serializer from "./Serializer";
export default class ObjectSerializer extends Serializer {
showProperty(attributeKey, attributeValue) {
switch (attributeKey.toString()) {
case "Class":
case "Name":
// Serielized separately
return false
}
return super.showProperty(attributeKey, attributeValue)
}
read(value) {
const parseResult = Serializer.grammar.Object.parse(value)
if (!parseResult.status) {
console.error("Error when trying to parse the object.")
return parseResult
}
return parseResult.value
}
write(object) {
let result = `
Begin Object Class=${object.Class} Name=${object.Name}
${this.subWrite([], object, "\n", " ")}
End Object
`
return result
}
}

View File

@@ -0,0 +1,46 @@
import ObjectEntity from "../entity/ObjectEntity"
import PinEntity from "../entity/PinEntity"
import Serializer from "./Serializer"
import SerializerFactory from "./SerializerFactory"
export default class ObjectSerializer extends Serializer {
constructor() {
super(ObjectEntity, " ", "\n", false)
}
showProperty(attributeKey, attributeValue) {
switch (attributeKey.toString()) {
case "Class":
case "Name":
case "CustomProperties":
// Serielized separately
return false
}
return super.showProperty(attributeKey, attributeValue)
}
read(value) {
const parseResult = Serializer.grammar.Object.parse(value)
if (!parseResult.status) {
console.error("Error when trying to parse the object.")
return parseResult
}
return parseResult.value
}
/**
*
* @param {ObjectEntity} object
* @returns
*/
write(object) {
let result = `Begin Object Class=${object.Class} Name=${object.Name}
${this.subWrite([], object)
+ object
.CustomProperties.map(pin => this.separator + this.prefix + "CustomProperties " + SerializerFactory.getSerializer(PinEntity).write(pin))
.join("")}
End Object`
return result
}
}

View File

@@ -1,23 +0,0 @@
import PinEntity from "../entity/PinEntity"
import Serializer from "./Serializer"
export default class PinSerializer extends Serializer {
getAttributes() {
return PinEntity.attributes
}
read(value) {
const parseResult = Serializer.grammar.Pin.parse(value)
if (!parseResult.status) {
console.error("Error when trying to parse the pin.")
return parseResult
}
return parseResult.value
}
write(object) {
let result = `Pin (${this.subWrite([], object, ",")})`
return result
}
}

154
js/serialization/Serializer.js Normal file → Executable file
View File

@@ -1,82 +1,72 @@
import Grammar from "./Grammar"
import Guid from "../entity/primitive/Guid"
import ObjectReference from "../entity/primitive/ObjectReference"
import Parsimmon from "parsimmon"
import TypeInitialization from "../entity/TypeInitialization"
import Utility from "../Utility"
export default class Serializer {
static grammar = Parsimmon.createLanguage(new Grammar())
writeValue(value) {
if (value === null) {
return "()"
}
switch (value?.constructor) {
case Function:
return this.writeValue(value())
case Boolean:
return Utility.FirstCapital(value.toString())
case ObjectReference:
case Guid:
return value.toString()
case String:
return `"${value}"`
}
}
/**
*
* @param {String[]} prefix
* @param {Object} object
* @param {String} separator
* @returns
*/
subWrite(key, object, separator = "\n", prefix = "") {
let result = ""
let fullKey = key.concat("")
const last = fullKey.length - 1
for (const property in object) {
fullKey[last] = property
const value = object[property]
if (object[property]?.constructor === Object) {
// Recursive call when finding an object
result += this.subWrite(fullKey, value, separator, prefix)
} else if (this.showProperty(fullKey, value)) {
result += prefix + fullKey.join(".") + "=" + this.writeValue(value) + separator
}
}
return result
}
getAttributes() {
return PinEntity.attributes
}
showProperty(attributeKey, attributeValue) {
const attributes = this.getAttributes()
const attribute = Utility.objectGet(attributes, attributeKey)
if (attribute instanceof TypeInitialization) {
return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault
}
return true
}
/**
*
* @param {String} value
*/
read(value) {
}
/**
* Returns a string representing the object (serialization)
* @param {*} object
* @returns The serialized string
*/
write(object) {
return ''
}
}
import Entity from "../entity/Entity"
import Grammar from "./Grammar"
import Parsimmon from "parsimmon"
import Primitive from "../entity/primitive/Primitive"
import SerializerFactory from "./SerializerFactory"
import TypeInitialization from "../entity/TypeInitialization"
import Utility from "../Utility"
export default class Serializer {
static grammar = Parsimmon.createLanguage(new Grammar())
constructor(entityType, prefix = "", separator = ",", trailingSeparator = false) {
this.entityType = entityType
this.prefix = prefix
this.separator = separator
this.trailingSeparator = trailingSeparator
}
writeValue(value) {
if (value === null) {
return "()"
}
switch (value?.constructor) {
case Function:
return this.writeValue(value())
case Boolean:
return Utility.FirstCapital(value.toString())
case Number:
return value.toString()
case String:
return `"${value}"`
}
if (value instanceof Entity) {
return SerializerFactory.getSerializer(Utility.getType(value)).write(value)
}
if (value instanceof Primitive) {
return value.toString()
}
}
subWrite(key, object) {
let result = ""
let fullKey = key.concat("")
const last = fullKey.length - 1
for (const property in object) {
fullKey[last] = property
const value = object[property]
if (object[property]?.constructor === Object) {
// Recursive call when finding an object
result += this.subWrite(fullKey, value, this.prefix, this.separator)
} else if (this.showProperty(fullKey, value)) {
result += (result.length ? this.separator : "") + this.prefix + fullKey.join(".") + "=" + this.writeValue(value)
}
}
if (this.trailingSeparator && result.length) {
// append separator at the end if asked and there was printed content
result += this.separator
}
return result
}
showProperty(attributeKey, attributeValue) {
const attributes = this.entityType.attributes
const attribute = Utility.objectGet(attributes, attributeKey)
if (attribute instanceof TypeInitialization) {
return !Utility.equals(attribute.value, attributeValue) || attribute.showDefault
}
return true
}
}

32
js/serialization/SerializerFactory.js Normal file → Executable file
View File

@@ -1,17 +1,15 @@
import PinEntity from "../entity/PinEntity"
import Utility from "../Utility"
import PinSerializer from "./PinSerializer"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectSerialize from "./ObjectSerialize"
export default class SerializerFactory {
static serializers = new Map([
[PinEntity, PinSerializer],
[ObjectEntity, ObjectSerialize]
])
static createSerializer(object) {
return new SerializerFactory.serializers.get(Utility.getType(object))()
}
}
import Utility from "../Utility"
export default class SerializerFactory {
static #serializers = new Map()
static registerSerializer(entity, object) {
SerializerFactory.#serializers.set(entity, object)
}
static getSerializer(entity) {
return SerializerFactory.#serializers.get(Utility.getType(entity))
}
}

92
js/template/BlueprintTemplate.js Normal file → Executable file
View File

@@ -1,46 +1,46 @@
import Template from "./Template";
export default class BlueprintTemplate extends Template {
header(element) {
return `
<div class="ueb-viewport-header">
<div class="ueb-viewport-zoom">1:1</div>
</div>
`
}
overlay() {
return `
<div class="ueb-viewport-overlay"></div>
`
}
/**
*
* @param {import("../Blueprint").Blueprint} element
* @returns
*/
viewport(element) {
return `
<div class="ueb-viewport-body">
<div class="ueb-grid"
style="--ueb-additional-x:${element.additional[0]}; --ueb-additional-y:${element.additional[1]}; --ueb-translate-x:${element.translateValue[0]}; --ueb-translate-y:${element.translateValue[1]}">
<div class="ueb-grid-content" data-nodes></div>
</div>
</div>
`
}
/**
* 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)}
`
}
}
import Template from "./Template"
export default class BlueprintTemplate extends Template {
header(element) {
return `
<div class="ueb-viewport-header">
<div class="ueb-viewport-zoom">1:1</div>
</div>
`
}
overlay() {
return `
<div class="ueb-viewport-overlay"></div>
`
}
/**
*
* @param {import("../Blueprint").Blueprint} element
* @returns
*/
viewport(element) {
return `
<div class="ueb-viewport-body">
<div class="ueb-grid"
style="--ueb-additional-x:${element.additional[0]}; --ueb-additional-y:${element.additional[1]}; --ueb-translate-x:${element.translateValue[0]}; --ueb-translate-y:${element.translateValue[1]}">
<div class="ueb-grid-content" data-nodes></div>
</div>
</div>
`
}
/**
* 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)}
`
}
}

136
js/template/NodeTemplate.js Normal file → Executable file
View File

@@ -1,68 +1,68 @@
import { PinEntity } from "../../dist/ueblueprint"
import Template from "./Template"
export default class NodeTemplate extends Template {
/**
* Computes the html content of the target element.
* @param {HTMLElement} entity Entity representing the element
* @returns The computed html
*/
header(entity) {
return `
<div class="ueb-node-header">
<span class="ueb-node-name">
<span class="ueb-node-symbol"></span>
<span class="ueb-node-text">${entity.graphNodeName}</span>
</span>
</div>
`
}
/**
* Computes the html content of the target element.
* @param {import("../entity/ObjectEntity").default} entity Entity representing the element
* @returns The computed html
*/
body(entity) {
let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity)
let outputs = inputs.filter(v => v.isOutput())
inputs = inputs.filter(v => !v.isOutput())
return `
<div class="ueb-node-body">
<div class="ueb-node-inputs">
${inputs.map((input, index) => `
<div class="ueb-node-input ueb-node-value-${input.type}">
<span class="ueb-node-value-icon ${inputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
${input.name}
</div>
`).join("") ?? ""}
</div>
<div class="ueb-node-outputs">
${outputs.map((output, index) => `
<div class="ueb-node-output ueb-node-value-${output.type}">
${output.name}
<span class="ueb-node-value-icon ${entity.outputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
</div>
`).join("") ?? ''}
</div>
</div>
`
}
/**
* Computes the html content of the target element.
* @param {HTMLElement} entity Entity representing the element
* @returns The computed html
*/
render(entity) {
return `
<div class="ueb-node-border">
<div class="ueb-node-content">
${this.header(entity)}
${this.body(entity)}
</div>
</div>
`
}
}
import { PinEntity } from "../../dist/ueblueprint"
import Template from "./Template"
export default class NodeTemplate extends Template {
/**
* Computes the html content of the target element.
* @param {HTMLElement} entity Entity representing the element
* @returns The computed html
*/
header(entity) {
return `
<div class="ueb-node-header">
<span class="ueb-node-name">
<span class="ueb-node-symbol"></span>
<span class="ueb-node-text">${entity.graphNodeName}</span>
</span>
</div>
`
}
/**
* Computes the html content of the target element.
* @param {import("../entity/ObjectEntity").default} entity Entity representing the element
* @returns The computed html
*/
body(entity) {
let inputs = entity.CustomProperties.filter(v => v instanceof PinEntity)
let outputs = inputs.filter(v => v.isOutput())
inputs = inputs.filter(v => !v.isOutput())
return `
<div class="ueb-node-body">
<div class="ueb-node-inputs">
${inputs.map((input, index) => `
<div class="ueb-node-input ueb-node-value-${input.type}">
<span class="ueb-node-value-icon ${inputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
${input.name}
</div>
`).join("") ?? ""}
</div>
<div class="ueb-node-outputs">
${outputs.map((output, index) => `
<div class="ueb-node-output ueb-node-value-${output.type}">
${output.name}
<span class="ueb-node-value-icon ${entity.outputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
</div>
`).join("") ?? ''}
</div>
</div>
`
}
/**
* Computes the html content of the target element.
* @param {HTMLElement} entity Entity representing the element
* @returns The computed html
*/
render(entity) {
return `
<div class="ueb-node-border">
<div class="ueb-node-content">
${this.header(entity)}
${this.body(entity)}
</div>
</div>
`
}
}

50
js/template/Template.js Normal file → Executable file
View File

@@ -1,25 +1,25 @@
/**
* @typedef {import("../graph/GraphNode").default} GraphNode
*/
export default class Template {
/**
* Computes the html content of the target element.
* @param {Entity} entity Entity representing the element
* @returns The computed html
*/
render(entity) {
return ``
}
/**
* Returns the html elements rendered by this template.
* @param {GraphNode} entity Entity representing the element
* @returns The rendered elements
*/
getElements(entity) {
let aDiv = document.createElement('div')
aDiv.innerHTML = this.render(element)
return aDiv.childNodes
}
}
/**
* @typedef {import("../graph/GraphNode").default} GraphNode
*/
export default class Template {
/**
* Computes the html content of the target element.
* @param {Entity} entity Entity representing the element
* @returns The computed html
*/
render(entity) {
return ``
}
/**
* Returns the html elements rendered by this template.
* @param {GraphNode} entity Entity representing the element
* @returns The rendered elements
*/
getElements(entity) {
let aDiv = document.createElement('div')
aDiv.innerHTML = this.render(element)
return aDiv.childNodes
}
}

127
ueblueprint.html Normal file → Executable file
View File

@@ -1,64 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<style>
</style>
<link rel="stylesheet" href="css/ueblueprint-node-value-type-color.css">
<link rel="stylesheet" href="css/ueblueprint-style.css">
</head>
<body>
<div>Hello</div>
<script type="module">
import { PinSerializer } from "./dist/ueblueprint.js"
let path = PinSerializer.grammar.ReferencePath.parse(`/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator`)
let pinEparsed = PinSerializer.grammar.Object.parse(`Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_1"
bIsPureFunc=True
FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr")
NodePosX=1856
NodePosY=1792
NodeGuid=43D899AC42EB640EF98BFCA1597FD6C9
CustomProperties Pin (PinId=FB3490034B2B6127D29E1397E04F6BD6,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=True,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=22257AFF4730E84DE3EF0DBA7A92E1EE,PinName="A",PinToolTip="A\nString\n\nThe original string",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_CallFunction_1 DB96A96142631A1B113BC69C8B77B9BD,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=81E183294B6CBC122C5E88A8C37F13A3,PinName="B",PinToolTip="B\nString\n\nThe string to append to A",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_CallFunction_1 DB96A96142631A1B113BC69C8B77B9BD,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=E9BBB3A54DE64C213F52B2AFC8197637,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nA new string which is the concatenation of A+B",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object`)
//let blueprint = new UEBlueprint()
//let node0 = new GraphNode(); node0.setLocation([985, 393]); let node1 = new GraphNode(); node1.setLocation([999, 114]); let node2 = new GraphNode(); node2.setLocation([811, 253]); let node3 = new GraphNode(); node3.setLocation([802, 146]); let node4 = new GraphNode(); node4.setLocation([597, 105]); let node5 = new GraphNode(); node5.setLocation([789, 233]); let node6 = new GraphNode(); node6.setLocation([549, 289]); let node7 = new GraphNode(); node7.setLocation([678, 193]); let node8 = new GraphNode(); node8.setLocation([1078, 244]); let node9 = new GraphNode(); node9.setLocation([751, 151]); let node10 = new GraphNode(); node10.setLocation([1046, -14]); let node11 = new GraphNode(); node11.setLocation([714, 267]); let node12 = new GraphNode(); node12.setLocation([767, 36]); let node13 = new GraphNode(); node13.setLocation([807, 219]); let node14 = new GraphNode(); node14.setLocation([1031, 70]); let node15 = new GraphNode(); node15.setLocation([906, 389]); let node16 = new GraphNode(); node16.setLocation([936, 131]); let node17 = new GraphNode(); node17.setLocation([689, 249]); let node18 = new GraphNode(); node18.setLocation([1153, 343]); let node19 = new GraphNode(); node19.setLocation([626, 209]); blueprint.addNode(node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10, node11, node12, node13, node14, node15, node16, node17, node18, node19);
//document.querySelector('body').appendChild(blueprint)
</script>
<script type="module">
/*
document.addEventListener("DOMContentLoaded", function (event) {
let leftmost = Number.MAX_SAFE_INTEGER;
let topmost = Number.MAX_SAFE_INTEGER;
let draggableElements = document.querySelectorAll('.ueb-node').forEach(function (node) {
new UEBlueprintDrag(node)
leftmost = Math.min(leftmost, node.offsetLeft)
topmost = Math.min(leftmost, node.offsetTop)
})
document.querySelectorAll('.ueb-grid').forEach(function (grid) {
let obj = new UEBlueprintDragScrollGrid(grid, {
'clickButton': 2,
'exitGrabSameButtonOnly': true,
'expandGridSize': 200
})
obj.stepSize = 1
let viewportWidth = grid.parentElement.clientWidth
let viewportHeight = grid.parentElement.clientHeight
grid.style.setProperty('--ueb-translateX', Math.round(viewportWidth / 2))
grid.style.setProperty('--ueb-translateY', Math.round(viewportHeight / 2))
grid.style.setProperty('--ueb-additionalX', 0)
grid.style.setProperty('--ueb-additionalY', 0)
})
});*/
</script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<style>
</style>
<link rel="stylesheet" href="css/ueblueprint-node-value-type-color.css">
<link rel="stylesheet" href="css/ueblueprint-style.css">
</head>
<body>
<div>Hello</div>
<script type="module">
import { SerializerFactory, ObjectEntity } from "./dist/ueblueprint.js"
let s = SerializerFactory.getSerializer(ObjectEntity)
let object = s.constructor.grammar.Object.parse(`Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_1"
bIsPureFunc=True
FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr")
NodePosX=1856
NodePosY=1792
NodeGuid=43D899AC42EB640EF98BFCA1597FD6C9
CustomProperties Pin (PinId=FB3490034B2B6127D29E1397E04F6BD6,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target Kismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=True,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=22257AFF4730E84DE3EF0DBA7A92E1EE,PinName="A",PinToolTip="A String The original string",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_CallFunction_1 DB96A96142631A1B113BC69C8B77B9BD,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=81E183294B6CBC122C5E88A8C37F13A3,PinName="B",PinToolTip="B String The string to append to A",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_CallFunction_1 DB96A96142631A1B113BC69C8B77B9BD,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=E9BBB3A54DE64C213F52B2AFC8197637,PinName="ReturnValue",PinToolTip="Return Value String A new string which is the concatenation of A+B",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object`)
console.log(s.write(object.value))
//let blueprint = new UEBlueprint()
//let node0 = new GraphNode(); node0.setLocation([985, 393]); let node1 = new GraphNode(); node1.setLocation([999, 114]); let node2 = new GraphNode(); node2.setLocation([811, 253]); let node3 = new GraphNode(); node3.setLocation([802, 146]); let node4 = new GraphNode(); node4.setLocation([597, 105]); let node5 = new GraphNode(); node5.setLocation([789, 233]); let node6 = new GraphNode(); node6.setLocation([549, 289]); let node7 = new GraphNode(); node7.setLocation([678, 193]); let node8 = new GraphNode(); node8.setLocation([1078, 244]); let node9 = new GraphNode(); node9.setLocation([751, 151]); let node10 = new GraphNode(); node10.setLocation([1046, -14]); let node11 = new GraphNode(); node11.setLocation([714, 267]); let node12 = new GraphNode(); node12.setLocation([767, 36]); let node13 = new GraphNode(); node13.setLocation([807, 219]); let node14 = new GraphNode(); node14.setLocation([1031, 70]); let node15 = new GraphNode(); node15.setLocation([906, 389]); let node16 = new GraphNode(); node16.setLocation([936, 131]); let node17 = new GraphNode(); node17.setLocation([689, 249]); let node18 = new GraphNode(); node18.setLocation([1153, 343]); let node19 = new GraphNode(); node19.setLocation([626, 209]); blueprint.addNode(node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10, node11, node12, node13, node14, node15, node16, node17, node18, node19);
//document.querySelector('body').appendChild(blueprint)
</script>
<script type="module">
/*
document.addEventListener("DOMContentLoaded", function (event) {
let leftmost = Number.MAX_SAFE_INTEGER;
let topmost = Number.MAX_SAFE_INTEGER;
let draggableElements = document.querySelectorAll('.ueb-node').forEach(function (node) {
new UEBlueprintDrag(node)
leftmost = Math.min(leftmost, node.offsetLeft)
topmost = Math.min(leftmost, node.offsetTop)
})
document.querySelectorAll('.ueb-grid').forEach(function (grid) {
let obj = new UEBlueprintDragScrollGrid(grid, {
'clickButton': 2,
'exitGrabSameButtonOnly': true,
'expandGridSize': 200
})
obj.stepSize = 1
let viewportWidth = grid.parentElement.clientWidth
let viewportHeight = grid.parentElement.clientHeight
grid.style.setProperty('--ueb-translateX', Math.round(viewportWidth / 2))
grid.style.setProperty('--ueb-translateY', Math.round(viewportHeight / 2))
grid.style.setProperty('--ueb-additionalX', 0)
grid.style.setProperty('--ueb-additionalY', 0)
})
});*/
</script>
</body>
</html>