From 5b40644a38ab3b857ca3399fea9c96b1cbfe0abb Mon Sep 17 00:00:00 2001 From: Francesco Luzzana Date: Tue, 5 Apr 2022 03:09:50 +0200 Subject: [PATCH] cables wip --- modular/module.py | 15 ++- modular/static/css/cables.css | 47 +++++++++ modular/static/css/style.css | 37 ++++--- modular/static/js/cables.js | 0 modular/static/js/controls.js | 63 +++++++++++ modular/static/js/draw.js | 193 ++++++++++++++++++++++++++++++++++ modular/templates/cables.html | 68 ++++++++++++ modular/templates/module.html | 9 +- 8 files changed, 415 insertions(+), 17 deletions(-) create mode 100644 modular/static/css/cables.css create mode 100644 modular/static/js/cables.js create mode 100644 modular/static/js/controls.js create mode 100644 modular/static/js/draw.js create mode 100644 modular/templates/cables.html diff --git a/modular/module.py b/modular/module.py index 3e85a22..54bbc8c 100644 --- a/modular/module.py +++ b/modular/module.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request +from flask import Blueprint, redirect, render_template, request, url_for from datetime import datetime import json import os @@ -12,12 +12,21 @@ def module(): preset = {} for key in request.form.keys(): preset[key] = request.form.get(key) - name = preset.get('name', '') - filename = f"{name}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.json" + name = preset.get('name', 'preset') + date = datetime.now().strftime('%Y%m%d%H%M%S') + filename = f"{name}~{date}.json" + with open(os.path.join(os.environ.get('MODULES'), filename), 'w') as f: f.write(json.dumps(preset)) + return redirect(url_for('module.module')) + modules = [os.path.splitext(filename)[0] for filename in os.listdir(os.environ.get('MODULES'))] return render_template('module.html', modules=modules) + + +@bp.route('/cables') +def cables(): + return render_template('cables.html') diff --git a/modular/static/css/cables.css b/modular/static/css/cables.css new file mode 100644 index 0000000..5c230ed --- /dev/null +++ b/modular/static/css/cables.css @@ -0,0 +1,47 @@ +.module { + display: inline-flex; + justify-content: space-between; +} + + +.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; +} \ No newline at end of file diff --git a/modular/static/css/style.css b/modular/static/css/style.css index c41034d..c55cbe0 100644 --- a/modular/static/css/style.css +++ b/modular/static/css/style.css @@ -7,29 +7,30 @@ --radius: 2px; } + +#rack { + width: 100%; + display: +} + + .module { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - - display: flex; - justify-content: flex-start; - align-items: start; - width: 480px; - height: 240px; + position: relative; + display: inline-block; + min-width: 480px; + min-height: 240px; border-radius: var(--radius); padding: 32px; border: 1px solid currentColor; - margin: 0 auto; + margin: 8px; } .control { position: relative; - display: flex; + display: inline-flex; flex-direction: column; justify-content: center; align-items: center; @@ -60,4 +61,14 @@ padding: 4px 8px; border-radius: var(--radius); border: 1px solid currentColor; -} \ No newline at end of file +} + +/* .socket input[type='text']{ + display: inline-block; + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid currentColor; + user-select: none; + pointer-events: none; +} */ \ No newline at end of file diff --git a/modular/static/js/cables.js b/modular/static/js/cables.js new file mode 100644 index 0000000..e69de29 diff --git a/modular/static/js/controls.js b/modular/static/js/controls.js new file mode 100644 index 0000000..e761970 --- /dev/null +++ b/modular/static/js/controls.js @@ -0,0 +1,63 @@ +let test = { + attack: "50", + decay: "83", + sustain: "0", + release: "50", + name: "tasto", +}; + +const rack = document.getElementById("rack"); + +let createKnob = function (control, value) { + let container = document.createElement("div"); + container.classList.add("control"); + + let knob = document.createElement("input"); + knob.setAttribute("type", "range"); + knob.classList.add("input-knob"); + knob.dataset.fgcolor = "white"; + knob.setAttribute("name", control); + knob.setAttribute("value", value); + + let label = document.createElement("label"); + label.setAttribute("for", control); + label.innerHTML = control; + + container.appendChild(knob); + container.appendChild(label); + + return container; +}; + +let deserialize = function (patch) { + let module = document.createElement("div"); + module.classList.add("module"); + + let name = document.createElement("input"); + name.id = "name"; + name.value = patch.name || ""; + name.placeholder = "Name"; + name.setAttribute("type", "text"); + + module.appendChild(name); + + let save = document.createElement("input"); + save.setAttribute("type", "submit"); + save.id = "submit"; + save.value = "Save"; + + module.appendChild(save); + + for (const [key, value] of Object.entries(patch)) { + console.log(key, value); + if (key == "name") { + name.value = value; + } else { + module.appendChild(createKnob(key, value)); + } + } + + rack.appendChild(module); +}; + +deserialize(test); diff --git a/modular/static/js/draw.js b/modular/static/js/draw.js new file mode 100644 index 0000000..ae7bbe1 --- /dev/null +++ b/modular/static/js/draw.js @@ -0,0 +1,193 @@ +// 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; + } +}); + +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(); + } +}; + +// 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; +}; + +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); + } +}; + +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 + ")"; +}; + +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); +}; + +// +// +// +// SAVE THE BRANCH + +// const form = document.querySelector("form"); + +// form.addEventListener("submit", () => { +// let wrapper = document.createElement("div"); +// wrapper.appendChild(svgElement); +// form["content"].value = wrapper.innerHTML; +// return true; +// }); diff --git a/modular/templates/cables.html b/modular/templates/cables.html new file mode 100644 index 0000000..a089543 --- /dev/null +++ b/modular/templates/cables.html @@ -0,0 +1,68 @@ + + + + + + + Module test + + + + + + + + +
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + diff --git a/modular/templates/module.html b/modular/templates/module.html index 8fb7c78..1399e35 100644 --- a/modular/templates/module.html +++ b/modular/templates/module.html @@ -6,6 +6,7 @@ Module test + @@ -15,6 +16,8 @@ {% endfor %} +
+
@@ -36,7 +39,11 @@
- + + +