aboutsummaryrefslogtreecommitdiffstats
path: root/grapher
diff options
context:
space:
mode:
Diffstat (limited to 'grapher')
-rw-r--r--grapher/index.html86
-rw-r--r--grapher/script.js288
-rw-r--r--grapher/style.css184
3 files changed, 558 insertions, 0 deletions
diff --git a/grapher/index.html b/grapher/index.html
new file mode 100644
index 0000000..e0990b7
--- /dev/null
+++ b/grapher/index.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width">
+ <meta name="description" content="A simple utility to help you make CV2 graphs, out-of-game.">
+ <meta name="author" content="@✨Aleteoryx✨#1027">
+ <title>CV2 Node Graph Generator</title>
+ <link crossorigin href="style.css" rel="stylesheet" type="text/css" />
+ <link crossorigin href="/chips.css" rel="stylesheet" type="text/css" />
+
+ <link crossorigin href="/util.js" rel="preload" as="script"/>
+ <link crossorigin href="/chips.js" rel="preload" as="script"/>
+ <link crossorigin href="script.js" rel="preload" as="script"/>
+ <link crossorigin href="/chips.css" rel="preload" as="style"/>
+ <link crossorigin href="style.css" rel="preload" as="style"/>
+ <link crossorigin href="/circuitsv2.json" rel="preload" as="fetch"/>
+ <link crossorigin href="https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json" rel="preload" as="fetch"/>
+
+ <script crossorigin src="/util.js"></script>
+ <script crossorigin src="/chips.js"></script>
+ <script crossorigin src="script.js"></script>
+ <noscript><style> #searcher, #graph, #canvas { display: none } </style></noscript>
+ </head>
+ <body>
+ <noscript>
+ The chip grapher requires JavaScript to function.
+ If you cannot enable it here, you can try using tyleo's
+ <a href="https://www.figma.com/community/file/1070759222767700948">
+ figma templates
+ </a>, although those also need javascript.4
+ </noscript>
+ <details id="searchbox" open>
+ <summary></summary>
+ <iframe id="searcher" src="/"></iframe>
+ </details>
+ <div id="graph">
+
+ </div>
+ <canvas id="canvas"></canvas>
+ <details id="helpbox">
+ <summary>?</summary>
+ <div>
+ <!-- Unused tabbing system. Ignore.
+ <input type="radio" name="test" class="selecttab" checked/>
+ <p class="tab">text for button 1</p>
+ <input type="radio" name="test" class="selecttab"/>
+ <p class="tab">text for button 2</p>
+ <input type="radio" name="test" class="selecttab"/>
+ <p class="tab">text for button 3</p>
+ <input type="radio" name="test" class="selecttab"/>
+ <p class="tab">text for button 4</p>-->
+ <h2>The Basics</h2>
+ <hr/>
+ <p>The chip grapher is a simple program.
+ To get started, use the searcher sidebar to find a chip you want to add.
+ That sidebar is just an embedded version of the main searcher, and all
+ behavior is the same. To learn more, press the '?' button on the sidebar.
+ Once you've decided on a chip, press the '+' button next to it to add it
+ to the graph.</p>
+ <h3>Interacting with existing chips</h3>
+ <hr/>
+ <p>Chips suppport various operations.</p>
+ <h4>Wiring</h4>
+ <hr/>
+ <p>To wire 2 ports to one another, simply click and drag to a port of the same type.
+ All input data ports have a maximum of one connection, as do output execs.
+ To disconnect a wire, simply click on the end with a maxiumum of one connection.
+ </p>
+ <h4>Selecting</h4>
+ <hr>
+ <p>To select a chip, just click on it. If the threshold is too fast, shoot me a message on discord.</p>
+ <h4>Deleting</h4>
+ <hr>
+ <p>Once a chip is selected, press the <kbd>Delete</kbd> key to delete it.</p>
+ <h4>Type Selection</h4>
+ <hr>
+ <p>Some chips have a type union on a port. You can see this union by hovering over any white port.
+ When a chip with a type union is selected, drop down menus will appear to set the actual type.
+ Setting it will unwire all connections to the chip, so be sure to set it first thing!
+ </p>
+ </div>
+ </details>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/grapher/script.js b/grapher/script.js
new file mode 100644
index 0000000..6057220
--- /dev/null
+++ b/grapher/script.js
@@ -0,0 +1,288 @@
+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 = 5;
+ 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);
+ 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.button == 0) {
+ 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) {
+ switch (mode) {
+ case 'wire_i-o':
+ if (e.target.matches('.exec')) delConnections(e.target);
+ wirestate.o = e.target;
+ if (!e.target.parentElement.matches('.output'))
+ connections.pop();
+ else if (wirestate.i.nextElementSibling.innerText != wirestate.o.nextElementSibling.innerText)
+ connections.pop();
+ break;
+ case 'wire_o-i':
+ if (!e.target.matches('.exec')) delConnections(e.target);
+ wirestate.i = e.target;
+ if (!e.target.parentElement.matches('.input'))
+ connections.pop();
+ else if (wirestate.i.nextElementSibling.innerText != wirestate.o.nextElementSibling.innerText)
+ connections.pop();
+ 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 & 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(2) : rects.right + remtopx(1.5));
+ 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);
+ })()
+}
diff --git a/grapher/style.css b/grapher/style.css
new file mode 100644
index 0000000..793d213
--- /dev/null
+++ b/grapher/style.css
@@ -0,0 +1,184 @@
+@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap');
+
+body, .chipbox *, iframe {
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+html {
+ --foreforeground: #3788ae;
+ --foreground: #082f41;
+ --background: #03141c;
+ color: white;
+ background: var(--background);
+ /*padding: 1cm;*/
+ font-size: 12pt;
+ font-family: 'Raleway', sans-serif;
+ height: 100%;
+}
+
+body {
+ height: 100vh;
+ width: 100vw;
+ overflow: hidden;
+
+ display: grid;
+ grid-template-areas:
+ "searcher graph";
+ grid-column-gap: 0;
+ grid-row-gap: 0;
+}
+
+iframe#searcher {
+ height: 100vh;
+ width: 13cm;
+
+ margin: 0;
+ grid-area: searcher;
+ position: relative;
+}
+
+div#graph {
+ --graphOffsetX: 0;
+ --graphOffsetY: 0;
+ grid-area: graph;
+ width: calc(100vw - 13cm);
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAA3SURBVDhPY2RgYPgPxBQDZhEZMM0EJqkIRg2kHIwaSDkYNZByMGog5YARWNJSpcSGASq7kIEBAL94Aos8Ra5sAAAAAElFTkSuQmCC");
+ background-repeat: repeat;
+ background-position-x: calc(var(--graphOffsetX) * 1px);
+ background-position-y: calc(var(--graphOffsetY) * 1px);
+}
+
+#searchbox:not([open]) + div#graph {
+ width:100vw !important;
+}
+
+#searchbox:not([open]) > summary::marker {
+ content: ">>>";
+}
+#searchbox[open] > summary::marker {
+ content: "<<<";
+}
+
+#searchbox[open] > summary {
+ left: 13cm;
+}
+
+#searchbox > summary::marker {
+ position: absolute;
+}
+
+#searchbox > summary {
+ position: absolute;
+ z-index: 99999;
+ top: 50%;
+ bottom: 50%;
+ padding-left: 1mm;
+ height: calc(1rem + 1mm);
+ border: 1mm solid var(--foreground);
+ border-right: 2mm solid var(--foreground);
+ background: var(--foreground);
+ border-top-right-radius: 2mm;
+ border-bottom-right-radius: 2mm;
+}
+
+#searchbox {
+ border-right: 2mm var(--foreground) solid;
+ z-index: 999999;
+}
+
+div.chipbox {
+ --chipOffsetX: 0;
+ --chipOffsetY: 0;
+ position: absolute;
+ user-select: none;
+ transform: translate(
+ calc((var(--graphOffsetX) + var(--chipOffsetX)) * 1px),
+ calc((var(--graphOffsetY) + var(--chipOffsetY)) * 1px));
+}
+
+div.chipbox > div#selected {
+ padding: -1mm;
+ border: 1mm white solid;
+}
+
+div.chipbox > div#selected + .ui {
+ display: unset;
+}
+div.chipbox > div:not(#selected) + .ui {
+ display: none;
+}
+
+canvas {
+ position:fixed;
+ top:0;
+ bottom:0;
+ left:0;
+ right:0;
+ z-index: 100;
+
+ pointer-events: none;
+}
+
+#helpbox > summary {
+ height: 1.5cm;
+ width: 1.5cm;
+ display: flex;
+ background: #0000;
+ border: #fff7 solid 0.9mm;
+ color: #fff7;
+ border-radius: 1.5cm;
+ padding: 0;
+ font-size: 1.5cm;
+ align-items: center;
+ justify-content: center;
+ float: left;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ user-select: none;
+}
+#helpbox[open] > summary {
+ margin-bottom: 0;
+ background: #fff7;
+ border: #0000 solid 0.9mm;
+ color: #fff;
+}
+
+#helpbox {
+ position: fixed;
+ bottom: 2rem;
+ right: 2rem;
+ background: #0000;
+ border-radius: 0;
+}
+
+#helpbox > div {
+ background: var(--foreground);
+ border-radius: 0.5cm;
+ height: 60vh;
+ width: 10cm;
+ overflow-x: auto;
+ padding: 1rem;
+ position: relative;
+ bottom: 2cm;
+ box-shadow: black 1mm 1mm 3mm
+}
+
+
+hr {
+ border: 0.5mm solid var(--foreforeground);
+ background: var(--foreforeground);
+ width: 100%;
+ height: 0%;
+
+}
+
+kbd {
+ background: white;
+ color: black;
+ padding: 0.5mm;
+ border-radius: 1mm;
+ box-shadow: inset black -0.25mm -0.25mm 1mm;
+}