diff options
Diffstat (limited to 'grapher')
-rw-r--r-- | grapher/index.html | 86 | ||||
-rw-r--r-- | grapher/script.js | 288 | ||||
-rw-r--r-- | grapher/style.css | 184 |
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; +} |