feature dump, i don't wanna write individual commits for this. not yet.

This commit is contained in:
Luca Conte 2025-02-17 03:14:34 +01:00
parent 3faca12367
commit 750676cb23
8 changed files with 322 additions and 36 deletions

52
Box.js
View File

@ -8,6 +8,7 @@ class Box {
y; y;
element; element;
content;
static instances = []; static instances = [];
@ -23,11 +24,22 @@ class Box {
this.title = title; this.title = title;
this.x = Camera.x + 100; this.x = Camera.x + 100;
this.y = Camera.y + 200; this.y = Camera.y + 200;
this.buildElement();
this.updatePosition(); let closestDistance;
do {
closestDistance = 999999;
this.x += 50;
this.y += 50;
for (let box of Box.instances) {
if (box == this) continue;
closestDistance = Math.min(Math.abs(box.x - this.x), Math.abs(box.y - this.y), closestDistance);
}
} while (closestDistance < 50);
} }
buildElement() { buildElement() {
if (this.element) return this.element;
this.element = document.createElement("div"); this.element = document.createElement("div");
this.element.classList.add("box"); this.element.classList.add("box");
@ -35,20 +47,19 @@ class Box {
topBar.classList.add("boxTopBar"); topBar.classList.add("boxTopBar");
this.element.appendChild(topBar); this.element.appendChild(topBar);
let deleteButton = document.createElement("span"); let deleteButton = document.createElement("span");
deleteButton.classList.add("boxDeleteButton"); deleteButton.classList.add("boxDeleteButton");
deleteButton.addEventListener("click", () => { this.deleteBox() }); deleteButton.addEventListener("click", () => { this.deleteBox() });
topBar.appendChild(deleteButton); topBar.appendChild(deleteButton);
let content = document.createElement("div"); this.content = document.createElement("div");
content.classList.add("boxContent"); this.content.classList.add("boxContent");
this.element.appendChild(content); this.element.appendChild(this.content);
let title = document.createElement("span"); let title = document.createElement("span");
title.classList.add("boxTitle"); title.classList.add("boxTitle");
title.innerText = this.title; title.innerText = this.title;
content.appendChild(title); this.content.appendChild(title);
let connectorContainer = document.createElement("div"); let connectorContainer = document.createElement("div");
connectorContainer.classList.add("boxConnectorContainer"); connectorContainer.classList.add("boxConnectorContainer");
@ -64,22 +75,33 @@ class Box {
connectorContainer.appendChild(inputConnectorContainer); connectorContainer.appendChild(inputConnectorContainer);
connectorContainer.appendChild(outputConnectorContainer); connectorContainer.appendChild(outputConnectorContainer);
content.appendChild(connectorContainer); this.content.appendChild(connectorContainer);
for (connector of this.inputs) { for (let connector of this.inputs) {
inputConnectorContainer.appendChild(connector.element); inputConnectorContainer.appendChild(connector.buildElement());
} }
for (connector of this.outputs) { for (let connector of this.outputs) {
outputConnectorContainer.appendChild(connector.element); outputConnectorContainer.appendChild(connector.buildElement());
} }
this.updatePosition();
return this.element;
} }
updatePosition() { updatePosition() {
if (!this.element) return;
this.element.style.left = (this.x - Camera.x) + "px"; this.element.style.left = (this.x - Camera.x) + "px";
this.element.style.top = (this.y - Camera.y) + "px"; this.element.style.top = (this.y - Camera.y) + "px";
} }
deleteBox() { deleteBox() {
for (let connector of this.inputs) {
connector.disconnectBoth();
}
for (let connector of this.outputs) {
connector.disconnectBoth();
}
this.element.parentNode.removeChild(this.element); this.element.parentNode.removeChild(this.element);
for (let i = 0; i < Box.instances.length; i++) { for (let i = 0; i < Box.instances.length; i++) {
if (Box.instances[i] == this) { if (Box.instances[i] == this) {
@ -89,9 +111,3 @@ class Box {
} }
} }
} }
function addElement(type) {
let box = new Box("test");
document.getElementById("playground").appendChild(box.element);
}

View File

@ -2,41 +2,91 @@ class Connector {
static INPUT = 0; static INPUT = 0;
static OUTPUT = 1; static OUTPUT = 1;
type; direction;
label;
connection;
element; element;
circle;
constructor(type, label = "") { listeners = [];
this.type = type;
constructor(direction, label = "") {
this.direction = direction;
this.element = ""; this.element = "";
this.label = label; this.label = label;
if (this.label == "") { if (this.label == "") {
switch (this.type) { switch (this.direction) {
case Connector.INPUT: case Connector.INPUT:
this.label = "Input"; this.label = "Input";
break;
case Connector.OUTPUT: case Connector.OUTPUT:
this.label = "Output"; this.label = "Output";
break;
} }
} }
this.buildElement();
} }
buildElement() { buildElement() {
if (this.element) return this.element;
this.element = document.createElement("div"); this.element = document.createElement("div");
this.element.classList.add("connector"); this.element.classList.add("connector");
if (this.type == Connector.INPUT) { if (this.direction == Connector.INPUT) {
this.element.classList.add("connectorInput"); this.element.classList.add("connectorInput");
} }
if (this.type == Connector.OUTPUT) { if (this.direction == Connector.OUTPUT) {
this.element.classList.add("connectorOutput"); this.element.classList.add("connectorOutput");
} }
let circle = document.createElement("span"); this.circle = document.createElement("span");
circle.classList.add("connectorCircle"); this.circle.classList.add("connectorCircle");
let label = document.createElement("span"); let label = document.createElement("span");
label.innerText = this.label;
label.classList.add("connectorLabel"); label.classList.add("connectorLabel");
this.element.appendChild(this.circle);
this.element.appendChild(label);
this.element.addEventListener("click", () => {
ConnectionController.clickConnector(this);
})
return this.element;
}
connect(connector) {
this.connection = connector;
this.element.classList.add("connected");
}
disconnect() {
this.connection = null;
this.element.classList.remove("connected");
}
disconnectBoth() {
if (!this.connection) return;
this.connection.disconnect();
this.disconnect();
}
send(data) {
if (!this.connection) return;
this.connection.receive(data);
}
receive(data) {
for (let listener of this.listeners) {
listener(data);
}
}
addListener(listener) {
this.listeners.push(listener);
} }
} }

30
TextBox.js Normal file
View File

@ -0,0 +1,30 @@
class TextBox extends Box {
textbox;
constructor() {
super("Text Box");
let input = new Connector(Connector.INPUT);
input.addListener((data) => {
this.outputs[0].send(data);
this.textbox.value = data;
});
this.inputs.push(input);
this.outputs.push(new Connector(Connector.OUTPUT));
}
buildElement() {
if (this.element) return this.element;
super.buildElement();
this.textbox = document.createElement("textarea");
this.textbox.placeholder = "Lots of text...";
this.textbox.addEventListener("input", () => {
this.outputs[0].send(this.textbox.value);
})
this.content.appendChild(this.textbox);
return this.element;
}
}

66
draw-controller.js Normal file
View File

@ -0,0 +1,66 @@
let cv, c;
document.addEventListener("DOMContentLoaded", () => {
cv = document.getElementById("canvas");
c = cv.getContext("2d");
window.requestAnimationFrame(draw);
});
function draw() {
cv.width = window.innerWidth;
cv.height = window.innerHeight;
// draw connections
c.strokeStyle = "white";
c.lineWidth = 3;
for (let box of Box.instances) {
for (let input of box.inputs) {
if (!input.connection) continue;
let output = input.connection;
let i = input.circle.getBoundingClientRect();
let ix = i.x + i.width / 2;
let iy = i.y + i.height / 2;
let o = output.circle.getBoundingClientRect();
let ox = o.x + o.width / 2;
let oy = o.y + o.height / 2;
drawConnection(ox, oy, ix, iy);
}
}
// draw currently connecting connection
if (ConnectionController.currentlySelectedConnector) {
let i = ConnectionController.currentlySelectedConnector.circle.getBoundingClientRect();
let ix = i.x + i.width / 2;
let iy = i.y + i.height / 2;
let ox = Mouse.position.x;
let oy = Mouse.position.y;
if (ConnectionController.currentlySelectedConnector.direction == Connector.OUTPUT) {
ox = ix;
oy = iy;
ix = Mouse.position.x;
iy = Mouse.position.y;
}
drawConnection(ox, oy, ix, iy);
}
window.requestAnimationFrame(draw);
}
function drawConnection(x1, y1, x2, y2) {
let cpDistance = Math.min(Math.max((x1 - x2), 50), 100);
c.beginPath();
c.moveTo(x1, y1);
c.lineTo(x1 + 10, y1);
c.bezierCurveTo(x1 + cpDistance, y1, x2 - cpDistance, y2, x2 - 10, y2);
c.lineTo(x2, y2);
c.stroke();
}

54
element-controller.js Normal file
View File

@ -0,0 +1,54 @@
function addElement(type) {
if (type == "TextBox") {
document.getElementById("playground").appendChild(new TextBox().buildElement());
}
}
class ConnectionController {
static currentlySelectedConnector = null;
static clickConnector(connector) {
if (this.currentlySelectedConnector) {
let a = this.currentlySelectedConnector;
let b = connector;
if (a == b) {
this.unselect();
return;
}
if (a.direction == b.direction) {
this.unselect();
this.clickConnector(b);
return;
}
if (b.connection) {
b.disconnectBoth();
}
a.element.classList.add("connected");
b.element.classList.add("connected");
a.connect(b);
b.connect(a);
a.element.classList.remove("connecting");
this.currentlySelectedConnector = null;
} else {
if (connector.connection) {
connector.disconnectBoth();
}
connector.element.classList.add("connecting");
this.currentlySelectedConnector = connector;
}
}
static unselect() {
if (!this.currentlySelectedConnector) return;
this.currentlySelectedConnector.element.classList.remove("connecting");
this.currentlySelectedConnector = null;
}
}

View File

@ -4,9 +4,13 @@
<title>DataTools</title> <title>DataTools</title>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<script src="Camera.js"></script> <script src="Camera.js"></script>
<script src="Connector.js"></script>
<script src="Box.js"></script> <script src="Box.js"></script>
<script src="TextBox.js"></script>
<script src="menu.js"></script> <script src="menu.js"></script>
<script src="input-controller.js"></script> <script src="input-controller.js"></script>
<script src="element-controller.js"></script>
<script src="draw-controller.js"></script>
</head> </head>
<body> <body>
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>

View File

@ -2,9 +2,22 @@ let draggingBox = null;
let draggingCamera = false; let draggingCamera = false;
let lastMousePos = {x: 0, y: 0}; let lastMousePos = {x: 0, y: 0};
class Mouse {
static position = {x: 0, y: 0};
static lastPosition = {x: 0, y: 0};
static updatePosition(x, y) {
this.lastPosition = this.position;
this.position = {x: x, y: y};
}
static getDelta() {
return {x : this.position.x - this.lastPosition.x, y : this.position.y - this.lastPosition.y};
}
}
document.addEventListener("mousedown", (e) => { document.addEventListener("mousedown", (e) => {
let x = e.clientX; Mouse.updatePosition(e.clientX, e.clientY);
let y = e.clientY;
if (e.button == 0) { if (e.button == 0) {
if (e.target.classList.contains("boxDeleteButton")) return; if (e.target.classList.contains("boxDeleteButton")) return;
@ -12,9 +25,10 @@ document.addEventListener("mousedown", (e) => {
let topBarRect = box.element.getElementsByClassName("boxTopBar")[0].getBoundingClientRect(); let topBarRect = box.element.getElementsByClassName("boxTopBar")[0].getBoundingClientRect();
box.element.classList.remove("dragging"); box.element.classList.remove("dragging");
if (isPointInRect(x, y, topBarRect.x, topBarRect.y, topBarRect.width, topBarRect.height)) { if (isPointInRect(Mouse.position.x, Mouse.position.y, topBarRect.x, topBarRect.y, topBarRect.width, topBarRect.height)) {
draggingBox = box; draggingBox = box;
box.element.classList.add("dragging"); box.element.classList.add("dragging");
e.preventDefault();
break; break;
} }
} }
@ -33,16 +47,18 @@ document.addEventListener("mouseup", (e) => {
if (e.button == 1 || e.button == 2) { if (e.button == 1 || e.button == 2) {
draggingCamera = false; draggingCamera = false;
} }
}) });
document.addEventListener("mousemove", (e) => { document.addEventListener("mousemove", (e) => {
Mouse.updatePosition(e.clientX, e.clientY);
let delta = {x : e.clientX - lastMousePos.x, y : e.clientY - lastMousePos.y}; let delta = Mouse.getDelta();
if (draggingBox != null) { if (draggingBox != null) {
draggingBox.x += delta.x; draggingBox.x += delta.x;
draggingBox.y += delta.y; draggingBox.y += delta.y;
draggingBox.updatePosition(); draggingBox.updatePosition();
e.preventDefault();
} }
if (draggingCamera) { if (draggingCamera) {
@ -53,10 +69,10 @@ document.addEventListener("mousemove", (e) => {
document.getElementById("canvas").style.backgroundPosition = (-Camera.x) + "px " + (-Camera.y) + "px"; document.getElementById("canvas").style.backgroundPosition = (-Camera.x) + "px " + (-Camera.y) + "px";
} e.preventDefault();
lastMousePos = {x: e.clientX, y: e.clientY}; }
}) });
function isPointInRect(px, py, rx, ry, rw, rh) { function isPointInRect(px, py, rx, ry, rw, rh) {
return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh; return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;

View File

@ -125,3 +125,53 @@ textarea {
font-family: Monospace; font-family: Monospace;
min-width: 200px; min-width: 200px;
} }
.boxConnectorContainer {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
margin-left: -20px;
margin-right: -20px;
}
.connector {
display: flex;
align-items: center;
gap: 10px;
--connectorCircleSize: 25px;
cursor: pointer;
}
.connectorCircle {
display: block;
width: var(--connectorCircleSize);
height: var(--connectorCircleSize);
box-sizing: border-box;
border: 3px solid var(--bg);
border-radius: var(--connectorCircleSize);
background-color: var(--connectorCircleColor);
transition: scale 0.05s linear;
}
.connector.connectorInput {
flex-direction: row;
--connectorCircleColor: var(--blue);
}
.connector.connectorOutput {
flex-direction: row-reverse;
--connectorCircleColor: var(--yellow);
}
.connector:hover .connectorCircle {
scale: 1.2;
}
.connector.connecting {
--connectorCircleColor: white;
}
.connector.connected .connectorCircle {
border-color: white;
}