Files
ueblueprint/js/element/PinElement.js
BarsDev 6ba2705386 Large refactoring and new nodes
* Fix node reference when changing elements

* Fix ScriptVariables parsing

* Fix invariant text and niagara types

* Niagara convert nodes

* Move node tests to own files

* More Niagara tests

* Niagara float and smaller fixes

* More Decoding

* More decoding

* WIP

* Float is real

* WIP

* More types and colors

* Test case and small polish

* WIP

* WIP

* Fix niagara script variables merging

* Fix Niagara variables

* Fixing mirrored ExportPath

* Fix Export paths name adjustments

* Simplify arc calculation

* Simplify a bit arc calculation

* source / destionation => origin / target

* Minor refactoring

* Fix switched link position

* Rename some properties for uniformity

* Fix input escape

* Simplify test

* About window

* Dialog backdrop style

* About dialog touches

* Remove dependency and minot improvement

* Light mode

* Fix link location and css small improvement

* Link direction and minor fixes

* Some minor fixes and refactoring

* Refactoring WIP

* Shorting repetitive bits

* More tests

* Simplify linking tests
2025-02-07 00:36:03 +02:00

305 lines
9.9 KiB
JavaScript
Executable File

import Configuration from "../Configuration.js"
import pinTemplate from "../decoding/pinTemplate.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import PinTemplate from "../template/pin/PinTemplate.js"
import ElementFactory from "./ElementFactory.js"
import IElement from "./IElement.js"
/**
* @template {IEntity} T
* @extends {IElement<PinEntity<T>, PinTemplate>}
*/
export default class PinElement extends IElement {
static properties = {
pinId: {
type: GuidEntity,
converter: {
fromAttribute: (value, type) => value
? GuidEntity.grammar.parse(value)
: null,
toAttribute: (value, type) => value?.toString(),
},
attribute: "data-id",
reflect: true,
},
pinType: {
type: String,
attribute: "data-type",
reflect: true,
},
advancedView: {
type: String,
attribute: "data-advanced-view",
reflect: true,
},
color: {
type: LinearColorEntity,
converter: {
fromAttribute: (value, type) => value
? LinearColorEntity.getLinearColorFromAnyFormat().parse(value)
: null,
/** @param {LinearColorEntity} value */
toAttribute: (value, type) => value?.toString() ?? "",
},
attribute: "data-color",
reflect: true,
},
defaultValue: {
type: String,
attribute: false,
},
isLinked: {
type: Boolean,
converter: BooleanEntity.booleanConverter,
attribute: "data-linked",
reflect: true,
},
pinDirection: {
type: String,
attribute: "data-direction",
reflect: true,
},
connectable: {
type: Boolean,
converter: BooleanEntity.booleanConverter,
attribute: "data-connectable",
reflect: true,
}
}
/** @type {NodeElement} */
nodeElement
static newObject(
entity = new PinEntity(),
template = /** @type {PinTemplate} */(new (pinTemplate(entity))()),
nodeElement = undefined
) {
const result = new PinElement()
result.initialize(entity, template, nodeElement)
return result
}
initialize(
entity = /** @type {PinEntity<T>} */(new PinEntity()),
template = /** @type {PinTemplate} */(new (pinTemplate(entity))()),
nodeElement = undefined
) {
this.nodeElement = nodeElement
this.advancedView = entity.bAdvancedView?.valueOf()
this.isLinked = false
this.connectable = !entity.bNotConnectable?.valueOf()
super.initialize(entity, template)
this.pinId = this.entity.PinId
this.updateType()
this.defaultValue = this.entity.getDefaultValue()
this.pinDirection = entity.isInput() ? "input" : entity.isOutput() ? "output" : "hidden"
/** @type {LinearColorEntity} */
this.color = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
}
setup() {
super.setup()
this.nodeElement = this.closest("ueb-node")
}
updateType() {
this.pinType = this.entity.getType()
const newColor = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
if (!this.color?.equals(newColor)) {
this.color = newColor
this.acknowledgeUpdate()
}
}
createPinReference() {
return new PinReferenceEntity(new SymbolEntity(this.nodeElement.getNodeName()), this.getPinId())
}
getPinId() {
return this.entity.PinId
}
getPinName() {
return this.entity.PinName?.toString() ?? ""
}
getPinDisplayName() {
return this.entity.pinTitle()
}
/** @param {PinElement} pin */
#traverseKnots(pin) {
while (pin?.isKnot()) {
const pins = pin.nodeElement.getPinElements()
pin = pin === pins[0] ? pins[1] : pins[0]
pin = pin.isLinked ? this.blueprint.getPin(pin.getLinks()[0]) : null
}
return pin?.isKnot() ? undefined : pin
}
isInput(ignoreKnots = false) {
/** @type {PinElement} */
let result = this
if (ignoreKnots) {
return this.#traverseKnots(result)?.isInput()
}
return result.entity.isInput()
}
/** @returns {boolean} True when the pin is the input part of a knot that can switch direction */
isInputLoosely() {
return this.isInput(false) && this.isInput(true) === undefined
}
/** @returns {boolean} True when the pin is input and if it is a knot it appears input */
isInputVisually() {
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
const isKnot = this.isKnot()
return isKnot && this.isInput() != template.switchDirectionsVisually
|| !isKnot && this.isInput()
}
isOutput(ignoreKnots = false) {
/** @type {PinElement} */
let result = this
if (ignoreKnots) {
return this.#traverseKnots(result)?.isOutput()
}
return result.entity.isOutput()
}
/** @returns {boolean} True when the pin is the output part of a knot that can switch direction */
isOutputLoosely() {
return this.isOutput(false) && this.isOutput(true) === undefined
}
/** @returns {boolean} True when the pin is output and if it is a knot it appears output */
isOutputVisually() {
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
const isKnot = this.isKnot()
return isKnot && this.isOutput() != template.switchDirectionsVisually
|| !isKnot && this.isOutput()
}
/** @returns {value is InstanceType<PinElement<>>} */
isKnot() {
return this.nodeElement?.getType() == Configuration.paths.knot
}
getLinkLocation(oppositeDirection = false) {
return this.template.getLinkLocation(oppositeDirection)
}
getNodeElement() {
return this.nodeElement
}
getLinks() {
return this.entity.LinkedTo?.valueOf() ?? []
}
getDefaultValue(maybeCreate = false) {
return this.defaultValue = this.entity.getDefaultValue(maybeCreate)
}
/** @param {T} value */
setDefaultValue(value) {
this.entity.DefaultValue = value
this.defaultValue = value
if (this.entity.recomputesNodeTitleOnChange) {
this.nodeElement?.computeNodeDisplayName()
}
}
/** @param {IElement[]} nodesWhitelist */
sanitizeLinks(nodesWhitelist = []) {
this.entity.LinkedTo = new (PinEntity.attributes.LinkedTo)(
this.entity.LinkedTo?.valueOf().filter(pinReference => {
let pin = this.blueprint.getPin(pinReference)
if (pin) {
if (nodesWhitelist.length && !nodesWhitelist.includes(pin.nodeElement)) {
return false
}
let link = this.blueprint.getLink(this, pin)
if (!link) {
link = /** @type {LinkElementConstructor} */(ElementFactory.getConstructor("ueb-link"))
.newObject(this, pin)
this.blueprint.addGraphElement(link)
}
}
return pin
})
)
this.isLinked = this.entity.isLinked()
}
/** @param {PinElement} targetPinElement */
linkTo(targetPinElement) {
const pinReference = this.createPinReference()
if (
this.isLinked
&& this.entity.isExecution()
&& this.isOutput(true)
&& this.getLinks().some(ref => !pinReference.equals(ref))
) {
if (this.isKnot()) {
}
this.unlinkFromAll()
}
if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
this.isLinked = this.entity.isLinked()
if (this.entity.recomputesNodeTitleOnChange) {
this.nodeElement?.computeNodeDisplayName()
}
}
}
/** @param {PinElement} targetPinElement */
unlinkFrom(targetPinElement, removeLink = true) {
if (this.entity.unlinkFrom(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
this.isLinked = this.entity.isLinked()
if (removeLink) {
this.blueprint.getLink(this, targetPinElement)?.remove() // Might be called after the link is removed
}
if (this.entity.recomputesNodeTitleOnChange) {
this.nodeElement?.computeNodeDisplayName()
}
}
}
unlinkFromAll() {
this.getLinks().map(ref => this.blueprint.getPin(ref)).forEach(pin => this.unlinkFrom(pin))
const isLinked = false
}
/**
* @param {PinElement} originalPinElement
* @param {PinReferenceEntity} newReference
*/
redirectLink(originalPinElement, newReference) {
const index = this.getLinks().findIndex(pinReference =>
pinReference.objectName.toString() == originalPinElement.getNodeElement().getNodeName()
&& pinReference.pinGuid.toString() == originalPinElement.entity.PinId.toString()
)
if (index >= 0) {
this.entity.LinkedTo.valueOf()[index] = newReference
return true
}
return false
}
acknowledgeUpdate() {
let event = new CustomEvent(Configuration.pinUpdateEventName)
this.dispatchEvent(event)
}
}