var v2data;
var graph;
var searcher;
const rootel = document.documentElement;
const chips = [];
/*
[
{
i: func() -> {x:Number, y:Number} | <div>,
o: func() -> {x:Number, y:Number} | <div>
}
]
*/
const connections = [];
var mode;
var targ;
var start;
var wirestate;
const lastmp = {x:0,y:0};
const graphPos = {x:0,y:0};
var canvas,ctx;
const allTypes = [];
function switchID(el, id) {
const old = document.getElementById(id);
if (old instanceof Element) old.id = '';
if (el instanceof Element) el .id = id;
}
function remtopx(value) {return value * parseFloat( getComputedStyle( document.documentElement ).fontSize )}
function renderCurveBetweenPorts(outx, outy, inx, iny) {
var dist = (((outx - inx) ** 2) + ((outy - iny) ** 2)) ** 0.5;
var heightOfCurve = Math.abs(iny - outy);
var widthOfCurve = Math.abs(inx - outx);
var halfWidth = (inx - outx)/2;
var cpbasex = (Math.abs((widthOfCurve * 2) / dist * 10) + 60) * (heightOfCurve / 150)**0.8;
var cp1x = outx + cpbasex;
var cp2x = inx - cpbasex;
//point(cp1x, outy);
//point(cp2x, iny);
//point(inx - outx, outy);
ctx.beginPath();
ctx.moveTo(outx,outy);
ctx.bezierCurveTo(cp1x, outy, cp2x, iny, inx, iny);
ctx.moveTo(outx,outy);
ctx.closePath();
ctx.lineWidth = 3;
ctx.stroke();
}
function appendTypeUI(chip) {
const data = [];
for(const key of Object.keys(chip.typeInfo)) {
data.push(`${key}: `);
let m = newEl('select', 'typeSelect');
m.addEventListener('change', e => {
if (e.target.value) chip.currentOverrides[key] = e.target.value;
else delete chip.currentOverrides[key]
delConnections(chip.el.children[0].children[0]);
chip.el.children[0].children[0].remove();
chip.el.children[0].prepend(generateChipHTML(chip.nd, chip.currentOverrides));
});
for(const type of chip.typeInfo[key]) {
let opt = newEl('option');
opt.value = (type == chip.typeInfo[key][0]) ? '' : type;
opt.innerText = type;
m.appendChild(opt);
}
data.push(m, newEl('br'))
}
const ui = newEl('div', 'ui');
ui.append(...data);
chip.el.append(ui);
}
function delConnections(el) {
let tmp = connections.filter(con => !(((con.i instanceof Node) && el.contains(con.i)) ||
((con.o instanceof Node) && el.contains(con.o)) ||
(con.i == el) || (con.i == el)));
connections.length = 0;
connections.push(...tmp);
}
window.onload = async function() {
graph = document.getElementById("graph");
searcher = document.getElementById("searcher");
v2data = await fetch(/*"https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json"/*/"/circuitsv2.json")
.then(res => res.json());
allTypes.push(...ListAllTypes(v2data.Nodes).sort((a,b) => (a.toLowerCase() > b.toLowerCase()) ? 1 : -1));
window.onmessage = function({data}) {
if (data.type == 'newChip') {
const types = {};
const typeParams = v2data.Nodes[data.GUID].NodeDescs[0].ReadonlyTypeParams;
for (const desc of Object.keys(typeParams))
types[desc] = [
`${desc}: ${typeParams[desc]}`,
...(typeParams[desc] == 'any' ? allTypes : typeParams[desc].match(/^\((.+)\)$/)[1].split(' | '))
];
const ne = newEl('div', 'chipbox');
const chipcontainer = newEl('div', 'selUI');
chipcontainer.append(generateChipHTML(v2data.Nodes[data.GUID].NodeDescs));
ne.append(chipcontainer);
graph.append(ne);
let newchipx = -graphPos.x + (graph.getClientRects()[0].width / 2) - (ne.getClientRects()[0].width / 2);
let newchipy = -graphPos.y + (graph.getClientRects()[0].height / 2) - (ne.getClientRects()[0].height / 2);
ne.style.setProperty('--chipOffsetX', newchipx);
ne.style.setProperty('--chipOffsetY', newchipy);
const chip = {
el: ne,
typeInfo: types,
currentOverrides: [],
nd: v2data.Nodes[data.GUID].NodeDescs
};
appendTypeUI(chip);
chips.push(chip);
console.log(types);
}
}
graph.addEventListener('mousedown', function(e) {
if (e.buttons & 1) {
start = performance.now();
targ = e.target;
if (e.target.parentElement.matches('.input')) {
if (!e.target.matches('.exec')) delConnections(e.target);
mode = 'wire_i-o';
wirestate = {
i: e.target,
o: lastmp
};
connections.push(wirestate);
}
else if (e.target.parentElement.matches('.output')) {
if (e.target.matches('.exec')) delConnections(e.target);
mode = 'wire_o-i';
wirestate = {
i: lastmp,
o: e.target
};
connections.push(wirestate);
}
else if (targ = (
() => {
for (const node of chips) if (node.el.contains(e.target)) return node.el;
return false;
})()
)
mode = 'drag';
else if (e.target == graph) switchID(null, 'selected')
}
});
rootel.addEventListener("mouseup", e => {
if (e.button == 0) {
let newCon;
switch (mode) {
case 'wire_i-o':
connections.pop()
newCon = {i: wirestate.i, o: e.target}
if (e.target.matches('.exec')) delConnections(e.target);
if (e.target.parentElement.matches('.output') && (newCon.i.nextElementSibling.innerText == newCon.o.nextElementSibling.innerText))
connections.push(newCon);
break;
case 'wire_o-i':
connections.pop()
newCon = {o: wirestate.o, i: e.target}
if (!e.target.matches('.exec') && !e.target.matches('#graph')) delConnections(e.target);
if (e.target.parentElement.matches('.input') && (newCon.i.nextElementSibling.innerText == newCon.o.nextElementSibling.innerText))
connections.push(newCon);
break;
case 'drag':
if ((performance.now() - start) < 150 && !targ.children[0].matches('#selected')) {
switchID(targ.children[0], 'selected')
}
break;
}
mode = null;
}
});
rootel.addEventListener("mousemove", e => {
if (e.buttons & 2) {
mode = '';
let tmp = connections.pop();
if ((tmp.i instanceof Element) && (tmp.o instanceof Element)) connections.push(tmp);
}
if (e.buttons & 4) {
graphPos.x += e.clientX - lastmp.x;
graphPos.y += e.clientY - lastmp.y;
graph.style.setProperty('--graphOffsetX', graphPos.x);
graph.style.setProperty('--graphOffsetY', graphPos.y);
}
switch (mode) {
case 'drag':
let newchipx = Number(targ.style.getPropertyValue('--chipOffsetX')) + e.clientX - lastmp.x;
let newchipy = Number(targ.style.getPropertyValue('--chipOffsetY')) + e.clientY - lastmp.y;
targ.style.setProperty('--chipOffsetX', newchipx);
targ.style.setProperty('--chipOffsetY', newchipy);
break;
}
lastmp.x = e.clientX;
lastmp.y = e.clientY;
});
root.addEventListener("mousemove", e => {
root.style.setProperty('--mouse-x', (e.clientX - graphPos.x - searcher.clientWidth) + "px");
root.style.setProperty('--mouse-y', (e.clientY - graphPos.y) + "px");
});
function deleteSel() {
let sel = document.getElementById("selected").parentElement;
if (!sel) return;
delConnections(sel);
{
let tmp = chips.filter(chip => !(chip.el == sel));
chips.length = 0;
chips.push(...tmp);
}
sel.remove();
}
root.addEventListener("keydown", e => {
switch (e.code) {
case 'Delete':
deleteSel();
break;
}
});
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
window.addEventListener("resize", e => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
(async function updateanim(t) {
ctx.clearRect(0,0,window.innerWidth,window.innerHeight);
for(const wire of connections) {
const points = [];
for (const point of [wire.o, wire.i]) {
if (point instanceof Element) {
let rects = point.getClientRects()[0];
points.push(point == wire.i ? rects.left - remtopx(1.5) : rects.right + remtopx(1.1));
points.push((rects.top + rects.bottom) / 2);
} else {
points.push(point.x);
points.push(point.y);
}
}
if ((((points[0] >= -10) && (points[0] <= window.innerWidth)) &&
((points[1] >= -10) && (points[1] <= window.innerHeight))) ||
(((points[2] >= -10) && (points[2] <= window.innerWidth)) &&
((points[3] >= -10) && (points[3] <= window.innerHeight)))) {
var m = null;
for (const cls of (wire.i instanceof Element ? wire.i : wire.o).classList) m = m || portColors[cls];
ctx.strokeStyle = m;
renderCurveBetweenPorts(...points);
}
}
requestAnimationFrame(updateanim);
})()
}