commit 36f86f523fb280f3e8947f9e43eb03408efde808 Author: Francesco Luzzana Date: Mon Apr 11 20:02:16 2022 +0200 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..60858a9 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Spaghetti Cables + +![spaghetti forgetti rigretti svg cables](cover.jpg) + +First attempt of using a cable-based interaction for web dasein. Research for the [mod cms](https://pad.xpub.nl/p/modcms) (workin title) + +See also the implementation in [panel](https://git.xpub.nl/kamo/panel)! diff --git a/cables.js b/cables.js new file mode 100644 index 0000000..530f4df --- /dev/null +++ b/cables.js @@ -0,0 +1,184 @@ +// Great resource from https://stackoverflow.com/a/40700068 +// Thank you ConnorFan + +var strokeWidth = 10; +var bufferSize; + +var svgElement = document.getElementById("svgElement"); +var rect = svgElement.getBoundingClientRect(); +var path = null; +var strPath; +var buffer = []; // Contains the last positions of the mouse cursor +var cable; + +const logs = document.getElementById("logs"); + +class Cable { + start = ""; + end = ""; + path = ""; + color = ""; + + constructor(start) { + this.start = start; + } +} + +svgElement.addEventListener("mousedown", function (e) { + bufferSize = document.getElementById("cmbBufferSize").value; + path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("fill", "none"); + + let color = randomColor(); + path.setAttribute("stroke", color); + path.setAttribute("stroke-width", strokeWidth); + path.setAttribute("stroke-linecap", "round"); + buffer = []; + var pt = getMousePosition(e); + appendToBuffer(pt); + strPath = "M" + pt.x + " " + pt.y; + path.setAttribute("d", strPath); + svgElement.appendChild(path); + + let socket = getSocket(pt); + if (socket) { + cable = new Cable(socket.querySelector("input").getAttribute("name")); + cable.color = color; + } +}); + +svgElement.addEventListener("mousemove", function (e) { + if (path) { + appendToBuffer(getMousePosition(e)); + updateSvgPath(); + } +}); + +svgElement.addEventListener("mouseup", function (e) { + if (path) { + let pt = getMousePosition(e); + let socket = getSocket(pt); + if (socket) { + cable.end = socket.querySelector("input").getAttribute("name"); + cable.path = path.getAttribute("d"); + logCable(cable); + } + + path = null; + } +}); + +svgElement.addEventListener("mouseleave", function (e) { + if (path) { + let pt = getMousePosition(e); + let socket = getSocket(pt); + if (socket) { + cable.end = socket.querySelector("input").getAttribute("name"); + cable.path = path.getAttribute("d"); + logCable(cable); + } + + path = null; + } +}); + +// ok +var getMousePosition = function (e) { + return { + x: e.pageX - rect.left, + y: e.pageY - rect.top, + }; +}; + +var appendToBuffer = function (pt) { + buffer.push(pt); + while (buffer.length > bufferSize) { + buffer.shift(); + } +}; + +// ok +// Calculate the average point, starting at offset in the buffer +var getAveragePoint = function (offset) { + var len = buffer.length; + if (len % 2 === 1 || len >= bufferSize) { + var totalX = 0; + var totalY = 0; + var pt, i; + var count = 0; + for (i = offset; i < len; i++) { + count++; + pt = buffer[i]; + totalX += pt.x; + totalY += pt.y; + } + return { + x: totalX / count, + y: totalY / count, + }; + } + return null; +}; + +// ok +var updateSvgPath = function () { + var pt = getAveragePoint(0); + + if (pt) { + // Get the smoothed part of the path that will not change + strPath += " L" + pt.x + " " + pt.y; + + // Get the last part of the path (close to the current mouse position) + // This part will change if the mouse moves again + var tmpPath = ""; + for (var offset = 2; offset < buffer.length; offset += 2) { + pt = getAveragePoint(offset); + tmpPath += " L" + pt.x + " " + pt.y; + } + + // Set the complete current path coordinates + path.setAttribute("d", strPath + tmpPath); + } +}; + +// ok +var randomColor = function () { + const hue = Math.floor(Math.random() * 360); + const saturation = Math.floor(50 + Math.random() * (50 + 1)) + "%"; + const lightness = "75%"; + return "hsl(" + hue + ", " + saturation + ", " + lightness + ")"; +}; + +// ok +var getSocket = function (position) { + precision = 8; + size = 24; + + const sockets = document.querySelectorAll(".socket"); + sock = Array.from(sockets).filter((socket) => { + input = socket.querySelector("input"); + return ( + position.x >= input.offsetLeft - precision + size / 2 && + position.x <= input.offsetLeft + precision + size / 2 && + position.y >= input.offsetTop - precision + size / 2 && + position.y <= input.offsetTop + precision + size / 2 + ); + })[0]; + return sock; +}; + +let logCable = function (cable) { + let entry = document.createElement("li"); + let marker = document.createElement("span"); + marker.classList.add("marker"); + marker.style.backgroundColor = cable.color; + + entry.appendChild(marker); + + let text = document.createElement("span"); + text.innerHTML = `connects ${cable.start} with ${cable.end}`; + + entry.appendChild(text); + + logs.appendChild(entry); +}; diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 0000000..37645f6 Binary files /dev/null and b/cover.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..1092e8f --- /dev/null +++ b/index.html @@ -0,0 +1,64 @@ + + + + + + + Document + + + + +
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..8f71117 --- /dev/null +++ b/style.css @@ -0,0 +1,110 @@ +* { + box-sizing: border-box; + font-family: 'Courier New', Courier, monospace; +} + +:root { + --radius: 2px; + +} + + +.control { + position: relative; + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.control label { + margin-top: 8px; +} + +.control+.control { + margin-left: 8px; +} + +.module input[type='submit'] { + position: absolute; + right: 32px; + bottom: 32px; + border: 1px solid currentColor; + background: none; + border-radius: var(--radius); + padding: 4px 8px; +} + +.module #name { + position: absolute; + bottom: 32px; + left: 32px; + padding: 4px 8px; + border-radius: var(--radius); + border: 1px solid currentColor; +} + + + +.module { + display: inline-flex; + justify-content: space-between; + position: relative; + min-width: 500px; + min-height: 500px; + border-radius: var(--radius); + + padding: 32px; + border: 1px solid currentColor; + + margin: 8px; +} + + +.socket + .socket { + margin-top: 64px; +} + +.socket input[type='text'] { + display: inline-block; + width: 24px; + height: 24px; + border-radius: 50%; + border: 1px solid currentColor; + cursor: alias; + position: relative; + z-index: 50; + background: none; + pointer-events: none; +} + +#svgElement { + z-index: 10; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + mix-blend-mode: multiply; +} + +#logs { + list-style: none; + margin: 8px; + padding: 0; +} + +.marker { + display: inline-block; + margin-right: 8px; + width: 0.75em; + height: 0.75em; + border-radius: 50%; + vertical-align: middle; +} + + + + + +