diff --git a/css/style.css b/css/style.css index cf2d16e..bde57e7 100644 --- a/css/style.css +++ b/css/style.css @@ -1,3 +1,8 @@ +:root { + color-scheme: light only; + touch-action: manipulation !important; +} + body, html { margin: 0; padding: 0; @@ -92,7 +97,7 @@ body, html { background-color: grey; } -.header { +.node .header { user-select: none; display: block; border-radius: 0.5rem; @@ -101,6 +106,7 @@ body, html { right: 0; text-align: center; height: 2rem; + line-height: 2rem; font-size: 1.5rem; color: white; cursor: grab; @@ -110,6 +116,56 @@ body, html { margin-bottom: 1rem; } +#renderOutput { + position: fixed; + top: 0; + left: 0; + z-index: 102; + background-color: rgb(60, 60, 60); + border-radius: 0.5rem; + max-width: 16rem; + max-height: 16rem; + overflow: scroll; + box-shadow: 0rem 0rem 0.4rem rgba(255, 255, 255, 0.5); +} + +#renderOutput .header button { + cursor: pointer; + outline: 0; + border: 0; + background-color: rgb(80, 80, 80); + border-radius: 0.2rem; + border: 2px solid black; + color: white; + text-align: center; + padding: 0.2rem 0.4rem; + margin: 0.1rem; + margin-left: 0.3rem; +} + +#renderOutput .header { + padding: 0.2rem; + user-select: none; + display: block; + border-radius: 0.5rem; + top: 0; + left: 0; + right: 0; + text-align: center; + height: 2rem; + font-size: 1.5rem; + color: white; + cursor: grab; + overflow: hidden; + overflow-x: hidden; + overflow-y: hidden; + background-color: rgb(40, 40, 40); +} + +#renderOutput[grabbing] .header { + cursor: grabbing; +} + .node[grabbing]>.header { cursor: grabbing; } @@ -128,6 +184,7 @@ body, html { background-color: lightgrey; cursor: text; font-size: 1.5rem; + white-space: nowrap; } .inputField { @@ -251,15 +308,6 @@ body, html { padding-left: 2rem; } -#rentex { - position: fixed; - z-index: 10; - top: 0; - left: 0; - pointer-events: none; - opacity: 0.7; -} - #modeSelect { border-radius: 0.2rem; color: white; @@ -299,3 +347,56 @@ body, html { user-select: none; cursor: pointer; } + +#trashbin { + position: fixed; + left: 0; + bottom: 0; + z-index: 5; + width: 3.5rem; + opacity: 0.5; + margin: 0.5rem; + transition: 0.3s; +} + +#trashbin:hover { + width: 4rem; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + outline: 0 !important; +} + +#suggestions { + position: fixed; + z-index: 300; + display: none; + background-color: lightgray; + border-radius: 0.5rem; + max-height: 10rem; + overflow-y: scroll; + overflow-x: hidden; + transition: 0.1s; +} + +#suggestions div { + padding: 0.5rem; + border-bottom: 1px solid grey; + cursor: pointer; +} + +#suggestions[visible] { + display: block; +} + +#rentex { + margin: auto 0; +} \ No newline at end of file diff --git a/flow.html b/flow.html index 4da581e..0d499e5 100644 --- a/flow.html +++ b/flow.html @@ -3,7 +3,8 @@ Flow - + + @@ -11,7 +12,17 @@ + + +
- Flow + Flow + +
+ ? +
Clear
@@ -39,6 +50,10 @@
Run >
+
Play
+
- +
+
- + - +
+
+
-
-
- + + + + + + + + diff --git a/libs/soundplayer.js b/libs/soundplayer.js new file mode 100644 index 0000000..070f73d --- /dev/null +++ b/libs/soundplayer.js @@ -0,0 +1,93 @@ +window.AudioContext = window.AudioContext || window.webkitAudioContext; +// Original JavaScript code by Chirp Internet: www.chirpinternet.eu +// Please acknowledge use of this code by including this header. + +function SoundPlayer(audioContext, filterNode) { + this.audioCtx = audioContext; + this.gainNode = this.audioCtx.createGain(); + + if (filterNode) { + // run output through extra filter (already connected to audioContext) + this.gainNode.connect(filterNode); + } else { + this.gainNode.connect(this.audioCtx.destination); + } + + this.oscillator = this.audioCtx.createOscillator(); +} + +SoundPlayer.prototype.setFrequency = function (val, when) { + //Frequency in Hz + if (this.oscillator !== null) { + if (when) { + this.oscillator.frequency.setValueAtTime( + val, + this.audioCtx.currentTime + when + ); + } else { + this.oscillator.frequency.setValueAtTime(val, this.audioCtx.currentTime); + } + } + return this; +}; + +SoundPlayer.prototype.setVolume = function (val, when) { + //Gain? + //min: -3.4028235e38 + //max: 3.4028235e38 + if (when) { + this.gainNode.gain.exponentialRampToValueAtTime( + val, + this.audioCtx.currentTime + when + ); + } else { + this.gainNode.gain.setValueAtTime(val, this.audioCtx.currentTime); + } + return this; +}; + +SoundPlayer.prototype.setWaveType = function (waveType) { + //sine, square, sawtooth or triangle + this.oscillator.type = waveType; + return this; +}; + +SoundPlayer.prototype.play = function (freq, vol, wave, when) { + this.oscillator = this.audioCtx.createOscillator(); + this.oscillator.connect(this.gainNode); + this.setFrequency(freq); + if (wave) { + this.setWaveType(wave); + } + this.setVolume(1 / 1000); + + if (when) { + this.setVolume(1 / 1000, when - 0.02); + this.oscillator.start(when - 0.02); + this.setVolume(vol, when); + } else { + this.oscillator.start(); + this.setVolume(vol, 0.02); + } + + return this; +}; + +SoundPlayer.prototype.stop = function (when) { + if (when) { + this.gainNode.gain.setTargetAtTime( + 1 / 1000, + this.audioCtx.currentTime + when - 0.05, + 0.02 + ); + this.oscillator.stop(this.audioCtx.currentTime + when); + } else { + this.gainNode.gain.setTargetAtTime( + 1 / 1000, + this.audioCtx.currentTime, + 0.02 + ); + this.oscillator.stop(this.audioCtx.currentTime + 0.05); + } + return this; +}; diff --git a/manifest.json b/manifest.json index 3fcdf80..d42a704 100644 --- a/manifest.json +++ b/manifest.json @@ -8,10 +8,10 @@ "sizes": "300x300" } ], - "start_url": "", + "start_url": "/", "background_color": "#000000", "display": "standalone", - "scope": "", + "scope": "/", "theme_color": "#000000", "description": "Visual math in the web." } diff --git a/res/rodbin.svg b/res/rodbin.svg new file mode 100644 index 0000000..fae1201 --- /dev/null +++ b/res/rodbin.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/autoEval.js b/scripts/autoEval.js index 645593b..cb9bf94 100644 --- a/scripts/autoEval.js +++ b/scripts/autoEval.js @@ -5,4 +5,4 @@ setInterval(() => { ) { dispOutput(true); } -}, 500); +}, 10); diff --git a/scripts/bLink.js b/scripts/bLink.js index 831ffbf..9277c2f 100644 --- a/scripts/bLink.js +++ b/scripts/bLink.js @@ -38,7 +38,9 @@ function linkDragHandler(outputElem) { "stroke:white;stroke-width:4;stroke-linecap:round;" ); document.querySelector("#linkCanvas").appendChild(line); + soundEffect("click"); } + function elementDrag(e) { e = e || window.event; e.preventDefault(); @@ -74,6 +76,7 @@ function linkDragHandler(outputElem) { if (line.remove) { line.remove(); } + soundEffect("connect"); makeLink(outputElem, el); } else { if (line.remove) { @@ -87,9 +90,11 @@ function linkDragHandler(outputElem) { document.ontouchmove = null; } } + function lerp(a, b, k) { return (b - a) * k + a; } + function resize() { document .querySelector("#linkCanvas") @@ -101,6 +106,7 @@ function resize() { .querySelector("#linkCanvas") .setAttribute("viewBox", `0 0 ${window.innerWidth} ${window.innerHeight}`); } + function makeLink(output, input) { var outRect = output.getBoundingClientRect(); var inRect = input.getBoundingClientRect(); diff --git a/scripts/blah_dummy.js b/scripts/blah_dummy.js new file mode 100644 index 0000000..3a02693 --- /dev/null +++ b/scripts/blah_dummy.js @@ -0,0 +1,177 @@ +window.library = {}; +function getOutput(auto = false) { + if (!document.querySelector(".node .header[data-flag-isOutput]")) { + if (!auto) { + alert( + "Unable to get output due to lack of an output node. Insert an output node to continue." + ); + } + return 0; + } + var outputNode = document.querySelector( + ".node .header[data-flag-isOutput]" + ).parentElement; + var v = outputNode.getValue() + return v.func(...v.fields); +} +function dispOutput(auto = false) { + document.getElementById("nOutputDisp").innerText = + Math.round(getOutput(auto) * 1000000) / 1000000; +} +function addNode( + namespace, + alias, + argv, + func, + color, + headerAttrs = {}, + attrs = {} +) { + window.library[namespace] = { + namespace: namespace, + alias: alias, + title: alias[0] || "Untitled", + argv: argv || [], + func: func, + color: color || "darkcyan", + headerAttrs: headerAttrs, + attrs: attrs, + }; + if (typeof attrs["doc"] === "string") { + window.documentation[namespace] = attrs["doc"]; + } +} +function addNodeToCanvas(nodetype, x, y) { + var bounds = document.querySelector("#canvas").getBoundingClientRect(); + var node = document.createElement("div"); + node.setAttribute("data-type", nodetype.namespace); + node.classList.add("node"); + node.style = ` + top:${y + bounds.y}; + left:${x + bounds.x}; + `; + node.setAttribute("data-y", y + bounds.y); + node.setAttribute("data-x", x + bounds.x); + var title = document.createElement("span"); + title.innerText = nodetype.title; + ``; + title.classList.add("header"); + title.style = "background: " + nodetype.color + ";"; + Object.keys(nodetype.headerAttrs).forEach((attr) => { + title.setAttribute(attr, nodetype.headerAttrs[attr]); + }); + node.append(title); + + if (!nodetype.attrs["no_out"]) { + var output = document.createElement("div"); + output.innerText = "O"; + output.classList.add("output"); + linkDragHandler(output); + node.append(output); + } + if (nodetype.attrs["dynamic"]) { + node.setAttribute("data-dynamic", true); + } + + var inputs = document.createElement("table"); + nodetype.argv.forEach((argv) => { + var tr = document.createElement("tr"); + tr.classList.add("inputRow"); + var td = document.createElement("td"); + td.classList.add("input"); + td.onclick = () => { + if (td["link"]) { + td["link"]["outputNode"]["link"] = null; + if (td["link"].remove) { + td["link"].remove(); + } + td["link"] = null; + } + }; + var i = document.createElement("input"); + var td2 = document.createElement("td"); + i.classList.add("inputField"); + i.setAttribute("type", "number"); + td2.append(i); + td.innerText = argv; + tr.append(td); + tr.append(td2); + inputs.append(tr); + }); + node.append(inputs); + + window.addEventListener("keydown", (event) => { + if ( + (event.key === "Backspace" || event.key === "Delete") && + node.hasAttribute("grabbing") + ) { + node["removeListeners"].forEach((func) => { + func(); + }); + node.remove(); + soundEffect("delete"); + } + }); + node["removeListeners"] = []; + node["dragListeners"] = []; + node["cache.tr"] = null; + node["getValue"] = function () { + var fields = []; + var iFields = + node["cache.tr"] ?? (node["cache.tr"] = node.querySelectorAll("tr")); + for (let i = 0; i < iFields.length; i++) { + const row = iFields[i]; + if ( + row.childNodes[1]?.childNodes[0]?.value && + parseFloat(row.childNodes[1].childNodes[0].value) + ) { + fields.push(parseFloat(row.childNodes[1].childNodes[0].value)); + } else if ( + row.childNodes[0]?.["link"]?.["outputNode"]?.parentElement?.["getValue"] + ) { + var v = row.childNodes[0]["link"]["outputNode"].parentElement["getValue"](); + fields.push( + v.func(...v.fields) || + 0 + ); + } else { + fields.push(0); + } + if (window.mode === "number" || window.renderPreflight) { + if (row.childNodes[1]?.childNodes[0]) { + row.childNodes[1].childNodes[0].setAttribute( + "placeholder", + fields[i] + ); + } + } + } + return {func: nodetype.func, fields}; + }; + dragElem(node); + document.querySelector("#canvas").append(node); + return node; +} +function insertNode() { + var results = []; + var name = document.querySelector("#input").innerText; + var keys = Object.keys(window.library); + try { + keys.forEach((key) => { + if (key.toLowerCase() === name.toLowerCase()) { + results.push(key); + } + window.library[key].alias.forEach((alias) => { + if (alias.toLowerCase() === name.toLowerCase()) { + results.push(key); + } + }); + }); + } catch (error) { + alert(error); + } + if (!window.library[results[0]]) { + return; + } + addNodeToCanvas(window.library[results[0]], 0, 0); +} diff --git a/scripts/classes.js b/scripts/classes.js index 75ef637..1bf8108 100644 --- a/scripts/classes.js +++ b/scripts/classes.js @@ -8,7 +8,11 @@ addNode( } return a + b; }, - "darkcyan" + "darkcyan", + {}, + { + doc: "Adds inputs A and B together and then returns it.", + } ); addNode( "subtract", @@ -20,7 +24,11 @@ addNode( } return a - b; }, - "darkcyan" + "darkcyan", + {}, + { + doc: "Subtracts B from A and then returns it.", + } ); addNode( "multiply", @@ -32,7 +40,11 @@ addNode( } return a * b; }, - "darkcyan" + "darkcyan", + {}, + { + doc: "Multiples A by B and then returns it.", + } ); addNode( "divide", @@ -44,7 +56,11 @@ addNode( } return a / b; }, - "darkcyan" + "darkcyan", + {}, + { + doc: "Divides A by B and then returns it.", + } ); addNode( "const", @@ -67,6 +83,9 @@ addNode( { contentEditable: true, onblur: "this.innerText = this.innerText.replaceAll('\\n', '')", + }, + { + doc: "A constant. Can be used to reroute flow or as an input.", } ); addNode( @@ -76,7 +95,11 @@ addNode( (base, exp) => { return Math.pow(base, exp); }, - "darkcyan" + "darkcyan", + {}, + { + doc: "Returns Base to the power Exponent and then returns it.", + } ); addNode( "if", @@ -91,7 +114,14 @@ addNode( return aeb; } }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Takes in inputs 'A' and 'B', then if 'A' is greater than 'B', it returns the value of the input labeled 'A>B'. + If 'A' is less than 'B', it returns the value of the input labeled 'A { return n % mod; }, - "darkcyan" + "darkcyan", + {}, + { + doc: `Finds the remainder of 'N' over 'Mod'. Eg: +
0 mod 2 = 0; +
1 mod 2 = 1; +
2 mod 2 = 0;`, + } ); addNode( "min", @@ -109,7 +146,11 @@ addNode( (a, b) => { return Math.min(a, b); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the smallest of A and B.`, + } ); addNode( "max", @@ -118,7 +159,11 @@ addNode( (a, b) => { return Math.max(a, b); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the largest of A and B.`, + } ); addNode( "unknown", @@ -131,6 +176,7 @@ addNode( {}, { no_out: true, + doc: "Replaces any nodes whose type does not exist when loading from a save.", } ); addNode( @@ -140,10 +186,13 @@ addNode( (v, min, max) => { return Math.min(Math.max(v, min), max); }, - "darkred" + "darkred", + {}, + { + doc: `Limits 'V' to the Minimum and Maximum values provided.`, + } ); - addNode( "rnd", ["Random (0.0-1.0)", "random", "rand", "ran"], @@ -155,6 +204,7 @@ addNode( {}, { dynamic: true, + doc: `Returns a random decimal between 0.0 to 1.0.`, } ); addNode( @@ -168,6 +218,7 @@ addNode( {}, { dynamic: true, + doc: `Returns a random integer between Min and Max.`, } ); addNode( @@ -178,17 +229,14 @@ addNode( var b = Math.pow(i, -1); return Math.pow(n, b); }, - "darkred" -); -addNode( - "debug", - ["Debug", "print", "alert"], - ["In"], - (input) => { - alert("Type: " + typeof input + "\nValue: " + input); - return input; - }, - "darkred" + "darkred", + {}, + { + doc: `Gets the root of a number. Eg: +
25 root 2 = 5; +
125 root 3 = 5; +
36 root 2 = 6;`, + } ); addNode( "output", @@ -203,6 +251,7 @@ addNode( }, { no_out: true, + doc: `Serves as the output in number mode. The value it gets from 'Out' is displayed in the toolbar.`, } ); addNode( @@ -212,7 +261,11 @@ addNode( (deg) => { return Math.sin(deg * (Math.PI / 180)); }, - "darkred" + "darkred", + {}, + { + doc: `Gets the sine of the angle in degrees provided.`, + } ); addNode( "cos", @@ -221,7 +274,11 @@ addNode( (deg) => { return Math.cos(deg * (Math.PI / 180)); }, - "darkred" + "darkred", + {}, + { + doc: `Gets the cosine of the angle in degrees provided.`, + } ); addNode( "abs", @@ -230,7 +287,16 @@ addNode( (n) => { return Math.abs(n); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the absolute of the input number Can be described as removing the number's sign. Eg: +
Abs(4)=4; +
Abs(-3)=3; +
Abs(0)=0; +
Abs(-1)=1; + `, + } ); addNode( "lerp", @@ -239,7 +305,15 @@ addNode( (a, b, k) => { return (b - a) * k + a; }, - "darkred" + "darkred", + {}, + { + doc: `Linear Interpolates between A and B with an Alpha ranging from 0.0 to 1.0. Eg: +
Lerping with A=0, B=10, Alpha=0.0 returns 0. +
Lerping with A=0, B=10, Alpha=0.5 returns 5. +
Lerping with A=0, B=10, Alpha=1.0 returns 10. + `, + } ); addNode( "time", @@ -256,5 +330,6 @@ addNode( {}, { dynamic: true, + doc: `Returns the current time in milliseconds (one thousandth of a second).`, } ); diff --git a/scripts/documentation.js b/scripts/documentation.js new file mode 100644 index 0000000..b75dfe9 --- /dev/null +++ b/scripts/documentation.js @@ -0,0 +1,33 @@ +window.documentation = {}; +function displayDocumentation() { + try { + var docWin = window.open(); + var keys = Object.keys(documentation); + keys.forEach((key) => { + var title = window.library[key].alias[0]; + var h2 = document.createElement("h2"); + h2.innerText = title; + var p = document.createElement("p"); + p.innerHTML = window.documentation[key]; + docWin.document.body.appendChild(h2); + docWin.document.body.appendChild(p); + + if (window.library[key].argv.length > 0) { + var h4 = document.createElement("h4"); + h4.innerText = "Arguments: "; + var t = document.createElement("table"); + for (let i = 0; i < window.library[key].argv.length; i++) { + var arg = window.library[key].argv[i]; + var tr = document.createElement("tr"); + tr.innerText = arg; + tr.style = "border: 1px solid black;"; + t.append(tr); + } + docWin.document.body.appendChild(h4); + docWin.document.body.appendChild(t); + } + }); + } catch (error) { + alert(error); + } +} diff --git a/scripts/dragElem.js b/scripts/dragElem.js index 9def415..20ee284 100644 --- a/scripts/dragElem.js +++ b/scripts/dragElem.js @@ -16,8 +16,8 @@ function dragElem(elmnt) { function dragMouseDown(e) { e = e || window.event; /*/ get the mouse cursor position at startup:/*/ - pos3 = (typeof e.touches === "object" ? e.touches[0].clientX : e.clientX); - pos4 = (typeof e.touches === "object" ? e.touches[0].clientY : e.clientY); + pos3 = typeof e.touches === "object" ? e.touches[0].clientX : e.clientX; + pos4 = typeof e.touches === "object" ? e.touches[0].clientY : e.clientY; document.onmouseup = closeDragElement; document.ontouchend = closeDragElement; document.ontouchcancel = closeDragElement; @@ -37,7 +37,6 @@ function dragElem(elmnt) { pos3 = typeof e.touches === "object" ? e.touches[0].clientX : e.clientX; pos4 = typeof e.touches === "object" ? e.touches[0].clientY : e.clientY; /*/ set the element's new position:/*/ - var bounds = document.querySelector("#canvas").getBoundingClientRect(); elmnt.style.top = elmnt.offsetTop - pos2 + "px"; elmnt.style.left = elmnt.offsetLeft - pos1 + "px"; elmnt.setAttribute("data-y", elmnt.offsetTop - pos2); @@ -52,11 +51,34 @@ function dragElem(elmnt) { function closeDragElement() { /*/ stop moving when mouse button is released:/*/ + document.onmouseup = null; document.onmousemove = null; document.ontouchend = null; document.ontouchcancel = null; document.ontouchmove = null; elmnt.removeAttribute("grabbing"); + if (elmnt.className.includes("node")) { + if (!document.querySelector("#trashbin")) { + return; + } + var trashBounds = document + .querySelector("#trashbin") + .getBoundingClientRect(); + if ( + trashBounds.x < pos3 && + trashBounds.x + trashBounds.width > pos3 && + trashBounds.y < pos4 && + trashBounds.y + trashBounds.width > pos4 + ) { + if (elmnt["removeListeners"]) { + elmnt["removeListeners"].forEach((func) => { + func(); + }); + elmnt.remove(); + soundEffect("delete"); + } + } + } } } diff --git a/scripts/dummies.js b/scripts/dummies.js new file mode 100644 index 0000000..3c57cd3 --- /dev/null +++ b/scripts/dummies.js @@ -0,0 +1,34 @@ +let x = { + func: (v1)=>{return v1}, + fields: [ + { + func:(v1,v2)=>{return v1*v2}, + fields:[ + { + func: ([])=>{return v1+v2}, + fields:[ + 1, + 2 + ] + }, + { + func: (v1,v2)=>{return v1/v2}, + fields:[ + 255, + 2 + ] + } + ] + } + ] +} + +let xCompiled = { + func: ([])=>{ + return func1([]) + } +} + +function getBlue(){ + x.func(x.fields) +} \ No newline at end of file diff --git a/scripts/math.js b/scripts/math.js index 66faecb..5996836 100644 --- a/scripts/math.js +++ b/scripts/math.js @@ -5,7 +5,12 @@ addNode( () => { return Math.PI; }, - "darkmagenta" + "darkmagenta", + {}, + { + dynamic: true, + doc: `Returns pi.`, + } ); addNode( "e", @@ -14,7 +19,12 @@ addNode( () => { return Math.E; }, - "darkmagenta" + "darkmagenta", + {}, + { + dynamic: true, + doc: `Returns e.`, + } ); addNode( @@ -24,7 +34,12 @@ addNode( (x) => { return Math.sqrt(x); }, - "darkred" + "darkred", + {}, + { + dynamic: true, + doc: `Returns the square-root of x.`, + } ); addNode( "cbrt", @@ -33,7 +48,12 @@ addNode( (x) => { return Math.cbrt(x); }, - "darkred" + "darkred", + {}, + { + dynamic: true, + doc: `Returns the cube-root of x.`, + } ); addNode( @@ -43,7 +63,11 @@ addNode( (x) => { return Math.floor(x); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the floor of N; can be described as rounding down or removes decimals.`, + } ); addNode( "ceil", @@ -52,7 +76,11 @@ addNode( (x) => { return Math.floor(x); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the ceil of N; can be described as rounding up.`, + } ); addNode( "round", @@ -61,7 +89,11 @@ addNode( (x) => { return Math.round(x); }, - "darkred" + "darkred", + {}, + { + doc: `Rounds N.`, + } ); addNode( "tan", @@ -70,5 +102,9 @@ addNode( (deg) => { return Math.tan(deg * (Math.PI / 180)); }, - "darkred" + "darkred", + {}, + { + doc: `Returns the tangent of the input angle.`, + } ); diff --git a/scripts/modes.js b/scripts/modes.js new file mode 100644 index 0000000..0ff9669 --- /dev/null +++ b/scripts/modes.js @@ -0,0 +1,25 @@ +window.modes = {}; +window.currentMode = "number"; +function addMode(id, title, selectors) { + window.modes[id] = { title: title, selectors: selectors }; + var o = document.createElement("option"); + o.value = id; + o.innerText = title; + document.querySelector("#modeSelect").append(o); +} +function modeUpdate(mode) { + window.currentMode = mode; + var k = Object.keys(window.modes); + k.forEach((key) => { + window.modes[key].selectors.forEach((sel) => { + document.querySelector(sel).classList.add("hidden"); + }); + }); + window.modes[mode].selectors.forEach((showSel) => { + document.querySelector(showSel).classList.remove("hidden"); + }); +} +addMode("number", "Number", ["#nOutputDisp", "#run", "td:has(#autoEval)"]); +addMode("rentex", "2D RenTex", ["#renderOutput", "#rdownload", "#render"]); +addMode("soundwave", "Soundwave", ["#playsp"]); +modeUpdate("number"); diff --git a/scripts/node.js b/scripts/node.js index c25046a..04a8d4e 100644 --- a/scripts/node.js +++ b/scripts/node.js @@ -36,6 +36,9 @@ function addNode( headerAttrs: headerAttrs, attrs: attrs, }; + if (typeof attrs["doc"] === "string") { + window.documentation[namespace] = attrs["doc"]; + } } function addNodeToCanvas(nodetype, x, y) { var bounds = document.querySelector("#canvas").getBoundingClientRect(); @@ -87,6 +90,7 @@ function addNodeToCanvas(nodetype, x, y) { var i = document.createElement("input"); var td2 = document.createElement("td"); i.classList.add("inputField"); + i.setAttribute("type", "number"); td2.append(i); td.innerText = argv; tr.append(td); @@ -104,38 +108,41 @@ function addNodeToCanvas(nodetype, x, y) { func(); }); node.remove(); + soundEffect("delete"); } }); node["removeListeners"] = []; node["dragListeners"] = []; - node["cacheValue"] = undefined; + node["cache.tr"] = null; node["getValue"] = function () { var fields = []; - var iFields = node.querySelectorAll("tr"); + var iFields = + node["cache.tr"] ?? (node["cache.tr"] = node.querySelectorAll("tr")); for (let i = 0; i < iFields.length; i++) { const row = iFields[i]; if ( - row.childNodes[1] && - row.childNodes[1].querySelector("input") && - row.childNodes[1].querySelector("input").value && - parseFloat(row.childNodes[1].querySelector("input").value) + row.childNodes[1]?.childNodes[0]?.value && + parseFloat(row.childNodes[1].childNodes[0].value) ) { - fields.push(parseFloat(row.childNodes[1].querySelector("input").value)); + fields.push(parseFloat(row.childNodes[1].childNodes[0].value)); } else if ( - row.childNodes[0] && - row.childNodes[0]["link"] && - row.childNodes[0]["link"]["outputNode"] && - row.childNodes[0]["link"]["outputNode"].closest(".node") && - row.childNodes[0]["link"]["outputNode"].closest(".node")["getValue"] + row.childNodes[0]?.["link"]?.["outputNode"]?.parentElement?.["getValue"] ) { fields.push( - row.childNodes[0]["link"]["outputNode"] - .closest(".node") - ["getValue"]() || 0 + row.childNodes[0]["link"]["outputNode"].parentElement["getValue"]() || + 0 ); } else { fields.push(0); } + if (window.mode === "number" || window.renderPreflight) { + if (row.childNodes[1]?.childNodes[0]) { + row.childNodes[1].childNodes[0].setAttribute( + "placeholder", + fields[i] + ); + } + } } return nodetype.func(...fields); }; diff --git a/scripts/noiseClasses.js b/scripts/noiseClasses.js index f6206a2..07ae147 100644 --- a/scripts/noiseClasses.js +++ b/scripts/noiseClasses.js @@ -13,7 +13,11 @@ addNode( } return noise.simplex2(x, y); }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Returns the simplex noise of X and Y with the Seed, a number ranging from -1.0 to 1.0.`, + } ); addNode( @@ -31,7 +35,61 @@ addNode( } return noise.perlin2(x, y); }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Returns the perlin noise of X and Y with the Seed, a number ranging from -1.0 to 1.0.`, + } +); +addNode( + "perlin3", + ["3D Perlin Noise", "noise"], + ["x", "y", "z", "seed"], + (x, y, z, seed) => { + if ( + typeof x !== "number" || + typeof y !== "number" || + typeof z !== "number" + ) { + return 0; + } + if (typeof seed === "number") { + noise.seed(seed); + } else { + noise.seed(0); + } + return noise.perlin3(x, y, z); + }, + "darkgreen", + {}, + { + doc: `Returns the perlin noise of X, Y and Z with the Seed, a number ranging from -1.0 to 1.0.`, + } +); +addNode( + "simplex3", + ["3D Simplex Noise", "noise"], + ["x", "y", "z", "seed"], + (x, y, z, seed) => { + if ( + typeof x !== "number" || + typeof y !== "number" || + typeof z !== "number" + ) { + return 0; + } + if (typeof seed === "number") { + noise.seed(seed); + } else { + noise.seed(0); + } + return noise.simplex3(x, y, z); + }, + "darkgreen", + {}, + { + doc: `Returns the simplex noise of X, Y and Z with the Seed, a number ranging from -1.0 to 1.0.`, + } ); addNode( @@ -67,7 +125,11 @@ addNode( return window.noise.worley.Euclidean(x, y, z)[0]; } }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Returns the worley euclidean noise of X, Y and Z with the Seed and index, a number ranging from 0.0 to 1.0.`, + } ); addNode( "worley_manhattan", @@ -102,7 +164,11 @@ addNode( return window.noise.worley.Manhattan(x, y, z)[0]; } }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Returns the worley manhattan noise of X, Y and Z with the Seed and index, a number ranging from 0.0 to 1.0.`, + } ); addNode( "worley_minkovski", @@ -137,5 +203,9 @@ addNode( return window.noise.worley.Minkovski(x, y, z)[0]; } }, - "darkgreen" + "darkgreen", + {}, + { + doc: `Returns the worley minkovski noise of X, Y and Z with the Seed and index, a number ranging from 0.0 to 1.0.`, + } ); diff --git a/scripts/protocol.js b/scripts/protocol.js new file mode 100644 index 0000000..e69de29 diff --git a/scripts/rentex.js b/scripts/rentex.js index 259c6b7..9cd8f3c 100644 --- a/scripts/rentex.js +++ b/scripts/rentex.js @@ -3,7 +3,15 @@ var rentex = { ry: 0, width: 300, height: 300, + redNode: null, + greenNode: null, + blueNode: null, + alphaNode: null, + duration: 0, }; +window.mode = "number"; +window.renderPreflight = false; +dragElem(document.querySelector("#renderOutput")); addNode( "xpos", ["X Position", "xcoord", "X"], @@ -15,6 +23,7 @@ addNode( {}, { dynamic: true, + doc: `In 2D RenTex mode: The X position of the pixel, 0 being the very left most column.`, } ); @@ -36,6 +45,7 @@ addNode( { no_out: true, static: true, + doc: `In 2D RenText mode, this node sets the width and height of the rendered image.`, } ); @@ -46,7 +56,11 @@ addNode( () => { return rentex.width; }, - "grey" + "grey", + {}, + { + doc: `Returns the width of the render, 300 by default.`, + } ); addNode( @@ -56,7 +70,11 @@ addNode( () => { return rentex.height; }, - "grey" + "grey", + {}, + { + doc: `Returns the height of the render, 300 by default.`, + } ); addNode( @@ -70,6 +88,7 @@ addNode( {}, { dynamic: true, + doc: `In 2D RenTex mode: The Y position of the pixel, 0 being the very top most row.`, } ); addNode( @@ -85,6 +104,7 @@ addNode( }, { no_out: true, + doc: `Sets the red value of the pixel in 2D RenTex mode.`, } ); addNode( @@ -100,6 +120,7 @@ addNode( }, { no_out: true, + doc: `Sets the green value of the pixel in 2D RenTex mode.`, } ); addNode( @@ -115,6 +136,7 @@ addNode( }, { no_out: true, + doc: `Sets the blue value of the pixel in 2D RenTex mode.`, } ); addNode( @@ -130,9 +152,13 @@ addNode( }, { no_out: true, + doc: `Sets the alpha value of the pixel in 2D RenTex mode. 0 means transparent, 255 means fully opaque.`, } ); function renderTexture2D() { + window.renderPreflight = true; + window.rx = 0; + window.ry = 0; try { getSize(); getRed(); @@ -142,9 +168,11 @@ function renderTexture2D() { } catch (error) { alert(error); } + window.renderPreflight = false; var ctx = document.querySelector("#rentex").getContext("2d"); var imageData = ctx.createImageData(rentex.width, rentex.height); var data = imageData.data; + rentex.duration = performance.now(); for (let i = 0; i < data.length; i += 4) { rentex.rx = (i / 4) % rentex.width; rentex.ry = Math.floor(i / 4 / rentex.width); @@ -153,7 +181,11 @@ function renderTexture2D() { data[i + 2] = getBlue(true); data[i + 3] = getAlpha(true); } + rentex.duration = performance.now() - rentex.duration; + alert(rentex.duration+"ms"); ctx.putImageData(imageData, 0, 0); + document.querySelector("#renderOutput").classList.remove("hidden"); + soundEffect("chime"); } function downloadRenderTexture() { var link = document.createElement("a"); @@ -161,31 +193,12 @@ function downloadRenderTexture() { link.href = document.querySelector("#rentex").toDataURL(); link.click(); } -function rModeUpdate(mode) { - switch (mode) { - case "number": - document.querySelector("#rentex").classList.add("hidden"); - document.querySelector("#rdownload").classList.add("hidden"); - document.querySelector("#render").classList.add("hidden"); - document.querySelector("#nOutputDisp").classList.remove("hidden"); - document.querySelector("#run").classList.remove("hidden"); - document - .querySelector("#autoEval") - .parentElement.classList.remove("hidden"); - break; - case "rentex": - document.querySelector("#rdownload").classList.remove("hidden"); - document.querySelector("#rentex").classList.remove("hidden"); - document.querySelector("#render").classList.remove("hidden"); - document.querySelector("#nOutputDisp").classList.add("hidden"); - document.querySelector("#run").classList.add("hidden"); - document.querySelector("#autoEval").parentElement.classList.add("hidden"); - break; - } -} function getRed(auto = false) { - if (!document.querySelector(".node .header[data-redoutput]")) { + if (!auto) { + rentex.redNode = document.querySelector(".node .header[data-redoutput]"); + } + if (!rentex.redNode) { if (!auto) { alert( "Unable to get red output due to lack of a redout node. Insert an redout node to continue." @@ -193,9 +206,7 @@ function getRed(auto = false) { } return 0; } - var outputNode = document.querySelector( - ".node .header[data-redoutput]" - ).parentElement; + var outputNode = rentex.redNode.parentElement; return outputNode.getValue(); } function getSize() { @@ -208,7 +219,12 @@ function getSize() { outputNode.getValue(); } function getAlpha(auto = false) { - if (!document.querySelector(".node .header[data-alphaoutput]")) { + if (!auto) { + rentex.alphaNode = document.querySelector( + ".node .header[data-alphaoutput]" + ); + } + if (!rentex.alphaNode) { if (!auto) { alert( "Unable to get alpha output due to lack of a alphaout node. Insert an alphaout node to continue." @@ -216,13 +232,17 @@ function getAlpha(auto = false) { } return 255; } - var outputNode = document.querySelector( - ".node .header[data-alphaoutput]" - ).parentElement; + var outputNode = rentex.alphaNode.parentElement; return outputNode.getValue(); } function getGreen(auto = false) { - if (!document.querySelector(".node .header[data-greenoutput]")) { + if (!auto) { + rentex.greenNode = document.querySelector( + ".node .header[data-greenoutput]" + ); + } + + if (!rentex.greenNode) { if (!auto) { alert( "Unable to get green output due to lack of a greenout node. Insert an greenout node to continue." @@ -230,13 +250,14 @@ function getGreen(auto = false) { } return 0; } - var outputNode = document.querySelector( - ".node .header[data-greenoutput]" - ).parentElement; + var outputNode = rentex.greenNode.parentElement; return outputNode.getValue(); } function getBlue(auto = false) { - if (!document.querySelector(".node .header[data-blueoutput]")) { + if (!auto) { + rentex.blueNode = document.querySelector(".node .header[data-blueoutput]"); + } + if (!rentex.blueNode) { if (!auto) { alert( "Unable to get blue output due to lack of a blueout node. Insert an blueout node to continue." @@ -244,8 +265,6 @@ function getBlue(auto = false) { } return 0; } - var outputNode = document.querySelector( - ".node .header[data-blueoutput]" - ).parentElement; + var outputNode = rentex.blueNode.parentElement; return outputNode.getValue(); } diff --git a/scripts/rentexButBetter.js b/scripts/rentexButBetter.js new file mode 100644 index 0000000..8ed1357 --- /dev/null +++ b/scripts/rentexButBetter.js @@ -0,0 +1,282 @@ +var rentex = { + rx: 0, + ry: 0, + width: 300, + height: 300, + redNode: null, + greenNode: null, + blueNode: null, + alphaNode: null, + duration: 0, +}; +window.mode = "number"; +window.renderPreflight = false; +dragElem(document.querySelector("#renderOutput")); +addNode( + "xpos", + ["X Position", "xcoord", "X"], + [], + () => { + return rentex.rx; + }, + "grey", + {}, + { + dynamic: true, + doc: `In 2D RenTex mode: The X position of the pixel, 0 being the very left most column.`, + } +); + +addNode( + "size", + ["Render Size", "render"], + ["Width", "Height"], + (width, height) => { + rentex.width = width || 300; + rentex.height = height || 300; + document.querySelector("#rentex").setAttribute("width", rentex.width); + document.querySelector("#rentex").setAttribute("height", rentex.height); + return; + }, + "grey", + { + "data-sizeoutput": "true", + }, + { + no_out: true, + static: true, + doc: `In 2D RenText mode, this node sets the width and height of the rendered image.`, + } +); + +addNode( + "width", + ["Render Width", "image width"], + [], + () => { + return rentex.width; + }, + "grey", + {}, + { + doc: `Returns the width of the render, 300 by default.`, + } +); + +addNode( + "height", + ["Render Height", "image height"], + [], + () => { + return rentex.height; + }, + "grey", + {}, + { + doc: `Returns the height of the render, 300 by default.`, + } +); + +addNode( + "ypos", + ["Y Position", "ycoord", "Y"], + [], + () => { + return rentex.ry; + }, + "grey", + {}, + { + dynamic: true, + doc: `In 2D RenTex mode: The Y position of the pixel, 0 being the very top most row.`, + } +); +addNode( + "redout", + ["Red (0-255)", "r", "red"], + ["R"], + (output) => { + return output; + }, + "darkred", + { + "data-redoutput": "true", + }, + { + no_out: true, + doc: `Sets the red value of the pixel in 2D RenTex mode.`, + } +); +addNode( + "greenout", + ["Green (0-255)", "g", "green"], + ["G"], + (output) => { + return output; + }, + "darkgreen", + { + "data-greenoutput": "true", + }, + { + no_out: true, + doc: `Sets the green value of the pixel in 2D RenTex mode.`, + } +); +addNode( + "blueout", + ["Blue (0-255)", "b", "blue"], + ["B"], + (output) => { + return output; + }, + "darkblue", + { + "data-blueoutput": "true", + }, + { + no_out: true, + doc: `Sets the blue value of the pixel in 2D RenTex mode.`, + } +); +addNode( + "alphaout", + ["Opacity (0-255)", "a", "alpha", "opacity"], + ["A"], + (output) => { + return output; + }, + "grey", + { + "data-alphaoutput": "true", + }, + { + no_out: true, + doc: `Sets the alpha value of the pixel in 2D RenTex mode. 0 means transparent, 255 means fully opaque.`, + } +); +function renderTexture2D() { + window.renderPreflight = true; + window.rx = 0; + window.ry = 0; + try { + getSize(); + getRed(); + getGreen(); + getBlue(); + getAlpha(); + } catch (error) { + alert(error); + } + window.renderPreflight = false; + var ctx = document.querySelector("#rentex").getContext("2d"); + var imageData = ctx.createImageData(rentex.width, rentex.height); + var data = imageData.data; + rentex.duration = performance.now(); + for (let i = 0; i < data.length; i += 4) { + rentex.rx = (i / 4) % rentex.width; + rentex.ry = Math.floor(i / 4 / rentex.width); + data[i] = getRed(true); + data[i + 1] = getGreen(true); + data[i + 2] = getBlue(true); + data[i + 3] = getAlpha(true); + } + rentex.duration = performance.now() - rentex.duration; + alert(rentex.duration + "ms"); + ctx.putImageData(imageData, 0, 0); + document.querySelector("#renderOutput").classList.remove("hidden"); + soundEffect("chime"); +} +function downloadRenderTexture() { + var link = document.createElement("a"); + link.download = "rentex.png"; + link.href = document.querySelector("#rentex").toDataURL(); + link.click(); +} + +function getRed(auto = false) { + if (!auto) { + rentex.redNode = document.querySelector(".node .header[data-redoutput]"); + } + if (!rentex.redNode) { + if (!auto) { + alert( + "Unable to get red output due to lack of a redout node. Insert an redout node to continue." + ); + } + return 0; + } + var outputNode = rentex.redNode.parentElement; + var v = outputNode.getValue(); + return v.func(...v.fields); +} +function getSize() { + if (!document.querySelector(".node .header[data-sizeoutput]")) { + return; + } + var outputNode = document.querySelector( + ".node .header[data-sizeoutput]" + ).parentElement; + var v = outputNode.getValue(); + return v.func(...v.fields); +} +function getAlpha(auto = false) { + if (!auto) { + rentex.alphaNode = document.querySelector( + ".node .header[data-alphaoutput]" + ); + } + if (!rentex.alphaNode) { + if (!auto) { + alert( + "Unable to get alpha output due to lack of a alphaout node. Insert an alphaout node to continue." + ); + } + return 255; + } + var outputNode = rentex.alphaNode.parentElement; + var v = outputNode.getValue(); + return v.func(...v.fields); +} +function getGreen(auto = false) { + if (!auto) { + rentex.greenNode = document.querySelector( + ".node .header[data-greenoutput]" + ); + } + + if (!rentex.greenNode) { + if (!auto) { + alert( + "Unable to get green output due to lack of a greenout node. Insert an greenout node to continue." + ); + } + return 0; + } + var outputNode = rentex.greenNode.parentElement; + var v = outputNode.getValue(); + return v.func(...v.fields); +} +function getBlue(auto = false) { + if (!auto) { + rentex.blueNode = document.querySelector(".node .header[data-blueoutput]"); + } + if (!rentex.blueNode) { + if (!auto) { + alert( + "Unable to get blue output due to lack of a blueout node. Insert an blueout node to continue." + ); + } + return 0; + } + var outputNode = rentex.blueNode.parentElement; + if (!auto) { + var v = outputNode.getValue(); + rentex.blueFunc = v.func; + rentex.blueFields = v.fields; + } + debugger; + return rentex.blueFunc(...rentex.blueFields); + // var v = outputNode.getValue(); + // return v.func(...v.fields); +} diff --git a/scripts/serialise.js b/scripts/serialise.js index 164a127..cca192c 100644 --- a/scripts/serialise.js +++ b/scripts/serialise.js @@ -54,7 +54,7 @@ function deserialise(serialised) { zoomIndex = serialised.zoomIndex || 1; updateZoom(); document.querySelector("#modeSelect").value = serialised.mode || "number"; - rModeUpdate(serialised.mode || "number"); + modeUpdate(serialised.mode || "number"); } catch (err) { alert(err); } diff --git a/scripts/sfx.js b/scripts/sfx.js new file mode 100644 index 0000000..d2aef4a --- /dev/null +++ b/scripts/sfx.js @@ -0,0 +1,10 @@ +window["sfx"] = { + click: new Audio("sfx/click.wav"), + chime: new Audio("sfx/chime.wav"), + delete: new Audio("sfx/delete.wav"), + connect: new Audio("sfx/connect.wav"), +}; +function soundEffect(key) { + window["sfx"][key].currentTime = 0; + window["sfx"][key].play(); +} diff --git a/scripts/soundwave.js b/scripts/soundwave.js new file mode 100644 index 0000000..8c244ac --- /dev/null +++ b/scripts/soundwave.js @@ -0,0 +1,144 @@ +addNode( + "frequencyout", + ["Frequency (Hz)", "freq", "hz", "frequency"], + ["G"], + (output) => { + return output; + }, + "darkviolet", + { + "data-frequencyoutput": "true", + }, + { + no_out: true, + doc: `Sets the output frequency (Hz) in Soundwave mode. Default is 256 Hz.`, + } +); +addNode( + "volumeout", + ["Volume (0.0-1.0)", "vol", "gain", "volume"], + ["G"], + (output) => { + return output; + }, + "darkviolet", + { + "data-volumeoutput": "true", + }, + { + no_out: true, + doc: `Sets the output volume in Soundwave mode, 0.0 being mute and 1.0 being full volume. Default is 0.5.`, + } +); +addNode( + "waveformout", + ["Wave Type ∿:0|⎍:1|△:2|⊿:3", "wave", "waveform", "wavetype"], + ["G"], + (output) => { + return output; + }, + "darkviolet", + { + "data-waveformoutput": "true", + }, + { + no_out: true, + doc: `Sets the output sound wavetype in Soundwave mode. 0 is a sine wave, 1 is square, 2 is triangle and 3 is sawtooth.`, + } +); + +var ctx = new AudioContext(); +var player = new SoundPlayer(ctx); +var SoundPlayerOn = false; +var updateAudio = null; +function togglePlay(btn) { + if (SoundPlayerOn) { + btn.innerText = "Play"; + player.stop(); + SoundPlayerOn = false; + if (updateAudio) { + clearInterval(updateAudio); + updateAudio = null; + } + } else { + btn.innerText = "Stop"; + player.play(256, 0.5, "sine"); + SoundPlayerOn = true; + if (updateAudio) { + clearInterval(updateAudio); + updateAudio = null; + } + getFrequency(); + getGain(); + getWaveForm(); + updateAudio = setInterval(() => { + player.setWaveType(getWaveForm(true)); + player.setVolume(getGain(true)); + player.setFrequency(getFrequency(true)); + }, 50); + } +} +function getFrequency(auto = false) { + if (!document.querySelector(".node .header[data-frequencyoutput]")) { + if (!auto) { + alert( + "Unable to get frequency output due to lack of a frequency node. Insert a frequency node to continue." + ); + } + return 256; + } + var outputNode = document.querySelector( + ".node .header[data-frequencyoutput]" + ).parentElement; + return outputNode.getValue() || 256; +} + +function getWaveForm(auto = false) { + if (!document.querySelector(".node .header[data-waveformoutput]")) { + if (!auto) { + alert( + "Unable to get waveform output due to lack of a waveform node. Insert a waveform node to continue." + ); + } + return "sine"; + } + var outputNode = document.querySelector( + ".node .header[data-waveformoutput]" + ).parentElement; + var waven = outputNode.getValue() || 0; + waven = Math.min(Math.max(waven, 0), 3); + waven = Math.floor(waven); + switch (waven) { + case 0: + return "sine"; + break; + case 1: + return "square"; + break; + case 2: + return "triangle"; + break; + case 3: + return "sawtooth"; + break; + + default: + return "sine"; + break; + } +} + +function getGain(auto = false) { + if (!document.querySelector(".node .header[data-volumeoutput]")) { + if (!auto) { + alert( + "Unable to get volume output due to lack of a volume node. Insert a volume node to continue." + ); + } + return 0.5; + } + var outputNode = document.querySelector( + ".node .header[data-volumeoutput]" + ).parentElement; + return outputNode.getValue(); +} diff --git a/scripts/suggestions.js b/scripts/suggestions.js new file mode 100644 index 0000000..0d7cbea --- /dev/null +++ b/scripts/suggestions.js @@ -0,0 +1,54 @@ +function positionSuggestions() { + var s = document.querySelector("#suggestions"); + var bounds = document.querySelector("#input").getBoundingClientRect(); + s.setAttribute( + "style", + `top: ${bounds.y + bounds.height}px; left: ${bounds.x}px; width: ${ + bounds.width + }px;` + ); +} +function showSuggestions() { + positionSuggestions(); + updateSuggestions(); + document.querySelector("#suggestions").setAttribute("visible", "true"); +} +function hideSuggestions() { + setTimeout(() => { + document.querySelector("#suggestions").removeAttribute("visible"); + }, 100); +} +function updateSuggestions() { + document.querySelector("#suggestions").innerHTML = ""; + var results = {}; + var name = document.querySelector("#input").innerText; + var keys = Object.keys(window.library); + keys.forEach((key) => { + if (key.toLowerCase().includes(name.toLowerCase())) { + results[key] = window.library[key].alias[0]; + } + window.library[key].alias.forEach((alias) => { + if (alias.toLowerCase().includes(name.toLowerCase())) { + results[key] = window.library[key].alias[0]; + } + }); + }); + function displayResults() { + var rKeys = Object.keys(results); + rKeys.forEach((key) => { + var r = document.createElement("div"); + r.innerText = results[key]; + r.onclick = () => { + document.querySelector("#input").innerText = key; + addNodeToCanvas(window.library[key], 0, 0); + }; + document.querySelector("#suggestions").append(r); + }); + } + displayResults(); +} +window.addEventListener("resize", positionSuggestions); +document.querySelector("#input").addEventListener("focus", showSuggestions); +document.querySelector("#input").addEventListener("keyup", updateSuggestions); +document.querySelector("#input").addEventListener("blur", hideSuggestions); +positionSuggestions(); diff --git a/scripts/zoom.js b/scripts/zoom.js index ba75dad..789d724 100644 --- a/scripts/zoom.js +++ b/scripts/zoom.js @@ -45,7 +45,7 @@ window.addEventListener("keydown", (e) => { if (e.key === "+") { zoomIndex -= 0.25; } - zoomIndex = Math.max(zoomIndex, 0.75); + zoomIndex = Math.max(zoomIndex, 1.25); zoomIndex = Math.min(zoomIndex, 2.5); updateZoom(); }); diff --git a/sfx/chime.wav b/sfx/chime.wav new file mode 100644 index 0000000..74888c4 Binary files /dev/null and b/sfx/chime.wav differ diff --git a/sfx/click.wav b/sfx/click.wav new file mode 100644 index 0000000..f795c3c Binary files /dev/null and b/sfx/click.wav differ diff --git a/sfx/connect.wav b/sfx/connect.wav new file mode 100644 index 0000000..697d739 Binary files /dev/null and b/sfx/connect.wav differ diff --git a/sfx/delete.wav b/sfx/delete.wav new file mode 100644 index 0000000..d8d9a2b Binary files /dev/null and b/sfx/delete.wav differ