Files
ueblueprint/js/element/LinkElement.js
2022-11-21 11:45:43 +01:00

264 lines
8.0 KiB
JavaScript

import { html, nothing } from "lit"
import Configuration from "../Configuration"
import IFromToPositionedElement from "./IFromToPositionedElement"
import LinkTemplate from "../template/LinkTemplate"
import SVGIcon from "../SVGIcon"
import Utility from "../Utility"
/**
* @typedef {import("./PinElement").default} PinElement
* @typedef {import("lit").TemplateResult<1>} TemplateResult
*/
/** @extends {IFromToPositionedElement<Object, LinkTemplate>} */
export default class LinkElement extends IFromToPositionedElement {
static properties = {
...super.properties,
source: {
type: String,
reflect: true,
},
destination: {
type: String,
reflect: true,
},
dragging: {
type: Boolean,
attribute: "data-dragging",
converter: Utility.booleanConverter,
reflect: true,
},
originatesFromInput: {
type: Boolean,
attribute: false,
},
svgPathD: {
type: String,
attribute: false,
},
linkMessageIcon: {
type: String,
attribute: false,
},
linkMessageText: {
type: String,
attribute: false,
},
}
/** @type {PinElement} */
#sourcePin
get sourcePin() {
return this.#sourcePin
}
set sourcePin(pin) {
this.#setPin(pin, false)
}
/** @type {PinElement} */
#destinationPin
get destinationPin() {
return this.#destinationPin
}
set destinationPin(pin) {
this.#setPin(pin, true)
}
#nodeDeleteHandler
#nodeDragSourceHandler
#nodeDragDestinatonHandler
#nodeReflowSourceHandler
#nodeReflowDestinatonHandler
/** @type {TemplateResult | nothing} */
linkMessageIcon = nothing
/** @type {TemplateResult | nothing} */
linkMessageText = nothing
/** @type {SVGPathElement} */
pathElement
/**
* @param {PinElement} source
* @param {PinElement?} destination
*/
constructor(source, destination) {
super({}, new LinkTemplate())
const self = this
this.#nodeDeleteHandler = () => self.remove()
this.#nodeDragSourceHandler = e => self.addSourceLocation(e.detail.value)
this.#nodeDragDestinatonHandler = e => self.addDestinationLocation(e.detail.value)
this.#nodeReflowSourceHandler = e => self.setSourceLocation()
this.#nodeReflowDestinatonHandler = e => self.setDestinationLocation()
this.source = null
this.destination = null
this.dragging = false
this.originatesFromInput = false
this.startPercentage = 0
this.svgPathD = ""
this.startPixels = 0
if (source) {
this.sourcePin = source
if (!destination) {
this.toX = this.fromX
this.toY = this.fromY
}
}
if (destination) {
this.destinationPin = destination
if (!source) {
this.fromX = this.toX
this.fromY = this.toY
}
}
this.#linkPins()
}
/**
* @param {PinElement} pin
* @param {Boolean} isDestinationPin
*/
#setPin(pin, isDestinationPin) {
const getCurrentPin = () => isDestinationPin ? this.destinationPin : this.sourcePin
if (getCurrentPin() == pin) {
return
}
if (getCurrentPin()) {
const nodeElement = getCurrentPin().getNodeElement()
nodeElement.removeEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
nodeElement.removeEventListener(
Configuration.nodeDragEventName,
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
)
nodeElement.removeEventListener(
Configuration.nodeReflowEventName,
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
)
this.#unlinkPins()
}
isDestinationPin
? this.#destinationPin = pin
: this.#sourcePin = pin
if (getCurrentPin()) {
const nodeElement = getCurrentPin().getNodeElement()
nodeElement.addEventListener(Configuration.nodeDeleteEventName, this.#nodeDeleteHandler)
nodeElement.addEventListener(
Configuration.nodeDragEventName,
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
)
nodeElement.addEventListener(
Configuration.nodeReflowEventName,
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
)
isDestinationPin
? this.setDestinationLocation()
: (this.setSourceLocation(), this.originatesFromInput = this.sourcePin.isInput())
this.#linkPins()
}
}
#linkPins() {
if (this.sourcePin && this.destinationPin) {
this.sourcePin.linkTo(this.destinationPin)
this.destinationPin.linkTo(this.sourcePin)
}
}
#unlinkPins() {
if (this.sourcePin && this.destinationPin) {
this.sourcePin.unlinkFrom(this.destinationPin)
this.destinationPin.unlinkFrom(this.sourcePin)
}
}
disconnectedCallback() {
super.disconnectedCallback()
this.#unlinkPins()
this.sourcePin = null
this.destinationPin = null
}
/** @param {Number[]?} location */
setSourceLocation(location = null) {
if (location == null) {
const self = this
if (!this.hasUpdated || !this.sourcePin.hasUpdated) {
Promise.all([this.updateComplete, this.sourcePin.updateComplete]).then(() => self.setSourceLocation())
return
}
location = this.sourcePin.template.getLinkLocation()
}
const [x, y] = location
this.fromX = x
this.fromY = y
}
/** @param {Number[]?} location */
setDestinationLocation(location = null) {
if (location == null) {
const self = this
if (!this.hasUpdated || !this.destinationPin.hasUpdated) {
Promise.all([this.updateComplete, this.destinationPin.updateComplete]).then(() => self.setDestinationLocation())
return
}
location = this.destinationPin.template.getLinkLocation()
}
this.toX = location[0]
this.toY = location[1]
}
startDragging() {
this.dragging = true
}
finishDragging() {
this.dragging = false
}
removeMessage() {
this.linkMessageIcon = ""
this.linkMessageText = ""
}
setMessageConvertType() {
this.linkMessageIcon = "ueb-icon-conver-type"
this.linkMessageText = `Convert ${this.sourcePin.pinType} to ${this.destinationPin.pinType}.`
}
setMessageCorrect() {
this.linkMessageIcon = SVGIcon.correct
this.linkMessageText = nothing
}
setMessageReplace() {
this.linkMessageIcon = SVGIcon.correct
this.linkMessageText = nothing
}
setMessageDirectionsIncompatible() {
this.linkMessageIcon = SVGIcon.reject
this.linkMessageText = html`Directions are not compatbile.`
}
setMessagePlaceNode() {
this.linkMessageIcon = "ueb-icon-place-node"
this.linkMessageText = html`Place a new node.`
}
setMessageReplaceLink() {
this.linkMessageIcon = SVGIcon.correct
this.linkMessageText = html`Replace existing input connections.`
}
setMessageSameNode() {
this.linkMessageIcon = SVGIcon.reject
this.linkMessageText = html`Both are on the same node.`
}
setMEssagetypesIncompatible() {
this.linkMessageIcon = SVGIcon.reject
this.linkMessageText = html`${this.sourcePin.pinType} is not compatible with ${this.destinationPin.pinType}.`
}
}