mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-03-06 15:47:30 +08:00
Initial commit
This commit is contained in:
35
js/UEBlueprintDOMModel.js
Normal file
35
js/UEBlueprintDOMModel.js
Normal file
@@ -0,0 +1,35 @@
|
||||
export default class UEBlueprintDOMModel {
|
||||
static dummyDiv = document.createElement('div')
|
||||
|
||||
static domTemplate(obj) {
|
||||
return ``
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.domElement = null
|
||||
}
|
||||
|
||||
createDOMElement() {
|
||||
if (this.domElement) {
|
||||
this.removeDOMElement()
|
||||
}
|
||||
this.constructor.dummyDiv.innerHTML = this.constructor.domTemplate(this)
|
||||
this.domElement = this.constructor.dummyDiv.removeChild(this.constructor.dummyDiv.firstElementChild)
|
||||
}
|
||||
|
||||
getDOMElement() {
|
||||
if (!this.domElement) {
|
||||
this.createDOMElement()
|
||||
}
|
||||
return this.domElement
|
||||
}
|
||||
|
||||
removeDOMElement() {
|
||||
if (!this.domElement) {
|
||||
return false
|
||||
}
|
||||
this.domElement.parentElement.removeChild(this.domElement)
|
||||
this.domElement = null
|
||||
return true
|
||||
}
|
||||
}
|
||||
69
js/UEBlueprintDrag.js
Normal file
69
js/UEBlueprintDrag.js
Normal file
@@ -0,0 +1,69 @@
|
||||
export default class UEBlueprintDrag {
|
||||
constructor(draggedNode, options) {
|
||||
this.blueprintNode = draggedNode;
|
||||
this.mousePosition = [0, 0];
|
||||
this.stepSize = 1
|
||||
this.clickButton = options?.clickButton ?? 0
|
||||
this.exitGrabSameButtonOnly = options?.exitGrabSameButtonOnly ?? false
|
||||
let self = this;
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
self.clicked(e.clientX, e.clientY)
|
||||
break;
|
||||
default:
|
||||
if (!self.exitGrabSameButtonOnly) {
|
||||
self.mouseUpHandler(e)
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
this.mouseMoveHandler = function (e) {
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY)
|
||||
const d = [mousePosition[0] - self.mousePosition[0], mousePosition[1] - self.mousePosition[1]]
|
||||
|
||||
if (d[0] == 0 && d[1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.blueprintNode.addLocation(d)
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition
|
||||
};
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitGrabSameButtonOnly || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
document.removeEventListener('mousemove', self.mouseMoveHandler)
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler)
|
||||
}
|
||||
};
|
||||
let element = this.blueprintNode.getDOMElement()
|
||||
element.addEventListener('mousedown', this.mouseDownHandler)
|
||||
element.addEventListener('contextmenu', e => e.preventDefault())
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
if (this.blueprintNode) {
|
||||
this.blueprintNode.getDOMElement().removeEventListener('mousedown', this.mouseDownHandler)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
snapToGrid(posX, posY) {
|
||||
return [
|
||||
this.stepSize * Math.round(posX / this.stepSize),
|
||||
this.stepSize * Math.round(posY / this.stepSize)
|
||||
]
|
||||
}
|
||||
|
||||
clicked(x, y) {
|
||||
// Get the current mouse position
|
||||
this.mousePosition = this.snapToGrid(x, y)
|
||||
this.stepSize = parseInt(getComputedStyle(this.blueprintNode.getDOMElement()).getPropertyValue('--ueb-grid-snap'))
|
||||
// Attach the listeners to `document`
|
||||
document.addEventListener('mousemove', this.mouseMoveHandler)
|
||||
document.addEventListener('mouseup', this.mouseUpHandler)
|
||||
}
|
||||
}
|
||||
78
js/UEBlueprintDragScroll.js
Normal file
78
js/UEBlueprintDragScroll.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import UEBlueprintDrag from "./UEBlueprintDrag.js"
|
||||
|
||||
export default class UEBlueprintDragScroll extends UEBlueprintDrag {
|
||||
constructor(scrolledEntity, options) {
|
||||
super(scrolledEntity, options)
|
||||
this.scrolledDOMElement = scrolledEntity.getGridDOMElement()
|
||||
this.expandGridSize = options?.expandGridSize ?? 200
|
||||
this.initialExpandGridSize = this.expandGridSize
|
||||
this.minZoom = options?.minZoom ?? -12
|
||||
let self = this;
|
||||
this.mouseMoveHandler = function (e) {
|
||||
const scrollMaxX = self.scrolledDOMElement.parentElement.scrollWidth - self.scrolledDOMElement.parentElement.clientWidth
|
||||
const scrollMaxY = self.scrolledDOMElement.parentElement.scrollHeight - self.scrolledDOMElement.parentElement.clientHeight
|
||||
let expandX = self.scrolledDOMElement.parentElement.scrollLeft < self.expandGridSize * 0.5 ? -1 : 0
|
||||
+ self.scrolledDOMElement.parentElement.scrollLeft > scrollMaxX - self.expandGridSize * 0.5 ? 1 : 0
|
||||
let expandY = self.scrolledDOMElement.parentElement.scrollTop < self.expandGridSize * 0.5 ? -1 : 0
|
||||
+ self.scrolledDOMElement.parentElement.scrollTop > scrollMaxY - self.expandGridSize * 0.5 ? 1 : 0
|
||||
|
||||
if (expandX != 0 || expandY != 0) {
|
||||
|
||||
/* Managining infinite scrolling: when the scrollbar reaches the end, the grid is expanded and the elements inside translated to give the illusion that they stayed in the same position*/
|
||||
self.expandAndTranslate(expandX * self.expandGridSize, expandY * self.expandGridSize)
|
||||
}
|
||||
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY)
|
||||
|
||||
// How far the mouse has been moved
|
||||
const dx = mousePosition[0] - self.mousePosition[0]
|
||||
const dy = mousePosition[1] - self.mousePosition[1]
|
||||
|
||||
self.scrolledDOMElement.parentElement.scrollLeft = self.scrolledDOMElement.parentElement.scrollLeft - dx
|
||||
self.scrolledDOMElement.parentElement.scrollTop = self.scrolledDOMElement.parentElement.scrollTop - dy
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition
|
||||
};
|
||||
this.mouseWheelHandler = function (e) {
|
||||
let blueprintRoot = self.elem.parentElement.parentElement
|
||||
let zoomLevel = 0
|
||||
let zoomLevelClass = "ueb-zoom-0"
|
||||
let classes = blueprintRoot.classList.values()
|
||||
for (let className of classes) {
|
||||
let v = className.match(/ueb\-zoom\-(\-?\d+)/)
|
||||
if (v) {
|
||||
zoomLevelClass = v[0]
|
||||
zoomLevel = parseInt(v[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
zoomLevel -= Math.round(e.deltaY / 50)
|
||||
zoomLevel = self.clamp(zoomLevel, -12, 0)
|
||||
blueprintRoot.classList.remove(zoomLevelClass)
|
||||
blueprintRoot.classList.add("ueb-zoom-" + zoomLevel)
|
||||
let scale = blueprintNode.getScale()
|
||||
let additionalX = Math.ceil(self.scrolledDOMElement.clientWidth * (1 - 1 / scale))
|
||||
let additionalY = Math.ceil(self.scrolledDOMElement.clientHeight * (1 - 1 / scale))
|
||||
self.blueprintNode.expand(additionalX, additionalY)
|
||||
|
||||
}
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('wheel', this.mouseWheelHandler)
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('wheel', e => e.preventDefault())
|
||||
this.blueprintNode.getGridDOMElement().parentElement.addEventListener('wheel', e => e.preventDefault())
|
||||
}
|
||||
|
||||
expandAndTranslate(x, y) {
|
||||
this.blueprintNode.expand(x, y)
|
||||
this.blueprintNode.translate(-x, -y)
|
||||
}
|
||||
|
||||
scaledExpand(x, y, scale) {
|
||||
|
||||
}
|
||||
|
||||
clamp(val, min, max) {
|
||||
return Math.min(Math.max(val, min), max);
|
||||
}
|
||||
|
||||
}
|
||||
40
js/UEBlueprintDraggableObject.js
Normal file
40
js/UEBlueprintDraggableObject.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import UEBlueprintDOMModel from "./UEBlueprintDOMModel.js"
|
||||
import UEBlueprintDrag from "./UEBlueprintDrag.js"
|
||||
|
||||
export default class UEBlueprintDraggableObject extends UEBlueprintDOMModel {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.dragObject = null
|
||||
this.location = [0, 0]
|
||||
}
|
||||
|
||||
createDOMElement() {
|
||||
super.createDOMElement()
|
||||
this.dragObject = new UEBlueprintDrag(this)
|
||||
}
|
||||
|
||||
removeDOMElement() {
|
||||
if (this.domElement) {
|
||||
this.dragObject.unlistenDOMElement()
|
||||
}
|
||||
return super.removeDOMElement()
|
||||
}
|
||||
|
||||
setLocation(value = [0, 0]) {
|
||||
this.location = value
|
||||
if (this.domElement) {
|
||||
this.domElement.style.setProperty('--ueb-position-x', this.location[0])
|
||||
this.domElement.style.setProperty('--ueb-position-y', this.location[1])
|
||||
}
|
||||
}
|
||||
|
||||
addLocation(value) {
|
||||
this.setLocation([this.location[0] + value[0], this.location[1] + value[1]])
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return this.location
|
||||
}
|
||||
|
||||
}
|
||||
77
js/UEBlueprintObject.js
Normal file
77
js/UEBlueprintObject.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import UEBlueprintDraggableObject from "./UEBlueprintDraggableObject.js"
|
||||
|
||||
export default class UEBlueprintObject extends UEBlueprintDraggableObject {
|
||||
static classInputs = [/*
|
||||
{
|
||||
name: "Input Example",
|
||||
type: 'integer'
|
||||
}
|
||||
*/]
|
||||
static classOutputs = [/*
|
||||
{
|
||||
name: "Return Value",
|
||||
type: 'string'
|
||||
}*/
|
||||
]
|
||||
static classInFlow = false
|
||||
static classOutFlow = false
|
||||
static className = 'Empty node'
|
||||
static domTemplate(obj) {
|
||||
return `
|
||||
<div class="ueb-node ${obj.selected ? 'ueb-selected' : ''}"
|
||||
style="--ueb-position-x:${obj.location[0]}; --ueb-position-y:${obj.location[1]}">
|
||||
<div class="ueb-node-border">
|
||||
<div class="ueb-node-content">
|
||||
<div class="ueb-node-header">
|
||||
<span class="ueb-node-name">
|
||||
<span class="ueb-node-symbol"></span>
|
||||
<span class="ueb-node-text">${obj.constructor.className}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ueb-node-body">
|
||||
<div class="ueb-node-inputs">
|
||||
${obj.constructor.classInputs.forEach((input, index) => `
|
||||
<div class="ueb-node-input ueb-node-value-${input.type}">
|
||||
<span class="ueb-node-value-icon ${obj.inputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
|
||||
${input.name}
|
||||
</div>
|
||||
`) ?? ''}
|
||||
</div>
|
||||
<div class="ueb-node-outputs">
|
||||
${obj.constructor.classOutputs.forEach((output, index) => `
|
||||
<div class="ueb-node-output ueb-node-value-${output.type}">
|
||||
${output.name}
|
||||
<span class="ueb-node-value-icon ${obj.outputs[index].connected ? 'ueb-node-value-fill' : ''}"></span>
|
||||
</div>
|
||||
`) ?? ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selected = false
|
||||
this.inputs = this.constructor.classInputs.map(value => {
|
||||
return {
|
||||
connected: null
|
||||
}
|
||||
})
|
||||
this.outputs = this.constructor.classOutputs.map(value => {
|
||||
return {
|
||||
connected: null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
isSelected() {
|
||||
return this.selected
|
||||
}
|
||||
|
||||
setSelected(value = true) {
|
||||
this.selected = value
|
||||
}
|
||||
}
|
||||
103
js/ueblueprint.js
Normal file
103
js/ueblueprint.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import UEBlueprintDOMModel from "./UEBlueprintDOMModel.js";
|
||||
import UEBlueprintDragScroll from "./UEBlueprintDragScroll.js";
|
||||
|
||||
export default class UEBlueprint extends UEBlueprintDOMModel {
|
||||
|
||||
static domTemplate(obj) {
|
||||
return `
|
||||
<div class="ueb" style="--ueb-grid-scale:${obj.scale}">
|
||||
<div class="ueb-viewport-header">
|
||||
<div class="ueb-viewport-zoom">1:1</div>
|
||||
</div>
|
||||
<div class="ueb-viewport-overlay"></div>
|
||||
<div class="ueb-viewport-body">
|
||||
<div class="ueb-grid"
|
||||
style="--ueb-additional-x:${obj.additional[0]}; --ueb-additional-y:${obj.additional[1]}; --ueb-translate-x:${obj.translate[0]}; --ueb-translate-y:${obj.translate[1]}">
|
||||
<div class="ueb-grid-content">
|
||||
${obj.nodes.forEach(node => node.getDOMElement()) ?? ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.gridDOMElement = null
|
||||
this.dragObject = null
|
||||
this.additional = [0, 0]
|
||||
this.translateValue = [0, 0]
|
||||
this.scale = 1
|
||||
this.nodes = []
|
||||
}
|
||||
|
||||
createDOMElement() {
|
||||
super.createDOMElement()
|
||||
this.gridDOMElement = this.domElement.querySelector('.ueb-grid')
|
||||
let contentElement = this.domElement.querySelector('.ueb-grid-content')
|
||||
if (!this.gridDOMElement || !contentElement) {
|
||||
console.error('Some expencted DOM elements not be found, please check domTemplate().')
|
||||
}
|
||||
// Populate the grid content with the node elements
|
||||
this.nodes.forEach(node => {
|
||||
contentElement.appendChild(node.getDOMElement())
|
||||
})
|
||||
this.dragObject = new UEBlueprintDragScroll(this, {
|
||||
'clickButton': 2
|
||||
})
|
||||
}
|
||||
|
||||
removeDOMElement() {
|
||||
if (this.domElement) {
|
||||
this.dragObject.unlistenDOMElement()
|
||||
}
|
||||
return super.removeDOMElement()
|
||||
}
|
||||
|
||||
getGridDOMElement() {
|
||||
return this.gridDOMElement
|
||||
}
|
||||
|
||||
setScroll(value = [0, 0]) {
|
||||
this.scroll = value
|
||||
}
|
||||
|
||||
addScroll(value) {
|
||||
this.setLocation([this.scroll[0] + value[0], this.scroll[1] + value[1]])
|
||||
}
|
||||
|
||||
getScroll() {
|
||||
return this.scroll
|
||||
}
|
||||
|
||||
expand(x, y) {
|
||||
x = Math.round(x)
|
||||
y = Math.round(y)
|
||||
this.additional = [this.additional[0] + Math.abs(x), this.additional[1] + Math.abs(y)]
|
||||
if (this.domElement) {
|
||||
this.domElement.style.setProperty('--ueb-additional-x', this.additional[0])
|
||||
this.domElement.style.setProperty('--ueb-additional-y', this.additional[1])
|
||||
this.domElement.parentElement.scrollLeft -= x
|
||||
this.domElement.parentElement.scrollTop -= y
|
||||
}
|
||||
}
|
||||
|
||||
translate(x, y) {
|
||||
x = Math.round(x)
|
||||
y = Math.round(y)
|
||||
this.translateValue = [this.translateValue[0] + x, this.translateValue[1] + y]
|
||||
if (this.domElement) {
|
||||
this.domElement.style.setProperty('--ueb-translate-x', this.translateValue[0])
|
||||
this.domElement.style.setProperty('--ueb-translate-y', this.translateValue[1])
|
||||
}
|
||||
}
|
||||
|
||||
getScale() {
|
||||
return this.scale
|
||||
}
|
||||
|
||||
addNode(...blueprintNode) {
|
||||
this.nodes.push(...blueprintNode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user