mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-04 08:50:33 +08:00
Various fixes
This commit is contained in:
1262
dist/ueblueprint.js
vendored
1262
dist/ueblueprint.js
vendored
File diff suppressed because it is too large
Load Diff
194
js/Utility.js
Normal file → Executable file
194
js/Utility.js
Normal file → Executable 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
108
js/entity/Entity.js
Normal file → Executable 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
26
js/entity/FunctionReferenceEntity.js
Normal file → Executable 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
54
js/entity/ObjectEntity.js
Normal file → Executable 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
98
js/entity/PinEntity.js
Normal file → Executable 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
28
js/entity/PinReferenceEntity.js
Normal file → Executable 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
38
js/entity/TypeInitialization.js
Normal file → Executable 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
30
js/entity/VariableReferenceEntity.js
Normal file → Executable 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
64
js/entity/primitive/Guid.js
Normal file → Executable 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
50
js/entity/primitive/Integer.js
Normal file → Executable 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
44
js/entity/primitive/LocalizedTextEntity.js
Normal file → Executable 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
46
js/entity/primitive/ObjectReference.js
Normal file → Executable 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
10
js/entity/primitive/Primitive.js
Normal file → Executable 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
15
js/export.js
Normal file → Executable 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
44
js/graph/GraphEntity.js
Normal file → Executable 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
58
js/graph/GraphLink.js
Normal file → Executable 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
50
js/graph/GraphNode.js
Normal file → Executable 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
108
js/graph/GraphSelector.js
Normal file → Executable 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
140
js/graph/SelectableDraggable.js
Normal file → Executable 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
82
js/input/Drag.js
Normal file → Executable 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
18
js/input/DragScroll.js
Normal file → Executable 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
200
js/input/MouseClickDrag.js
Normal file → Executable 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
62
js/input/MouseWheel.js
Normal file → Executable 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
44
js/input/Pointing.js
Normal file → Executable 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
54
js/input/Select.js
Normal file → Executable 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
18
js/input/Zoom.js
Normal file → Executable 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
328
js/selection/FastSelectionModel.js
Normal file → Executable 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
306
js/selection/OrderedIndexArray.js
Normal file → Executable 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
88
js/selection/SimpleSelectionModel.js
Normal file → Executable 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
25
js/serialization/GeneralSerializer.js
Executable file
25
js/serialization/GeneralSerializer.js
Executable 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
354
js/serialization/Grammar.js
Normal file → Executable 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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
46
js/serialization/ObjectSerializer.js
Executable file
46
js/serialization/ObjectSerializer.js
Executable 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
|
||||
}
|
||||
}
|
||||
@@ -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
154
js/serialization/Serializer.js
Normal file → Executable 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
32
js/serialization/SerializerFactory.js
Normal file → Executable 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
92
js/template/BlueprintTemplate.js
Normal file → Executable 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
136
js/template/NodeTemplate.js
Normal file → Executable 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
50
js/template/Template.js
Normal file → Executable 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
127
ueblueprint.html
Normal file → Executable 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>
|
||||
Reference in New Issue
Block a user