From 050a141ab48dd43cbcb245bea49313b540b48985 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:11:32 +0100 Subject: [PATCH 01/19] Move Device and Link classes in separate files --- device.js | 50 +++++++++++++++++++++++++++++ link.js | 40 +++++++++++++++++++++++ preload.js | 93 +++--------------------------------------------------- 3 files changed, 95 insertions(+), 88 deletions(-) create mode 100644 device.js create mode 100644 link.js diff --git a/device.js b/device.js new file mode 100644 index 0000000..a502ef0 --- /dev/null +++ b/device.js @@ -0,0 +1,50 @@ +const path = require('path'); + +const publicPath = + process.env.NODE_ENV === 'development' + ? './public' + : path.join(process.resourcesPath, 'public'); +const imagesPath = path.join(publicPath, "assets", "images"); + +class Device { + constructor(x, y, type, id, name) { + this.x = x; + this.y = y; + this.type = type; + this.id = id; + this.name = name; + } + + show() { + return "
" + + "
A
" + + "
B
" + + "
"; + } + + move(newX, newY) { + this.x = newX; + this.y = newY; + } + + delete() { + let finished = false; + do { + finished = false + links.forEach((link, index, fullArray) => { + // if link on the device to delete + if ((this.id == link.device1) || (this.id == link.device2)) { + finished = true; + links.splice(index, 1); // changes the array length, act as break in loop + } + }); + } + while (finished) + } +} + +module.exports = Device; \ No newline at end of file diff --git a/link.js b/link.js new file mode 100644 index 0000000..e60679d --- /dev/null +++ b/link.js @@ -0,0 +1,40 @@ +const AES50 = { + A: "A", + B: "B" +}; + +class Link { + constructor(dev1, aes50_1, dev2, aes50_2) { + this.device1 = dev1; + this.device2 = dev2; + this.aes50_1 = aes50_1; + this.aes50_2 = aes50_2; + this.check(); + } + + /// Check if link is valable + check() { + links.forEach((link, index, fullArray) => { + // if link already on aes50 + if ((this.device1 == link.device1 && this.aes50_1 == link.aes50_1) || + (this.device1 == link.device2 && this.aes50_1 == link.aes50_2) || + (this.device2 == link.device1 && this.aes50_2 == link.aes50_1) || + (this.device2 == link.device2 && this.aes50_2 == link.aes50_2)) { + links.splice(index, 1); + } + }); + } + + show() { + let x1offset = this.aes50_1 == AES50.A ? 58 : 78; + let x2offset = this.aes50_2 == AES50.A ? 58 : 78; + //return ""; + return ""; + } +} + +module.exports = Link diff --git a/preload.js b/preload.js index a81daa9..2d900ec 100644 --- a/preload.js +++ b/preload.js @@ -1,6 +1,8 @@ const { contextBridge, ipcRenderer } = require('electron'); const { fs } = require('fs'); -const path = require('path') +const path = require('path'); +const Device = require('./device.js') +const Link = require('./link.js') // Uncomment for npm start command // process.env.NODE_ENV = 'development' @@ -16,91 +18,6 @@ const AES50 = { B: "B" }; -class Device { - constructor(x, y, type, id, name) { - this.x = x; - this.y = y; - this.type = type; - this.id = id; - this.name = name; - this.visible = true; - } - - show() { - if (this.visible) - { - return "
" + - "
A
" + - "
B
" + - "
"; - } - else return ""; - } - - move(newX, newY) { - this.x = newX; - this.y = newY; - } - - delete() { - this.visible = false; - links.forEach(link => { - // if link on the device to delete - if ((this.id == link.device1) || (this.id == link.device2)) { - link.delete() - } - }); - } -} - -class Link { - constructor(dev1, aes50_1, dev2, aes50_2) { - this.valid = true - this.device1 = dev1; - this.device2 = dev2; - this.aes50_1 = aes50_1; - this.aes50_2 = aes50_2; - this.check(); - } - - /// Check if link is valable - check() { - links.forEach((link, index, fullArray) => { - // if link already on aes50 - if ((this.device1 == link.device1 && this.aes50_1 == link.aes50_1) || - (this.device1 == link.device2 && this.aes50_1 == link.aes50_2) || - (this.device2 == link.device1 && this.aes50_2 == link.aes50_1) || - (this.device2 == link.device2 && this.aes50_2 == link.aes50_2)) { - links.splice(index, 1); - } - }); - } - - delete() { - this.valid = false; - this.device1 = -1; - this.aes50_1 = -1; - this.device2 = -1; - this.aes50_2 = -2; - } - - show() { - if (this.valid) { - let x1offset = this.aes50_1 == AES50.A ? 58 : 78; - let x2offset = this.aes50_2 == AES50.A ? 58 : 78; - //return ""; - return ""; - } - } -} /* contextBridge.exposeInMainWorld('versions', { node: () => process.versions.node, @@ -112,6 +29,7 @@ contextBridge.exposeInMainWorld('versions', { */ let devices = []; +// TODO: Share variables with modules let links = []; let selectedElement = null; let originX, originY, mouseX, mouseY; @@ -178,9 +96,7 @@ function enableClick(ele) { function enableTextBox(parent, ele) { ele.addEventListener("keyup", (ev) => { deviceId = ev.target.parentElement.id; - console.log(deviceId) devices[id2index(deviceId, devices)].name = ev.target.value; - console.log(devices) }); } @@ -279,5 +195,6 @@ ipcRenderer.on('file', (event, arg) => { links.push(new Link(link.device1, link.aes50_1, link.device2, link.aes50_2)); }) draw(); + idCnt = devices[devices.length - 1].id + 1; } }) \ No newline at end of file From 937e1e0baf303a096e77ffb1ecf4322babf338b6 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:50:46 +0100 Subject: [PATCH 02/19] Move constants in separate file --- const.js | 35 +++++++++++++++++++++++++++++++++++ device-detail-preload.js | 8 ++------ device-detail.html | 4 ---- device.js | 9 ++------- link.js | 9 +++------ preload.js | 16 ++-------------- 6 files changed, 44 insertions(+), 37 deletions(-) create mode 100644 const.js diff --git a/const.js b/const.js new file mode 100644 index 0000000..5a2780d --- /dev/null +++ b/const.js @@ -0,0 +1,35 @@ +const path = require('path'); + +const publicPath = + process.env.NODE_ENV === 'development' + ? './public' + : path.join(process.resourcesPath, 'public'); + +module.exports = { + imagesPath : path.join(publicPath, "assets", "images"), + iconsPath : path.join(publicPath, "assets", "icons", "svg"), + AES50: { + A: "A", + B: "B" + }, + icons : [ + "No icon", "Kick Back", "Kick Front", "Snare Top", "Snare Bottom", "High Tom", "Mid Tom", "Floor Tom", "Hi-Hat", "Ride", + "Drum Kit", "Cowbell", "Bongos", "Congas", "Tambourine", "Vibraphone", "Electric Bass", "Acoustic Bass", "Contrabass", "Les Paul Guitar", + "Ibanez Guitar", "Washburn Guitar", "Acoustic Guitar", "Bass Amp", "Guitar Amp", "Amp Cabinet", "Piano", "Organ", "Harpsichord", "Keyboard", + "Synthesizer 1", "Synthesizer 2", "Synthesizer 3", "Keytar", "Trumpet", "Trombone", "Saxophone", "Clarinet", "Violin", "Cello", + "Male Vocal", "Female Vocal", "Choir", "Hand Sign", "Talk A", "Talk B", "Large Diaphragm Mic", "Condenser Mic Left", "Condenser Mic Right", "Handheld Mic", + "Wireless Mic", "Podium Mic", "Headset Mic", "XLR Jack", "TRS Plug", "TRS Plug Left", "TRS Plug Right", "RCA Plug Left", "RCA Plug Right", "Reel to Reel", + "FX", "Computer", "Monitor Wedge", "Left Speaker", "Right Speaker", "Speaker Array", "Speaker on a Pole", "Amp Rack", "Controls", "Fader", + "MixBus", "Matrix", "Routing", "Smiley" + ], + colors : [ + {"Name": "Off", "Color": "#000000", "ID": "OFF"}, + {"Name": "Red", "Color": "#E72D2E", "ID": "RD"}, + {"Name": "Green", "Color": "#35e72d", "ID": "GN"}, + {"Name": "Yellow", "Color": "#FCF300", "ID": "YE"}, + {"Name": "Blue", "Color": "#0060FF", "ID": "BL"}, + {"Name": "Magenta", "Color": "#ED27AC", "ID": "MG"}, + {"Name": "Cyan", "Color": "#2DE0E7", "ID": "CY"}, + {"Name": "White", "Color": "#FFFFFF", "ID": "WH"} + ] +} \ No newline at end of file diff --git a/device-detail-preload.js b/device-detail-preload.js index 0fd52f2..0dae2eb 100644 --- a/device-detail-preload.js +++ b/device-detail-preload.js @@ -1,11 +1,7 @@ const { contextBridge, ipcRenderer } = require('electron') const path = require('path') +const Constants = require('./const.js') -const publicPath = - process.env.NODE_ENV === 'development' - ? './public' - : path.join(process.resourcesPath, 'public'); -const imagesPath = path.join(publicPath, "assets", "images"); function selectTopDiv(ele) { while (!ele.classList.contains('device') && ele.tagName != "INPUT") { @@ -28,5 +24,5 @@ function enableRightClick(ele) { ipcRenderer.on('type', (event, arg) => { console.log(arg) - document.getElementById("canvas").innerHTML += "
"; + document.getElementById("canvas").innerHTML += "
"; }); \ No newline at end of file diff --git a/device-detail.html b/device-detail.html index cfb885c..dc2cce2 100644 --- a/device-detail.html +++ b/device-detail.html @@ -1,9 +1,6 @@ - Device detail @@ -16,5 +13,4 @@
- \ No newline at end of file diff --git a/device.js b/device.js index a502ef0..b9e5efc 100644 --- a/device.js +++ b/device.js @@ -1,10 +1,5 @@ const path = require('path'); - -const publicPath = - process.env.NODE_ENV === 'development' - ? './public' - : path.join(process.resourcesPath, 'public'); -const imagesPath = path.join(publicPath, "assets", "images"); +const Constants = require('./const.js'); class Device { constructor(x, y, type, id, name) { @@ -22,7 +17,7 @@ class Device { "px; left: " + this.x + "px;'>" + "
A
" + "
B
" + - ""; } diff --git a/link.js b/link.js index e60679d..9081713 100644 --- a/link.js +++ b/link.js @@ -1,7 +1,4 @@ -const AES50 = { - A: "A", - B: "B" -}; +const Constants = require('./const.js'); class Link { constructor(dev1, aes50_1, dev2, aes50_2) { @@ -26,8 +23,8 @@ class Link { } show() { - let x1offset = this.aes50_1 == AES50.A ? 58 : 78; - let x2offset = this.aes50_2 == AES50.A ? 58 : 78; + let x1offset = this.aes50_1 == Constants.AES50.A ? 58 : 78; + let x2offset = this.aes50_2 == Constants.AES50.A ? 58 : 78; //return ""; return " process.versions.node, @@ -106,7 +94,7 @@ window.addEventListener("mousemove", (ev) => { if (selectedElement.classList.contains('AES50')) { fromID = selectedElement.parentElement.id fromAES50 = selectedElement.classList[1] - xoffset = fromAES50 == AES50.A ? 58 : 78; + xoffset = fromAES50 == Constants.AES50.A ? 58 : 78; document.getElementById("lines").innerHTML = " Number(element.id) === Number(id)); + }, } \ No newline at end of file diff --git a/device.js b/device.js index b9e5efc..14e2377 100644 --- a/device.js +++ b/device.js @@ -26,7 +26,7 @@ class Device { this.y = newY; } - delete() { + delete(links) { let finished = false; do { finished = false diff --git a/link.js b/link.js index 9081713..f391eb7 100644 --- a/link.js +++ b/link.js @@ -6,30 +6,16 @@ class Link { this.device2 = dev2; this.aes50_1 = aes50_1; this.aes50_2 = aes50_2; - this.check(); } - /// Check if link is valable - check() { - links.forEach((link, index, fullArray) => { - // if link already on aes50 - if ((this.device1 == link.device1 && this.aes50_1 == link.aes50_1) || - (this.device1 == link.device2 && this.aes50_1 == link.aes50_2) || - (this.device2 == link.device1 && this.aes50_2 == link.aes50_1) || - (this.device2 == link.device2 && this.aes50_2 == link.aes50_2)) { - links.splice(index, 1); - } - }); - } - - show() { + show(devices) { let x1offset = this.aes50_1 == Constants.AES50.A ? 58 : 78; let x2offset = this.aes50_2 == Constants.AES50.A ? 58 : 78; //return ""; - return ""; } } diff --git a/preload.js b/preload.js index f79b000..30ec5eb 100644 --- a/preload.js +++ b/preload.js @@ -23,8 +23,17 @@ let selectedElement = null; let originX, originY, mouseX, mouseY; let idCnt = 0; -function id2index(id, list) { - return list.findIndex((element) => Number(element.id) === Number(id)); +/// Check if link is valable +function check(links) { + links.forEach((link, index, fullArray) => { + // if link already on aes50 + if ((this.device1 == link.device1 && this.aes50_1 == link.aes50_1) || + (this.device1 == link.device2 && this.aes50_1 == link.aes50_2) || + (this.device2 == link.device1 && this.aes50_2 == link.aes50_1) || + (this.device2 == link.device2 && this.aes50_2 == link.aes50_2)) { + links.splice(index, 1); + } + }); } function selectTopDiv(ele) { @@ -53,7 +62,7 @@ function draw() { function drawLine() { document.getElementById("lines").innerHTML = ""; links.forEach(link => { - document.getElementById("lines").innerHTML += link.show(); + document.getElementById("lines").innerHTML += link.show(devices); }) } @@ -84,7 +93,7 @@ function enableClick(ele) { function enableTextBox(parent, ele) { ele.addEventListener("keyup", (ev) => { deviceId = ev.target.parentElement.id; - devices[id2index(deviceId, devices)].name = ev.target.value; + devices[Constants.id2index(deviceId, devices)].name = ev.target.value; }); } @@ -95,13 +104,13 @@ window.addEventListener("mousemove", (ev) => { fromID = selectedElement.parentElement.id fromAES50 = selectedElement.classList[1] xoffset = fromAES50 == Constants.AES50.A ? 58 : 78; - document.getElementById("lines").innerHTML = ""; links.forEach(link => { - document.getElementById("lines").innerHTML += link.show(); + document.getElementById("lines").innerHTML += link.show(devices); }) } else if (selectedElement.classList.contains('device')) { @@ -111,7 +120,7 @@ window.addEventListener("mousemove", (ev) => { if (Sy < 0) Sy = 0; selectedElement.style.top = Math.round(Sy / 10) * 10 + "px"; selectedElement.style.left = Math.round(Sx / 10) * 10 + "px"; - index = id2index(selectedElement.id, devices); + index = Constants.id2index(selectedElement.id, devices); devices[index].move(parseInt(selectedElement.style.left, 10), parseInt(selectedElement.style.top, 10)); drawLine() } @@ -123,12 +132,13 @@ window.addEventListener("mouseup", (ev) => { toID = target.parentElement.id if (fromID != toID) { - links.push(new Link(devices[id2index(fromID, devices)].id, selectedElement.classList[1], devices[id2index(toID, devices)].id, target.classList[1])) + links.push(new Link(devices[Constants.id2index(fromID, devices)].id, selectedElement.classList[1], devices[Constants.id2index(toID, devices)].id, target.classList[1])) + check(links); } drawLine(); } else if (selectedElement.classList.contains('device')) { - index = id2index(selectedElement.id, devices); + index = Constants.id2index(selectedElement.id, devices); devices[index].move(parseInt(selectedElement.style.left, 10), parseInt(selectedElement.style.top, 10)); drawLine() } @@ -138,15 +148,15 @@ window.addEventListener("mouseup", (ev) => { function enableDoubleClick(ele) { ele.ondblclick = function (ev) { current = selectTopDiv(ev.target); - ipcRenderer.send('window', devices[current.id].type); + ipcRenderer.send('window', devices[Constants.id2index(current.id, devices)]); } } function enableRightClick(ele) { ele.oncontextmenu = function (ev) { current = selectTopDiv(ev.target); - devices[id2index(current.id, devices)].delete(); - devices.splice(id2index(current.id, devices), 1); + devices[Constants.id2index(current.id, devices)].delete(links); + devices.splice(Constants.id2index(current.id, devices), 1); draw(); } } @@ -181,6 +191,7 @@ ipcRenderer.on('file', (event, arg) => { }); arg.links.forEach(link => { links.push(new Link(link.device1, link.aes50_1, link.device2, link.aes50_2)); + check(links); }) draw(); idCnt = devices[devices.length - 1].id + 1; From 89adcf282d89ef963e89b1c973a389b77008bf69 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:25:19 +0100 Subject: [PATCH 04/19] Add modal for io configuration --- device-detail-preload.js | 89 ++++++++++++++++++++++++++++++++++++- device-detail-renderer.js | 53 ++++++++++++++++++++++ device-detail.html | 48 +++++++++++++++++++- main.js | 3 ++ preload.js | 5 +++ style.css | 92 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 device-detail-renderer.js diff --git a/device-detail-preload.js b/device-detail-preload.js index 0dae2eb..c242b1c 100644 --- a/device-detail-preload.js +++ b/device-detail-preload.js @@ -1,7 +1,11 @@ const { contextBridge, ipcRenderer } = require('electron') const path = require('path') +const Connector = require('./connector.js') const Constants = require('./const.js') +var modal; +var overlay; +var closeModalBtn; function selectTopDiv(ele) { while (!ele.classList.contains('device') && ele.tagName != "INPUT") { @@ -10,6 +14,23 @@ function selectTopDiv(ele) { return ele; } +const openModal = function (io) { + modal.classList.remove("hidden"); + overlay.classList.remove("hidden"); + document.getElementById("modal-title").innerHTML = io.device.name + " - " + io.input + " " + io.port; + document.getElementById("channel-name").value = io.name; + $(document).find('#color-list').attr('data-selected', io.color); + selectColor(io.color); + selectIcon(io.icon); + $(document).find('#channel-phase').attr('checked', io.phaseInvert); + $(document).find('#channel-invert').attr('checked', io.colorInvert); +}; + +const closeModal = function () { + modal.classList.add("hidden"); + overlay.classList.add("hidden"); +}; + function enableDoubleClick(ele) { ele.ondblclick = function (ev) { current = selectTopDiv(ev.target); @@ -22,7 +43,73 @@ function enableRightClick(ele) { } } +function changeXLR(XLR, icon, color, name) { + connector = XLR.querySelector('#connector'); + circle = "" + connector.innerHTML = circle.outerHTML; + // TODO: Save channel values in database + //https://www.svgviewer.dev/ +} + +function selectColor(id) { + let $selected = $(document).find('#color-list').find(':button[value="' + id + '"]'); + let $selectedValue = $selected.val(); + let $icon = $selected.find('svg'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); +} + +function selectIcon(id) { + let $selected = $(document).find('#icon-list').find(':button[value="' + id + '"]'); + let $selectedValue = $selected.val(); + let $icon = $selected.find('object'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); +} + ipcRenderer.on('type', (event, arg) => { console.log(arg) - document.getElementById("canvas").innerHTML += "
"; + document.getElementById("canvas").innerHTML += "
"; + + var svg = document.getElementById("svg"); + console.log(svg) + svg.addEventListener("load", function (ev) { + var inputs = svg.contentDocument.getElementById("Inputs").childNodes; + inputs.forEach(input => { + input.addEventListener("click", function (ev) { + openModal(new Connector(arg, input.id.slice(5,7), "Input", "salut", "RD", true, "72", false, false)); + changeXLR(input); + }); + }); + var outputs = svg.contentDocument.getElementById("Outputs").childNodes; + outputs.forEach(output => { + output.addEventListener("click", function (ev) { + openModal(new Connector(arg, output.id.slice(5,7), "Output", "salut", "RD", true, "72", false, false)); + changeXLR(output); + }); + }); + }); +}); + +ipcRenderer.on('ready', (event, arg) => { + modal = document.querySelector(".modal"); + overlay = document.querySelector(".overlay"); + closeModalBtn = document.querySelector(".btn-close"); + + closeModalBtn.addEventListener("click", closeModal); + overlay.addEventListener("click", closeModal); }); \ No newline at end of file diff --git a/device-detail-renderer.js b/device-detail-renderer.js new file mode 100644 index 0000000..b844123 --- /dev/null +++ b/device-detail-renderer.js @@ -0,0 +1,53 @@ +window.$ = window.jQuery = require("jquery"); +const path = require('path'); +const Constants = require('./const.js') + +$(document).ready(function() { + // Create dropdowns + Constants.icons.forEach((icon, index, fullArray) => { + $('#icon-list').append( + "" + ) + }); + + Constants.colors.forEach((color, index, fullArray) => { + $('#color-list').append( + "" + ) + }); + + $(document).click(function() { + $('.dropdown-menu.show').removeClass('show'); + }); + + $('body').on('click','.trigger-dropdown', function(e){ + e.stopPropagation(); + $(this).closest('.dropdown-wrapper').find('.dropdown-menu').toggleClass('show'); + }); + + $('body').on('click','.dropdown-item', function(e){ + e.stopPropagation(); + let $selectedValue = $(this).val(); + let $icon = $(this).find('svg'); + if ($icon.length <= 0) { + $icon = $(this).find('object'); + } + let $text = $(this).find('span'); + let $btn = $(this).closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $(this).closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); + }); +}); \ No newline at end of file diff --git a/device-detail.html b/device-detail.html index dc2cce2..859deb6 100644 --- a/device-detail.html +++ b/device-detail.html @@ -1,8 +1,10 @@ + - Device detail
+ + + + \ No newline at end of file diff --git a/main.js b/main.js index 1503027..92bb411 100644 --- a/main.js +++ b/main.js @@ -71,6 +71,7 @@ const menuTemplate = [ }, { label: 'SD16', + accelerator: 'CmdOrCtrl+Shift+M', click: () => win.webContents.send('menu', 'sd16'), }, { @@ -156,6 +157,8 @@ var childWindow; ipcMain.on('window', (event, arg) => { createChildWindow("device-detail.html", "device-detail-preload.js") childWindow.webContents.send('type', arg) + childWindow.setTitle('Mixo • ' + arg.name); + childWindow.webContents.send('ready'); }) ipcMain.on('file', (event, arg) => { diff --git a/preload.js b/preload.js index 30ec5eb..271b917 100644 --- a/preload.js +++ b/preload.js @@ -104,8 +104,13 @@ window.addEventListener("mousemove", (ev) => { fromID = selectedElement.parentElement.id fromAES50 = selectedElement.classList[1] xoffset = fromAES50 == Constants.AES50.A ? 58 : 78; +<<<<<<< Updated upstream document.getElementById("lines").innerHTML = ""; diff --git a/style.css b/style.css index e4445d5..280ead3 100644 --- a/style.css +++ b/style.css @@ -59,6 +59,7 @@ body { .detail { width: 100% !important; height: 100% !important; + padding-top: 0px !important; } #canvas svg { @@ -73,4 +74,93 @@ body { .line { stroke-width: 3px; -} \ No newline at end of file +} + +/* MODAL STYLE */ +.modal { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.4rem; + width: 450px; + padding: 1.3rem; + min-height: 250px; + position: absolute; + top: 20%; + background-color: white; + border: 1px solid #ddd; + border-radius: 15px; + z-index: 2; + left: 50%; + translate: -50%; + opacity: 1; + transition: visibility 0s, opacity 1s ease-in-out; +} + +.modal .flex { + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal input { + padding: 0.7rem 1rem; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 0.9em; +} + +.modal p { + font-size: 0.9rem; + color: #777; + margin: 0.4rem 0 0.2rem; +} + +button { + cursor: pointer; + border: none; + font-weight: 600; +} + +.btn { + display: inline-block; + padding: 0.8rem 1.4rem; + font-weight: 700; + background-color: black; + color: white; + border-radius: 5px; + text-align: center; + font-size: 1em; +} + +.btn-open { + position: absolute; + bottom: 150px; +} + +.btn-close { + transform: translate(10px, -20px); + padding: 0.5rem 0.7rem; + background: #eee; + border-radius: 50%; +} + +.overlay { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(3px); + z-index: 1; + opacity: 1; + transition: visibility 0s, opacity 1s ease-in-out; +} + +.hidden { + display: none; + opacity: 0; +} \ No newline at end of file From f754f1f1a4debf0c090040a3c05a5b77994d95c1 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Tue, 19 Mar 2024 09:56:05 +0100 Subject: [PATCH 05/19] Simplify SVG --- public/assets/images/SD16.svg | 446 +++++++++++++++------------------- public/assets/images/SD8.svg | 360 +++++++++++++-------------- 2 files changed, 367 insertions(+), 439 deletions(-) diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index 1e133ee..7f95ee9 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -1,299 +1,255 @@ - + + - - - - - - + + + 1 + + + + + + - - - - - - - - - - + + 2 + + + + + + - - - - - - - - - - + + 3 + + + + + + - - - - - - - - - - + + 4 + + + + + + - - - - - - - - - - + + 5 + + + + + + - - - - - - - - - - + + 6 + + + + + + - - - - - - - - - - + + 7 + + + + + + - - - - - - - - - - + + 8 + + + + + + - - - - - - - - - - + + 9 + + + + + + - - - - - - - - - - + + 10 + + + + + + - - - - - - - - - - + + 11 + + + + + + - - - - - - - - - - + + 12 + + + + + + - - - - - - - - - - + + 13 + + + + + + - - - - - - - - - - + + 14 + + + + + + - - - - - - - - - - + + 15 + HI-Z + + + + + + - - - - - - - - - - - - - - - - - - - - + + 16 + HI-Z + + + + + + - - - - - - - - - - - + + + OUT 1 + + + + + - - - - - - - - - + + OUT 2 + + + + + - - - - - - - - - + + OUT 3 + + + + + - - - - - - - - - + + OUT 4 + + + + + - - - - - - - - - + + OUT 5 + + + + + - - - - - - - - - + + OUT 6 + + + + + - - - - - - - - - + + OUT 7 + + + + + - - - - - - - - - + + OUT 8 + + + + + - - - - diff --git a/public/assets/images/SD8.svg b/public/assets/images/SD8.svg index 17f79db..fb6e762 100644 --- a/public/assets/images/SD8.svg +++ b/public/assets/images/SD8.svg @@ -1,203 +1,175 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 1 + + + + + + + + + + 2 + + + + + + + + + + 3 + + + + + + + + + + 4 + + + + + + + + + + 5 + + + + + + + + + + 6 + + + + + + + + + + 7 + HI-Z + + + + + + + + + + 8 + HI-Z + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + OUT 1 + + + + + + + + + OUT 2 + + + + + + + + + OUT 3 + + + + + + + + + OUT 4 + + + + + + + + + OUT 5 + + + + + + + + + OUT 6 + + + + + + + + + OUT 7 + + + + + + + + + OUT 8 + + + + + + From c25745426972d8f5a094eef1e90b8a7809ac58c2 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:29:24 +0100 Subject: [PATCH 06/19] Custom dropdown select implementation --- style.css | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index 280ead3..c8eaee0 100644 --- a/style.css +++ b/style.css @@ -2,6 +2,7 @@ html, body { height: 100%; margin: 0; + font-family: sans-serif; } #canvas { @@ -103,7 +104,7 @@ body { justify-content: space-between; } -.modal input { +.modal input, .modal select { padding: 0.7rem 1rem; border: 1px solid #ddd; border-radius: 5px; @@ -163,4 +164,85 @@ button { .hidden { display: none; opacity: 0; -} \ No newline at end of file +} + +/* CUSTOM DROPDOWN */ +.dropdown-wrapper { + width: 100%; + height: 45px; + float: left; + position: relative; + font-size: 0.9em; +} + +.trigger-dropdown { + width: 100%; + height: 45px; + background-color: #fff; + border: 0; + padding: 0.7rem 1rem; + border: 1px solid #ddd; + border-radius: 5px; + transition: 0.2s ease-in; + cursor:pointer; +} + +.trigger-dropdown:hover { + background-color:#eee; +} + +.trigger-dropdown .fa-caret-down { + float: right; + line-height: 22px; +} + +.trigger-dropdown svg { + width: 22px; + float: left; + height: 22px; +} + +.dropdown-menu { + width: 100%; + display:none; + z-index: 1; + position: absolute; + left: 0; + top: 45px; + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + max-height: 650%; + overflow-y:scroll; + border: 1px solid #ddd; + border-radius: 5px; + flex-wrap: wrap; + gap: 0px; + background-color: #fff; +} + +.dropdown-menu.show { + display:flex; +} + +.dropdown-item svg { + width: 22px; + height: 22px; + float: left; + margin-right:10px; +} + +.dropdown-item { + width: 49.99%; + height: 45px; + line-height: 25px; + border: 0; + padding: 0.7rem 1rem; + cursor: pointer; + transition:0.2s ease-in; + background-color:#fff; + color: #5a616c; + text-align: left; +} + +.dropdown-item:hover { + background-color:#e5e5e5; +} From 538148f2e778df17462323208c492ba02deea1ea Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:44:11 +0100 Subject: [PATCH 07/19] Update dropdown select --- style.css | 82 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/style.css b/style.css index c8eaee0..6be3396 100644 --- a/style.css +++ b/style.css @@ -1,7 +1,8 @@ html, body { - height: 100%; margin: 0; + min-height: 100%; + overflow: hidden; font-family: sans-serif; } @@ -85,9 +86,9 @@ body { gap: 0.4rem; width: 450px; padding: 1.3rem; - min-height: 250px; + min-height: 200px; position: absolute; - top: 20%; + top: 15vh; background-color: white; border: 1px solid #ddd; border-radius: 15px; @@ -98,6 +99,10 @@ body { transition: visibility 0s, opacity 1s ease-in-out; } +#modal-title { + margin: 0px 0px 10px 0px; +} + .modal .flex { display: flex; align-items: center; @@ -120,7 +125,10 @@ body { button { cursor: pointer; border: none; - font-weight: 600; +} + +.buttons { + margin-top: 10px; } .btn { @@ -132,6 +140,14 @@ button { border-radius: 5px; text-align: center; font-size: 1em; + border: 2px solid black; + flex-grow: 3; +} + +.cancel { + background-color: white; + color: black; + flex-grow: 1 !important; } .btn-open { @@ -140,9 +156,8 @@ button { } .btn-close { - transform: translate(10px, -20px); + transform: translate(0px, -7px); padding: 0.5rem 0.7rem; - background: #eee; border-radius: 50%; } @@ -166,6 +181,17 @@ button { opacity: 0; } +/* Columns */ +.column-container { + display: flex; + width: 100%; + gap: 10px; +} + +.column { + flex-grow: 1; +} + /* CUSTOM DROPDOWN */ .dropdown-wrapper { width: 100%; @@ -173,6 +199,7 @@ button { float: left; position: relative; font-size: 0.9em; + flex-grow: 1; } .trigger-dropdown { @@ -185,6 +212,10 @@ button { border-radius: 5px; transition: 0.2s ease-in; cursor:pointer; + text-align: left; + display: flex; + gap: 10px; + align-items: center; } .trigger-dropdown:hover { @@ -198,19 +229,30 @@ button { .trigger-dropdown svg { width: 22px; + min-width: 22px; float: left; height: 22px; + min-height: 22px; +} + +.trigger-dropdown span { + float: left; + margin: 0px; + line-height: normal; + flex-grow: 1; + padding-top: 2px; } .dropdown-menu { - width: 100%; + width: calc(200% + 10px); display:none; z-index: 1; position: absolute; left: 0; top: 45px; box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3); - max-height: 650%; + max-height: calc(100vh - 30vh - 150px); + min-height: 100px; overflow-y:scroll; border: 1px solid #ddd; border-radius: 5px; @@ -218,6 +260,16 @@ button { gap: 0px; background-color: #fff; } + +.dropdown-menu button { + display: flex; + gap: 10px; + align-items: center; +} + +.right { + left: calc(-100% - 10px); +} .dropdown-menu.show { display:flex; @@ -225,17 +277,17 @@ button { .dropdown-item svg { width: 22px; + min-width: 22px; + min-height: 22px; height: 22px; - float: left; - margin-right:10px; } .dropdown-item { - width: 49.99%; + width: 33.33%; height: 45px; line-height: 25px; border: 0; - padding: 0.7rem 1rem; + padding: 0.7rem; cursor: pointer; transition:0.2s ease-in; background-color:#fff; @@ -243,6 +295,12 @@ button { text-align: left; } +.dropdown-item span { + margin: 0px; + line-height: normal; + padding-top: 2px; +} + .dropdown-item:hover { background-color:#e5e5e5; } From 0183ecca080139e0508c025b3e88d25ca84ca96a Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:54:51 +0100 Subject: [PATCH 08/19] Add icons submodule --- .gitmodules | 3 +++ public/assets/icons | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 public/assets/icons diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3a0cc0f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "public/assets/icons"] + path = public/assets/icons + url = https://github.com/mamarguerat/behringer-icons.git diff --git a/public/assets/icons b/public/assets/icons new file mode 160000 index 0000000..4734c80 --- /dev/null +++ b/public/assets/icons @@ -0,0 +1 @@ +Subproject commit 4734c805930edc106e9e9a28761ab13aa8d89b04 From 4b4510e526a07924a75402bd2f9b04c468d6212b Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:25:12 +0100 Subject: [PATCH 09/19] Show icon --- public/assets/icons | 2 +- style.css | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/public/assets/icons b/public/assets/icons index 4734c80..4b19b5c 160000 --- a/public/assets/icons +++ b/public/assets/icons @@ -1 +1 @@ -Subproject commit 4734c805930edc106e9e9a28761ab13aa8d89b04 +Subproject commit 4b19b5ccb0febdeb301a9860a5545599817121bc diff --git a/style.css b/style.css index 6be3396..993fde3 100644 --- a/style.css +++ b/style.css @@ -235,6 +235,14 @@ button { min-height: 22px; } +.trigger-dropdown .icon { + width: 22px; + min-width: 22px; + float: left; + height: 22px; + min-height: 22px; +} + .trigger-dropdown span { float: left; margin: 0px; @@ -281,6 +289,13 @@ button { min-height: 22px; height: 22px; } + +.dropdown-item .icon { + width: 22px; + min-width: 22px; + min-height: 22px; + height: 22px; +} .dropdown-item { width: 33.33%; From eba14abdee713f2d5753653b677cf60dd13a2328 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:16:43 +0100 Subject: [PATCH 10/19] Add io class --- io.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 io.js diff --git a/io.js b/io.js new file mode 100644 index 0000000..a1cb0bb --- /dev/null +++ b/io.js @@ -0,0 +1,16 @@ +class io { + constructor(device, port, input, name, color, colorInvert, icon, phaseInvert) { + this.device = device; + this.port = port; + this.input = input; + this.name = name.slice(0, 12); + this.color = color; + this.colorInvert = colorInvert; + this.icon = icon; + this.phaseInvert = phaseInvert; + } + + getColor() { + return this.color + this.colorInvert ? "i" : ""; + } +} \ No newline at end of file From 81e3e784a1a5a29c07903771e0664a9c295c48ff Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:55:26 +0100 Subject: [PATCH 11/19] Icon picker bug fixes --- io.js => connector.js | 9 ++++++--- preload.js | 5 ----- 2 files changed, 6 insertions(+), 8 deletions(-) rename io.js => connector.js (80%) diff --git a/io.js b/connector.js similarity index 80% rename from io.js rename to connector.js index a1cb0bb..e16a815 100644 --- a/io.js +++ b/connector.js @@ -1,5 +1,5 @@ -class io { - constructor(device, port, input, name, color, colorInvert, icon, phaseInvert) { +class Connector { + constructor(device, port, input, name, color, colorInvert, icon, pwr, phaseInvert) { this.device = device; this.port = port; this.input = input; @@ -7,10 +7,13 @@ class io { this.color = color; this.colorInvert = colorInvert; this.icon = icon; + this.pwr = pwr; this.phaseInvert = phaseInvert; } getColor() { return this.color + this.colorInvert ? "i" : ""; } -} \ No newline at end of file +} + +module.exports = Connector \ No newline at end of file diff --git a/preload.js b/preload.js index 271b917..30ec5eb 100644 --- a/preload.js +++ b/preload.js @@ -104,13 +104,8 @@ window.addEventListener("mousemove", (ev) => { fromID = selectedElement.parentElement.id fromAES50 = selectedElement.classList[1] xoffset = fromAES50 == Constants.AES50.A ? 58 : 78; -<<<<<<< Updated upstream document.getElementById("lines").innerHTML = ""; From a5dd19d3400d98709d87be36ef5d7d088d58aa9f Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:10:44 +0100 Subject: [PATCH 12/19] Refactor --- README.md | 1 + connector.js | 19 -- const.js | 37 --- device.js | 45 ---- index.html | 37 --- link.js | 23 -- package-lock.json | 78 +++--- package.json | 5 +- preload.js | 199 -------------- public/assets/icon.png | Bin 0 -> 32933 bytes public/assets/images/SD16.svg | 160 +++-------- .../images/Screenshot 2024-03-19 132902.png | Bin 0 -> 128178 bytes public/assets/images/rj45.svg | 6 + public/assets/images/xlr-m.svg | 7 + public/assets/images/xlr.svg | 16 ++ renderer.js | 17 -- device-detail.html => src/device-detail.html | 4 +- src/index.html | 31 +++ src/js/beans/connector.js | 60 +++++ src/js/beans/device.js | 62 +++++ src/js/beans/deviceTypeLUT.js | 41 +++ src/js/beans/link.js | 26 ++ src/js/const.js | 35 +++ src/js/ctrl/indexCtrl.js | 252 ++++++++++++++++++ .../js/device-detail-preload.js | 0 .../js/device-detail-renderer.js | 0 main.js => src/js/main.js | 173 +++++++++++- src/js/main/indexMain.js | 4 + src/js/preload.js | 35 +++ src/js/wrk/indexWrk.js | 167 ++++++++++++ style.css => src/styles/style.css | 13 +- 31 files changed, 998 insertions(+), 555 deletions(-) delete mode 100644 connector.js delete mode 100644 const.js delete mode 100644 device.js delete mode 100644 index.html delete mode 100644 link.js delete mode 100644 preload.js create mode 100644 public/assets/icon.png create mode 100644 public/assets/images/Screenshot 2024-03-19 132902.png create mode 100644 public/assets/images/rj45.svg create mode 100644 public/assets/images/xlr-m.svg create mode 100644 public/assets/images/xlr.svg delete mode 100644 renderer.js rename device-detail.html => src/device-detail.html (94%) create mode 100644 src/index.html create mode 100644 src/js/beans/connector.js create mode 100644 src/js/beans/device.js create mode 100644 src/js/beans/deviceTypeLUT.js create mode 100644 src/js/beans/link.js create mode 100644 src/js/const.js create mode 100644 src/js/ctrl/indexCtrl.js rename device-detail-preload.js => src/js/device-detail-preload.js (100%) rename device-detail-renderer.js => src/js/device-detail-renderer.js (100%) rename main.js => src/js/main.js (56%) create mode 100644 src/js/main/indexMain.js create mode 100644 src/js/preload.js create mode 100644 src/js/wrk/indexWrk.js rename style.css => src/styles/style.css (96%) diff --git a/README.md b/README.md index f66bcaf..57b72e3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ +

diff --git a/connector.js b/connector.js deleted file mode 100644 index e16a815..0000000 --- a/connector.js +++ /dev/null @@ -1,19 +0,0 @@ -class Connector { - constructor(device, port, input, name, color, colorInvert, icon, pwr, phaseInvert) { - this.device = device; - this.port = port; - this.input = input; - this.name = name.slice(0, 12); - this.color = color; - this.colorInvert = colorInvert; - this.icon = icon; - this.pwr = pwr; - this.phaseInvert = phaseInvert; - } - - getColor() { - return this.color + this.colorInvert ? "i" : ""; - } -} - -module.exports = Connector \ No newline at end of file diff --git a/const.js b/const.js deleted file mode 100644 index 2ef09a5..0000000 --- a/const.js +++ /dev/null @@ -1,37 +0,0 @@ -const path = require('path'); - -const publicPath = process.env.NODE_ENV === 'development' - ? './public' - : path.join(process.resourcesPath, 'public'); - -module.exports = { - imagesPath : path.join(publicPath, "assets", "images"), - iconsPath : path.join(publicPath, "assets", "icons", "svg"), - AES50: { - A: "A", - B: "B" - }, - icons : [ - "No icon", "Kick Back", "Kick Front", "Snare Top", "Snare Bottom", "High Tom", "Mid Tom", "Floor Tom", "Hi-Hat", "Ride", - "Drum Kit", "Cowbell", "Bongos", "Congas", "Tambourine", "Vibraphone", "Electric Bass", "Acoustic Bass", "Contrabass", "Les Paul Guitar", - "Ibanez Guitar", "Washburn Guitar", "Acoustic Guitar", "Bass Amp", "Guitar Amp", "Amp Cabinet", "Piano", "Organ", "Harpsichord", "Keyboard", - "Synthesizer 1", "Synthesizer 2", "Synthesizer 3", "Keytar", "Trumpet", "Trombone", "Saxophone", "Clarinet", "Violin", "Cello", - "Male Vocal", "Female Vocal", "Choir", "Hand Sign", "Talk A", "Talk B", "Large Diaphragm Mic", "Condenser Mic Left", "Condenser Mic Right", "Handheld Mic", - "Wireless Mic", "Podium Mic", "Headset Mic", "XLR Jack", "TRS Plug", "TRS Plug Left", "TRS Plug Right", "RCA Plug Left", "RCA Plug Right", "Reel to Reel", - "FX", "Computer", "Monitor Wedge", "Left Speaker", "Right Speaker", "Speaker Array", "Speaker on a Pole", "Amp Rack", "Controls", "Fader", - "MixBus", "Matrix", "Routing", "Smiley" - ], - colors : [ - {"Name": "Off", "Color": "#000000", "ID": "OFF"}, - {"Name": "Red", "Color": "#E72D2E", "ID": "RD"}, - {"Name": "Green", "Color": "#35e72d", "ID": "GN"}, - {"Name": "Yellow", "Color": "#FCF300", "ID": "YE"}, - {"Name": "Blue", "Color": "#0060FF", "ID": "BL"}, - {"Name": "Magenta", "Color": "#ED27AC", "ID": "MG"}, - {"Name": "Cyan", "Color": "#2DE0E7", "ID": "CY"}, - {"Name": "White", "Color": "#FFFFFF", "ID": "WH"} - ], - id2index : function id2index(id, list) { - return list.findIndex((element) => Number(element.id) === Number(id)); - }, -} \ No newline at end of file diff --git a/device.js b/device.js deleted file mode 100644 index 14e2377..0000000 --- a/device.js +++ /dev/null @@ -1,45 +0,0 @@ -const path = require('path'); -const Constants = require('./const.js'); - -class Device { - constructor(x, y, type, id, name) { - this.x = x; - this.y = y; - this.type = type; - this.id = id; - this.name = name; - } - - show() { - return "

" + - "
A
" + - "
B
" + - "
"; - } - - move(newX, newY) { - this.x = newX; - this.y = newY; - } - - delete(links) { - let finished = false; - do { - finished = false - links.forEach((link, index, fullArray) => { - // if link on the device to delete - if ((this.id == link.device1) || (this.id == link.device2)) { - finished = true; - links.splice(index, 1); // changes the array length, act as break in loop - } - }); - } - while (finished) - } -} - -module.exports = Device; \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 00cc9b0..0000000 --- a/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - Mixo - - - - -
- - -
- - - - \ No newline at end of file diff --git a/link.js b/link.js deleted file mode 100644 index f391eb7..0000000 --- a/link.js +++ /dev/null @@ -1,23 +0,0 @@ -const Constants = require('./const.js'); - -class Link { - constructor(dev1, aes50_1, dev2, aes50_2) { - this.device1 = dev1; - this.device2 = dev2; - this.aes50_1 = aes50_1; - this.aes50_2 = aes50_2; - } - - show(devices) { - let x1offset = this.aes50_1 == Constants.AES50.A ? 58 : 78; - let x2offset = this.aes50_2 == Constants.AES50.A ? 58 : 78; - //return ""; - return ""; - } -} - -module.exports = Link diff --git a/package-lock.json b/package-lock.json index 49f8eea..f19754d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "Mixo", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Mixo", - "version": "0.0.4", + "version": "0.0.5", "license": "GPL-3.0", "dependencies": { "@electron/remote": "^2.1.2", + "about-window": "^1.15.2", "electron-squirrel-startup": "^1.0.0", "electron-updater": "^6.1.8", "fs": "^0.0.1-security", @@ -981,9 +982,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dependencies": { "undici-types": "~5.26.4" } @@ -1008,9 +1009,9 @@ } }, "node_modules/@types/verror": { - "version": "1.10.9", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.9.tgz", - "integrity": "sha512-MLx9Z+9lGzwEuW16ubGeNkpBDE84RpB/NyGgg6z2BTpWzKkGU451cAY3UkUzZEp72RHF585oJ3V8JVNqIplcAQ==", + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", + "integrity": "sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg==", "dev": true, "optional": true }, @@ -1044,6 +1045,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/about-window": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/about-window/-/about-window-1.15.2.tgz", + "integrity": "sha512-31mDAnLUfKm4uShfMzeEoS6a3nEto2tUt4zZn7qyAKedaTV4p0dGiW1n+YG8vtRh78mZiewghWJmoxDY+lHyYg==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1879,16 +1885,16 @@ } }, "node_modules/config-file-ts/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -1901,9 +1907,9 @@ } }, "node_modules/config-file-ts/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2174,9 +2180,9 @@ "dev": true }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, "engines": { "node": ">=8" @@ -2300,9 +2306,9 @@ } }, "node_modules/electron": { - "version": "29.1.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-29.1.0.tgz", - "integrity": "sha512-giJVIm0sWVp+8V1GXrKqKTb+h7no0P3ooYqEd34AD9wMJzGnAeL+usj+R0155/0pdvvP1mgydnA7lcaFA2M9lw==", + "version": "29.1.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-29.1.6.tgz", + "integrity": "sha512-UIYfpHR9gRBFKHyejHuXUVQ7nNzZRnoPVOHlijkvqR+DSLwgJ2ZcVVt0LNduNeO8PhPkY1+6kHonL52OTC1cOw==", "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -3643,9 +3649,9 @@ "dev": true }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "devOptional": true, "dependencies": { "function-bind": "^1.1.2" @@ -5100,12 +5106,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -6105,9 +6111,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -6300,9 +6306,9 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 30b23af..b775f2f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "Mixo", "version": "0.0.5", - "description": "Routing tool for behringer x32 based on hardware availabilities instead of software capabilities. Aim to simplify routing and create automatic documentation. ", - "main": "main.js", + "description": "Routing tool for behringer x32 based on hardware availabilities instead of software capabilities. Aim to simplify routing and create automatic documentation.", + "main": "src/js/main.js", "scripts": { "start": "electron-forge start", "package": "electron-forge package", @@ -43,6 +43,7 @@ }, "dependencies": { "@electron/remote": "^2.1.2", + "about-window": "^1.15.2", "electron-squirrel-startup": "^1.0.0", "electron-updater": "^6.1.8", "fs": "^0.0.1-security", diff --git a/preload.js b/preload.js deleted file mode 100644 index 30ec5eb..0000000 --- a/preload.js +++ /dev/null @@ -1,199 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron'); -const Device = require('./device.js') -const Link = require('./link.js') -const Constants = require('./const.js') - -// Uncomment for npm start command -// process.env.NODE_ENV = 'development' - -/* -contextBridge.exposeInMainWorld('versions', { - node: () => process.versions.node, - chrome: () => process.versions.chrome, - electron: () => process.versions.electron, - ping: () => ipcRenderer.invoke('ping'), - // we can also expose variables, not just functions -}) -*/ - -let devices = []; -// TODO: Share variables with modules -let links = []; -let selectedElement = null; -let originX, originY, mouseX, mouseY; -let idCnt = 0; - -/// Check if link is valable -function check(links) { - links.forEach((link, index, fullArray) => { - // if link already on aes50 - if ((this.device1 == link.device1 && this.aes50_1 == link.aes50_1) || - (this.device1 == link.device2 && this.aes50_1 == link.aes50_2) || - (this.device2 == link.device1 && this.aes50_2 == link.aes50_1) || - (this.device2 == link.device2 && this.aes50_2 == link.aes50_2)) { - links.splice(index, 1); - } - }); -} - -function selectTopDiv(ele) { - while (ele.tagName != "BODY" && !ele.classList.contains('device') && !ele.classList.contains('AES50') && ele.tagName != "INPUT") { - ele = ele.parentElement; - } - return ele; -} - -function draw() { - document.getElementById("canvas").innerHTML = ""; - devices.forEach(device => { - document.getElementById("canvas").innerHTML += device.show(); - }) - drawLine(); - - var ele = document.getElementsByClassName("device"); - for (var i = 0; i < ele.length; i++) { - enableClick(ele[i]); - enableDoubleClick(ele[i]); - enableRightClick(ele[i]); - enableTextBox(ele[i], ele[i].children[3]); - } -} - -function drawLine() { - document.getElementById("lines").innerHTML = ""; - links.forEach(link => { - document.getElementById("lines").innerHTML += link.show(devices); - }) -} - -function enableClick(ele) { - ele.addEventListener("mousedown", (ev) => { - element = selectTopDiv(ev.target) - if (element.classList.contains('AES50')) { - selectedElement = element; - fromID = selectedElement.parentElement.id - fromAES50 = selectedElement.classList[1] - links.forEach((link, index, fullArray) => { - if ((link.device1 == fromID && link.aes50_1 == fromAES50) || - (link.device2 == fromID && link.aes50_2 == fromAES50)) { - links.splice(index, 1); - } - }); - } - else if (element.classList.contains('device')) { - selectedElement = element; // Select element - originX = selectedElement.offsetLeft; - originY = selectedElement.offsetTop; - mouseX = ev.clientX; - mouseY = ev.clientY; - } - }); -} - -function enableTextBox(parent, ele) { - ele.addEventListener("keyup", (ev) => { - deviceId = ev.target.parentElement.id; - devices[Constants.id2index(deviceId, devices)].name = ev.target.value; - }); -} - -window.addEventListener("mousemove", (ev) => { - drawLine() - if (selectedElement == null) return; - if (selectedElement.classList.contains('AES50')) { - fromID = selectedElement.parentElement.id - fromAES50 = selectedElement.classList[1] - xoffset = fromAES50 == Constants.AES50.A ? 58 : 78; - document.getElementById("lines").innerHTML = ""; - links.forEach(link => { - document.getElementById("lines").innerHTML += link.show(devices); - }) - } - else if (selectedElement.classList.contains('device')) { - var Sx = ev.clientX - mouseX + originX, - Sy = ev.clientY - mouseY + originY; - if (Sx < 0) Sx = 0; - if (Sy < 0) Sy = 0; - selectedElement.style.top = Math.round(Sy / 10) * 10 + "px"; - selectedElement.style.left = Math.round(Sx / 10) * 10 + "px"; - index = Constants.id2index(selectedElement.id, devices); - devices[index].move(parseInt(selectedElement.style.left, 10), parseInt(selectedElement.style.top, 10)); - drawLine() - } -}); - -window.addEventListener("mouseup", (ev) => { - if (selectedElement.classList.contains('AES50')) { - target = selectTopDiv(ev.target) - toID = target.parentElement.id - if (fromID != toID) - { - links.push(new Link(devices[Constants.id2index(fromID, devices)].id, selectedElement.classList[1], devices[Constants.id2index(toID, devices)].id, target.classList[1])) - check(links); - } - drawLine(); - } - else if (selectedElement.classList.contains('device')) { - index = Constants.id2index(selectedElement.id, devices); - devices[index].move(parseInt(selectedElement.style.left, 10), parseInt(selectedElement.style.top, 10)); - drawLine() - } - selectedElement = null; // Unselect element -}); - -function enableDoubleClick(ele) { - ele.ondblclick = function (ev) { - current = selectTopDiv(ev.target); - ipcRenderer.send('window', devices[Constants.id2index(current.id, devices)]); - } -} - -function enableRightClick(ele) { - ele.oncontextmenu = function (ev) { - current = selectTopDiv(ev.target); - devices[Constants.id2index(current.id, devices)].delete(links); - devices.splice(Constants.id2index(current.id, devices), 1); - draw(); - } -} - -ipcRenderer.on('menu', (event, arg) => { - devices.push(new Device(10, 10, arg, idCnt++, arg)); - // if (devices.length == 2) links.push(new Link(devices[0], AES50.A, devices[1], AES50.A)); - // if (devices.length == 3) links.push(new Link(devices[1], AES50.B, devices[2], AES50.A)); - - draw(); -}); - -ipcRenderer.on('file', (event, arg) => { - if ('save' == arg.function || 'saveas' == arg.function) { - // Combine the arrays into an object - let data = { - devices: devices, - links: links - }; - // Convert the object to JSON - let json = JSON.stringify(data, null, 2); - ipcRenderer.send('file', { - function: arg.function, - json: json - }); - } - else if ('load' == arg.function) { - devices = []; - links = []; - arg.devices.forEach(device => { - devices.push(new Device(device.x, device.y, device.type, device.id, device.name)); - }); - arg.links.forEach(link => { - links.push(new Link(link.device1, link.aes50_1, link.device2, link.aes50_2)); - check(links); - }) - draw(); - idCnt = devices[devices.length - 1].id + 1; - } -}) \ No newline at end of file diff --git a/public/assets/icon.png b/public/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa4f9a5ca79aef8b9fdecc26a2ee56af705fc25 GIT binary patch literal 32933 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 literal 0 HcmV?d00001 diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index 7f95ee9..2cb04c6 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -1,15 +1,17 @@ - + @@ -18,163 +20,105 @@ 1 - - - - - + + + 2 - - - - - + 3 - - - - - + 4 - - - - - + 5 - - - - - + 6 - - - - - + + + 7 - - - - - + 8 - - - - - + 9 - - - - - + 10 - - - - - + 11 - - - - - + 12 - - - - - + 13 - - - - - + 14 - - - - - + 15 HI-Z - - - - - + 16 HI-Z - - - - - + + + @@ -182,73 +126,51 @@ OUT 1 - - - - + OUT 2 - - - - + OUT 3 - - - - + OUT 4 - - - - + OUT 5 - - - - + OUT 6 - - - - + OUT 7 - - - - + OUT 8 - - - - + + + @@ -256,20 +178,16 @@ - - + - - + - - + - - + \ No newline at end of file diff --git a/public/assets/images/Screenshot 2024-03-19 132902.png b/public/assets/images/Screenshot 2024-03-19 132902.png new file mode 100644 index 0000000000000000000000000000000000000000..0753ea78b2dca4b315999ed007dbad1ab346171b GIT binary patch literal 128178 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 AH2?qr literal 0 HcmV?d00001 diff --git a/public/assets/images/rj45.svg b/public/assets/images/rj45.svg new file mode 100644 index 0000000..88078e8 --- /dev/null +++ b/public/assets/images/rj45.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/assets/images/xlr-m.svg b/public/assets/images/xlr-m.svg new file mode 100644 index 0000000..cc7af05 --- /dev/null +++ b/public/assets/images/xlr-m.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/assets/images/xlr.svg b/public/assets/images/xlr.svg new file mode 100644 index 0000000..68bb002 --- /dev/null +++ b/public/assets/images/xlr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/renderer.js b/renderer.js deleted file mode 100644 index fed3b1b..0000000 --- a/renderer.js +++ /dev/null @@ -1,17 +0,0 @@ -//const information = document.getElementById('info') -//information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})` - -const func = async () => { - const response = await window.versions.ping() - console.log(response) -} - -function resizeInput() { - $(this).attr('size', $(this).val().length); -} - -$('input[type="text"]') - // event handler - .keyup(resizeInput) - // resize on page load - .each(resizeInput); \ No newline at end of file diff --git a/device-detail.html b/src/device-detail.html similarity index 94% rename from device-detail.html rename to src/device-detail.html index 859deb6..91c401a 100644 --- a/device-detail.html +++ b/src/device-detail.html @@ -4,7 +4,7 @@ - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/js/beans/connector.js b/src/js/beans/connector.js new file mode 100644 index 0000000..a8297e0 --- /dev/null +++ b/src/js/beans/connector.js @@ -0,0 +1,60 @@ +class Connector { + constructor() { + this._name = ""; + this._color = "OFF"; + this._colorInvert = false; + this._icon = "01"; + } + + // MARK: RW properties + /*----- Read/Write properties ---------------------------------------------------------------------------------------------*/ + + setName(name) { + this._name = name.slice(0, 12); + } + + getName() { + return this._name; + } + + setColor(color) { + this._color = color; + } + + getColor(color) { + this._color = color; + } + + setColorInvert(colorInvert) { + this._colorInvert = colorInvert; + } + + getColorInvert() { + return this._colorInvert; + } + + setColor(color, invert) { + this._color = color; + this._colorInvert = invert; + } + + getColorText() { + return this._color + this._colorInvert ? "i" : ""; + } + + setPhantomPower(pwr) { + this._pwr = pwr; + } + + getPhantomPower() { + return this._pwr; + } + + setPhaseInvert(phaseInvert) { + this._phaseInvert = phaseInvert; + } + + getPhaseInvert() { + return this._phaseInvert; + } +} diff --git a/src/js/beans/device.js b/src/js/beans/device.js new file mode 100644 index 0000000..09caee1 --- /dev/null +++ b/src/js/beans/device.js @@ -0,0 +1,62 @@ +class Device { + constructor(type, id, totalInput, totalOutput) { + this.x = 10; + this.y = 10; + this._type = type; + this._id = id; + this.name = type; + this.inputs = []; + this.outputs = []; + + // Create array of inputs + for (let i = 0; i < totalInput; i++) { + let connector = new Connector(); + connector.setPhantomPower(false); + connector.setPhaseInvert(false); + this.inputs.push(connector); + } + // Create array of outputs + for (let i = 0; i < totalOutput; i++) { + this.outputs.push(new Connector()); + } + } + + // MARK: RO properties + /*----- Read only properties ----------------------------------------------------------------------------------------------*/ + + getType() { + return this._type; + } + + getId() { + return this._id; + } + + // MARK: RW properties + /*----- Read/Write properties ---------------------------------------------------------------------------------------------*/ + + setPos(x, y) { + this.x = x; + this.y = y; + } + + getPos() { + return [this.x, this.y]; + } + + getPosX() { + return this.x; + } + + getPosY() { + return this.y; + } + + setName(name) { + this.name = name; + } + + getName() { + return this.name; + } +} diff --git a/src/js/beans/deviceTypeLUT.js b/src/js/beans/deviceTypeLUT.js new file mode 100644 index 0000000..c545033 --- /dev/null +++ b/src/js/beans/deviceTypeLUT.js @@ -0,0 +1,41 @@ +class DeviceTypeLUT { + constructor() { + this._deviceInfo = [ + /* ----- Mixers ----- */ + { Type: "Mixer", ID: "x32c", Inputs: 16, Outputs: 8, Brand: "Behringer", FullName: "X32 Compact" }, + /* ----- Stage boxes ----- */ + { Type: "Stage Box", ID: "sd16", Inputs: 16, Outputs: 8, Brand: "Behringer", FullName: "SD16" }, + { Type: "Stage Box", ID: "sd8", Inputs: 8, Outputs: 8, Brand: "Behringer", FullName: "SD8" }, + ]; + } + + getInputsCnt(type) { + let inputCnt = this._deviceInfo.filter((device) => (device.ID === type))[0].Inputs; + console.log("[deviceTypeLUT] Input Count for " + type + " is " + inputCnt); + return inputCnt; + } + + getOutputsCnt(type) { + let outputCnt = this._deviceInfo.filter((device) => (device.ID === type))[0].Outputs; + console.log("[deviceTypeLUT] Output Count for " + type + " is " + outputCnt); + return outputCnt; + } + + getIoCnt(type) { + return [this.getInputsCnt(type), this.getOutputsCnt(type)]; + } + + getBrandMixers(brand) { + return this._deviceInfo.filter((device) => + (device.Type === "Mixer") && (device.Brand === brand) + ); + } + + getBrandStageBoxes(brand) { + return this._deviceInfo.filter((device) => + (device.Type === "Stage Box") && (device.Brand === brand) + ); + } +} + +/*----- Private functions ---------------------------------------------------------------------------------------------------*/ diff --git a/src/js/beans/link.js b/src/js/beans/link.js new file mode 100644 index 0000000..4b1e31f --- /dev/null +++ b/src/js/beans/link.js @@ -0,0 +1,26 @@ +class Link { + constructor(dev1, aes50_1, dev2, aes50_2) { + this._device1 = dev1; + this._device2 = dev2; + this._aes50_1 = aes50_1; + this._aes50_2 = aes50_2; + } + + // MARK: RO properties + /*----- Read only properties ----------------------------------------------------------------------------------------------*/ + getFromDeviceId() { + return this._device1; + } + + getToDeviceId() { + return this._device2; + } + + getFromAes50() { + return this._aes50_1; + } + + getToAes50() { + return this._aes50_2; + } +} diff --git a/src/js/const.js b/src/js/const.js new file mode 100644 index 0000000..2437a47 --- /dev/null +++ b/src/js/const.js @@ -0,0 +1,35 @@ +const path = require('path'); +const publicPath = process.env.NODE_ENV === 'development' + ? '../public' + : path.join(process.resourcesPath, 'public'); + +class Const { + constructor() { + this.imagesPath = path.join(publicPath, "assets", "images"); + this.iconsPath = path.join(publicPath, "assets", "icons", "svg"); + this.AES50 = { + A: "A", + B: "B" + }; + this.icons = [ + "No icon", "Kick Back", "Kick Front", "Snare Top", "Snare Bottom", "High Tom", "Mid Tom", "Floor Tom", "Hi-Hat", "Ride", + "Drum Kit", "Cowbell", "Bongos", "Congas", "Tambourine", "Vibraphone", "Electric Bass", "Acoustic Bass", "Contrabass", "Les Paul Guitar", + "Ibanez Guitar", "Washburn Guitar", "Acoustic Guitar", "Bass Amp", "Guitar Amp", "Amp Cabinet", "Piano", "Organ", "Harpsichord", "Keyboard", + "Synthesizer 1", "Synthesizer 2", "Synthesizer 3", "Keytar", "Trumpet", "Trombone", "Saxophone", "Clarinet", "Violin", "Cello", + "Male Vocal", "Female Vocal", "Choir", "Hand Sign", "Talk A", "Talk B", "Large Diaphragm Mic", "Condenser Mic Left", "Condenser Mic Right", "Handheld Mic", + "Wireless Mic", "Podium Mic", "Headset Mic", "XLR Jack", "TRS Plug", "TRS Plug Left", "TRS Plug Right", "RCA Plug Left", "RCA Plug Right", "Reel to Reel", + "FX", "Computer", "Monitor Wedge", "Left Speaker", "Right Speaker", "Speaker Array", "Speaker on a Pole", "Amp Rack", "Controls", "Fader", + "MixBus", "Matrix", "Routing", "Smiley" + ]; + this.colors = [ + { Name: "Off", Color: "#000000", ID: "OFF" }, + { Name: "Red", Color: "#E72D2E", ID: "RD" }, + { Name: "Green", Color: "#35e72d", ID: "GN" }, + { Name: "Yellow", Color: "#FCF300", ID: "YE" }, + { Name: "Blue", Color: "#0060FF", ID: "BL" }, + { Name: "Magenta", Color: "#ED27AC", ID: "MG" }, + { Name: "Cyan", Color: "#2DE0E7", ID: "CY" }, + { Name: "White", Color: "#FFFFFF", ID: "WH" } + ]; + } +} \ No newline at end of file diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js new file mode 100644 index 0000000..de926e1 --- /dev/null +++ b/src/js/ctrl/indexCtrl.js @@ -0,0 +1,252 @@ +const { ipcRenderer } = require("electron"); + +const constants = new Const(); +class IndexCtrl { + + // MARK: Constructor + constructor() { + /* ----- IPC ----- */ + ipcRenderer.on('menu', (event, arg) => { + this.menuClick(arg); + }) + /* ----- DOM Event Listeners ----- */ + window.addEventListener("mousemove", (e) => { + this.mouseMove(e); + }) + window.addEventListener("mouseup", (e) => { + this.mouseUp(e); + }) + $('#canvas').on('mousedown', '.AES50', (e) => { + this.AES50Select(e); + }) + $('#canvas').on('mousedown', '.deviceimg', (e) => { + this.deviceSelect(e); + }) + $('#canvas').on('contextmenu', '.deviceimg', (e) => { + this.deleteDevice(e); + }); + $('#canvas').on('dblclick', '.deviceimg', (e) => { + this.openDeviceDetail(e); + }); + $('#canvas').on('keyup', (e) => { + this.saveText(e); + }) + this.selectedElement = {}; + } + + // MARK: Event handling + /** + * Mouse up event handling + * @param {Event} e + */ + mouseUp(e) { + if (this.selectedElement.Type === "AES50") { + let id = e.target.parentElement.getAttribute('deviceid'); + let Aes50Cible = e.target.getAttribute('aes50'); + $('#currentline').remove(); + if (null != Aes50Cible) { + indexWrk.addLink(this.selectedElement.Id, this.selectedElement.Aes50Source, id, Aes50Cible); + } + } + else if (this.selectedElement.Type === "Device") { + console.log(`new x ${this.selectedElement.NewX} new y ${this.selectedElement.NewY}`) + indexWrk.moveDeviceId(this.selectedElement.Id, this.selectedElement.NewX, this.selectedElement.NewY); + } + this.selectedElement = {}; + } + + /** + * Mouse move event handling + * @param {Event} e + */ + mouseMove(e) { + // directly change DOM for selected element, save in Worker on mouseup + if (this.selectedElement.Type === "AES50") { + // AES50 selected + $('#currentline').attr('d', + 'M' + (this.selectedElement.FromX + this.selectedElement.Xoffset) + ',' + this.selectedElement.FromY + + ' C ' + (this.selectedElement.FromX + this.selectedElement.Xoffset) + ',' + (this.selectedElement.FromY - 80) + + ' ' + (e.clientX + 0) + ',' + (e.clientY - 80) + + ' ' + (e.clientX + 0) + ',' + e.clientY + ); + } + else if (this.selectedElement.Type === "Device") { + // Device selected + this.selectedElement.NewX = e.clientX - this.selectedElement.MouseX + this.selectedElement.Left; + this.selectedElement.NewY = e.clientY - this.selectedElement.MouseY + this.selectedElement.Top; + if (this.selectedElement.NewX < 0) { + this.selectedElement.NewX = 0; + } + else { + this.selectedElement.NewX = Math.round(this.selectedElement.NewX / 10) * 10; + } + if (this.selectedElement.NewY < 0) { + this.selectedElement.NewY = 0; + } + else { + this.selectedElement.NewY = Math.round(this.selectedElement.NewY / 10) * 10; + } + this.moveDevice(this.selectedElement.Id, this.selectedElement.NewX, this.selectedElement.NewY); + } + } + + deleteDevice(e) { + let id = $(e.target).parent().attr('deviceid'); + indexWrk.removeDeviceId(id); + this.selectedElement = {}; + } + + /** + * Open window with device detail + * @param {Event} e + */ + openDeviceDetail(e) { + //TODO: Open new window + } + + /** + * Save name of device on key up + * @param {Event} e + */ + saveText(e) { + let id = $(e.target).parent().attr('deviceid'); + let value = $(e.target).val(); + indexWrk.saveNameId(id, value); + } + + /** + * AES50 button mouse down + * @param {Event} e + */ + AES50Select(e) { + let id = $(e.target).parent().attr('deviceid'); + let aes50 = $(e.target).attr('aes50'); + let x, y; + let xoffset = aes50 == constants.AES50.A ? 58 : 78; + [x, y] = indexWrk.getDevicePosId(id); + this.selectedElement = { + Type: "AES50", + Id: id, + Aes50Source: aes50, + FromX: x, + FromY: y, + Xoffset: xoffset, + } + document.getElementById('lines').innerHTML += + ""; + } + + /** + * Device image mouse down + * @param {Event} e + */ + deviceSelect(e) { + console.log(e.target); + let parent = $(e.target).parent(); + let x = e.clientX; + let y = e.clientY; + let top = parent.css('top').replace(/[^0-9\.]+/g, '') | 0; + let left = parent.css('left').replace(/[^0-9\.]+/g, '') | 0; + this.selectedElement = { + Type: "Device", + Id: parent.attr('deviceid'), + Top: top, + Left: left, + NewX: left, + NewY: top, + MouseX: x, + MouseY: y, + }; + } + + // MARK: IPC events + /** + * Menu action received from IPC + * @param {*} arg + */ + menuClick(arg) { + console.log(`[indexCtrl] menu click ${arg.action}`); + if (arg.action === 'add') + { + console.log(`[indexCtrl] add device`); + indexWrk.addDevice(arg.type); + } + } + + // MARK: Functions + /** + * Refresh DOM + * @param {Device[]} devices + * @param {Link[]} links + */ + drawCanvas(devices, links) { + $('#canvas').empty(); + $('#canvas').append('') + if (devices.length > 0) { + this.drawDevices(devices); + } + if (links.length > 0) { + this.drawLines(links); + } + } + + /** + * Add links to DOM + * @param {links[]} links + */ + drawLines(links) { + console.log(`[indexCtrl] draw lines`); + $('#lines').empty(); + links.forEach(link => { + let x1, y1, x2, y2; + [x1, y1] = indexWrk.getDevicePosId(link.getFromDeviceId()); + x1 += link.getFromAes50() == constants.AES50.A ? 58 : 78; + [x2, y2] = indexWrk.getDevicePosId(link.getToDeviceId()); + x2 += link.getToAes50() == constants.AES50.A ? 58 : 78; + document.getElementById('lines').innerHTML += + ""; + }); + } + + /** + * Add devices to DOM + * @param {Device[]} devices + */ + drawDevices(devices) { + console.log(`[indexCtrl] draw devices`); + devices.forEach(device => { + $("#canvas").append( + "
" + + "
A
" + + "
B
" + + "
" + ); + }); + } + + /** + * Move a device in DOM and save new position + * @param {Number} id + * @param {Number} x + * @param {Number} y + */ + moveDevice(id, x, y) { + let $element = $('*[deviceid="' + id + '"]'); + $element.css('top', y + 'px'); + $element.css('left', x + 'px'); + indexWrk.moveDeviceId(id, x, y); + } +} diff --git a/device-detail-preload.js b/src/js/device-detail-preload.js similarity index 100% rename from device-detail-preload.js rename to src/js/device-detail-preload.js diff --git a/device-detail-renderer.js b/src/js/device-detail-renderer.js similarity index 100% rename from device-detail-renderer.js rename to src/js/device-detail-renderer.js diff --git a/main.js b/src/js/main.js similarity index 56% rename from main.js rename to src/js/main.js index 92bb411..7c65ad0 100644 --- a/main.js +++ b/src/js/main.js @@ -1,6 +1,8 @@ const { dialog, app, BrowserWindow, ipcMain, Menu } = require('electron'); -const path = require('path'); +const join = require('path').join; const fs = require('fs'); +const openAboutWindow = require('about-window').default; +const isMac = process.platform === 'darwin' let win let filePath = ""; @@ -11,8 +13,32 @@ app.on("ready", () => { autoUpdater.checkForUpdatesAndNotify(); }); +// MARK: Menu template const menuTemplate = [ - { role: 'appMenu' }, + // { role: 'appMenu' }, + ...(isMac + ? [ + { + label: app.name, + submenu: [ + { + label: 'About', + click: () => aboutWindow() + }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + } + ] + : []), + + // { role: 'fileMenu' } { label: 'File', submenu: [ @@ -38,7 +64,47 @@ const menuTemplate = [ accelerator: 'Shift+CmdOrCtrl+E', } ] - } + }, + { type: 'separator' }, + ...(isMac + ? [ + { role: 'close' } + ] + : [ + { role: 'quit' } + ] + ), + ] + }, + // { role: 'editMenu' } + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + ...(isMac + ? [ + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + { type: 'separator' }, + { + label: 'Speech', + submenu: [ + { role: 'startSpeaking' }, + { role: 'stopSpeaking' } + ] + } + ] + : [ + { role: 'delete' }, + { type: 'separator' }, + { role: 'selectAll' } + ]) ] }, { @@ -53,7 +119,7 @@ const menuTemplate = [ { label: 'X32 Compact', accelerator: 'CmdOrCtrl+M', - click: () => win.webContents.send('menu', 'x32c'), + click: () => win.webContents.send('menu', { action: 'add', type: 'x32c' }), }, { label: 'X32 Producer' @@ -67,12 +133,12 @@ const menuTemplate = [ { type: 'separator' }, { label: 'SD8', - click: () => win.webContents.send('menu', 'sd8'), + click: () => win.webContents.send('menu', { action: 'add', type: 'sd8' }), }, { label: 'SD16', accelerator: 'CmdOrCtrl+Shift+M', - click: () => win.webContents.send('menu', 'sd16'), + click: () => win.webContents.send('menu', { action: 'add', type: 'sd16' }), }, { label: 'S32' @@ -111,22 +177,70 @@ const menuTemplate = [ } ] }, - { role: 'viewMenu' } + // { role: 'windowMenu' } + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(isMac + ? [ + { type: 'separator' }, + { role: 'front' }, + { type: 'separator' }, + { role: 'window' } + ] + : [ + { role: 'close' } + ]) + ] + }, + ...(app.isPackaged + ? [] + : [{ role: 'viewMenu' }] + ), + { + role: 'help', + submenu: [ + ...(isMac + ? [] + : [ + { + label: 'About', + click: () => aboutWindow() + }, + ]), + { + label: 'Learn More', + click: async () => { + const { shell } = require('electron') + await shell.openExternal('https://martinmarguerat.ch') + } + }, + { + label: 'Report a bug', + click: async () => { + const { shell } = require('electron') + await shell.openExternal('https://github.com/mamarguerat/mixo/issues') + } + } + ] + }, ]; const menu = Menu.buildFromTemplate(menuTemplate) +// MARK: Create window const createWindow = () => { win = new BrowserWindow({ width: 1000, height: 700, webPreferences: { - preload: path.join(__dirname, 'preload.js'), nodeIntegration: true, contextIsolation: false, }, }); - ipcMain.handle('ping', () => 'pong') - win.loadFile('index.html'); + ipcMain.handle('ping', () => 'pong'); + win.loadFile(join(__dirname, '..', 'index.html')); }; app.whenReady().then(() => { @@ -147,15 +261,16 @@ app.whenReady().then(() => { }); app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { + // if (process.platform !== 'darwin') { app.quit(); - } + // } }); var childWindow; +// MARK: IPC events ipcMain.on('window', (event, arg) => { - createChildWindow("device-detail.html", "device-detail-preload.js") + createChildWindow(join("..", "device-detail.html"), "device-detail-preload.js") childWindow.webContents.send('type', arg) childWindow.setTitle('Mixo • ' + arg.name); childWindow.webContents.send('ready'); @@ -188,6 +303,8 @@ ipcMain.on('file', (event, arg) => { }) } }) + +// MARK: Functions function loadFile() { dialog.showOpenDialog({ title: 'Open Mixo project', @@ -226,11 +343,39 @@ function createChildWindow(fileName, preloadFileName) { menuBarVisible: false, autoHideMenuBar: true, webPreferences: { - preload: path.join(__dirname, preloadFileName), + preload: join(__dirname, preloadFileName), nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, } }) childWindow.loadFile(fileName) +} + +function aboutWindow() { + openAboutWindow({ + icon_path: ( + process.env.NODE_ENV === 'development' + ? '../../public/assets/icon.png' + : join(process.resourcesPath, 'public', 'assets', 'icon.png') + ), + package_json_dir: ( + process.env.NODE_ENV === 'development' + ? join(__dirname, '..', '..') + : process.resourcesPath + ), + win_options: { + parent: win, + modal: true, + titleBarStyle: "hidden", + movable: false, + resizable: false, + }, + css_path: join(__dirname, "..", "styles", "style.css"), + bug_link_text: "Report a bug", + product_name: "Mixo", + show_close_button: "Close", + adjust_window_size: true, + description: "Routing simplified", + }) } \ No newline at end of file diff --git a/src/js/main/indexMain.js b/src/js/main/indexMain.js new file mode 100644 index 0000000..9a92ee2 --- /dev/null +++ b/src/js/main/indexMain.js @@ -0,0 +1,4 @@ +$(document).ready(function () { + indexCtrl = new IndexCtrl(); + indexWrk = new IndexWrk(); +}); \ No newline at end of file diff --git a/src/js/preload.js b/src/js/preload.js new file mode 100644 index 0000000..b3bb818 --- /dev/null +++ b/src/js/preload.js @@ -0,0 +1,35 @@ +function enableDoubleClick(ele) { + ele.ondblclick = function (ev) { + current = selectTopDiv(ev.target); + ipcRenderer.send('window', devices[Constants.id2index(current.id, devices)]); + } +} + +ipcRenderer.on('file', (event, arg) => { + if ('save' == arg.function || 'saveas' == arg.function) { + // Combine the arrays into an object + let data = { + devices: devices, + links: links + }; + // Convert the object to JSON + let json = JSON.stringify(data, null, 2); + ipcRenderer.send('file', { + function: arg.function, + json: json + }); + } + else if ('load' == arg.function) { + devices = []; + links = []; + arg.devices.forEach(device => { + devices.push(new Device(device.x, device.y, device.type, device.id, device.name)); + }); + arg.links.forEach(link => { + links.push(new Link(link.device1, link.aes50_1, link.device2, link.aes50_2)); + check(links); + }) + draw(); + idCnt = devices[devices.length - 1].id + 1; + } +}) \ No newline at end of file diff --git a/src/js/wrk/indexWrk.js b/src/js/wrk/indexWrk.js new file mode 100644 index 0000000..51f61f7 --- /dev/null +++ b/src/js/wrk/indexWrk.js @@ -0,0 +1,167 @@ +class IndexWrk { + + // MARK: Constructor + constructor() { + this.devices = []; + this.links = []; + this.devTypeLUT = new DeviceTypeLUT(); + this.id = 0; + } + + // MARK: Functions + /** + * Add an empty device to the devices array + * @param {String} deviceType + */ + addDevice(deviceType) { + let inputCnt, outputCnt; + console.log(`[indexWrk] add device`); + [inputCnt, outputCnt] = this.devTypeLUT.getIoCnt(deviceType); + this.devices.push(new Device(deviceType, this.id++, inputCnt, outputCnt)); + indexCtrl.drawCanvas(this.devices, this.links); + } + + /** + * Add a link in the link array + * @param {Number} fromID + * @param {String} fromAES + * @param {Number} toID + * @param {String} toAES + */ + addLink(fromID, fromAES, toID, toAES) { + this.deleteConnectedAt(fromID, fromAES, toID, toAES); + if (fromID != toID && false == this.checkIfExists(fromID, fromAES, toID, toAES)) { + console.log(`[indexWrk] add link from id ${fromID} ${fromAES} to id ${toID} ${toAES}`); + this.links.push(new Link(fromID, fromAES, toID, toAES)); + } + console.log(this.links); + indexCtrl.drawCanvas(this.devices, this.links); + } + + /** + * Delete link already created in the selected AES ports + * @param {Number} fromID + * @param {String} fromAES + * @param {Number} toID + * @param {String} toAES + */ + deleteConnectedAt(fromID, fromAES, toID, toAES) { + // Do it twice to delete all 2 possible links + for (let idx = 0; idx < 2; idx++) { + this.links.forEach((link, index, fullArray) => { + // if link already on aes50 + console.log((fromID == link.getFromDeviceId() && fromAES == link.getFromAes50())); + console.log((fromID == link.getToDeviceId() && fromAES == link.getToAes50())); + console.log((toID == link.getFromDeviceId() && toAES == link.getFromAes50())); + console.log((toID == link.getToDeviceId() && toAES == link.getToAes50())); + if ((fromID == link.getFromDeviceId() && fromAES == link.getFromAes50()) || + (fromID == link.getToDeviceId() && fromAES == link.getToAes50()) || + (toID == link.getFromDeviceId() && toAES == link.getFromAes50()) || + (toID == link.getToDeviceId() && toAES == link.getToAes50())) { + fullArray.splice(index, 1); + } + }); + } + } + + /** + * Remove a device in the devices array from an ID + * @param {Number} id + */ + removeDeviceId(id) { + console.log(`[indexWrk] remove device with id ${id}`); + this.deleteConnectedAt(id, 'A', id, 'B'); + this.removeDeviceIndex(id2index(id, this.devices)); + indexCtrl.drawCanvas(this.devices, this.links); + } + + /** + * Remove a device in the devices array from an index + * @param {Number} index + */ + removeDeviceIndex(index) { + this.devices.splice(index, 1); + // TODO: remove links of this device + } + + /** + * Move a device in the devices array fom an id + * @param {Number} id + * @param {Number} x + * @param {Number} y + */ + moveDeviceId(id, x, y) { + console.log(`[indexWrk] move device with id ${id} to position x=${x} y=${y}`); + this.moveDeviceIndex(id2index(id, this.devices), x, y); + indexCtrl.drawLines(this.links); + } + + /** + * Move a device in the devices array fom an index + * @param {Number} id + * @param {Number} x + * @param {Number} y + */ + moveDeviceIndex(index, x, y) { + this.devices[index].setPos(x, y); + } + + saveNameId(id, name) { + console.log(`[indexWrk] save new name ${name}`); + this.saveNameIndex(id2index(id, this.devices), name); + } + + saveNameIndex(index, name) { + this.devices[index].setName(name); + } + + /** + * Get a device position in the devices array fom an id + * @param {Number} id + */ + getDevicePosId(id) { + return this.getDevicePosIndex(id2index(id, this.devices)); + } + + /** + * Get a device position in the devices array fom an index + * @param {Number} id + */ + + getDevicePosIndex(index) { + return this.devices[index].getPos(); + } + + /** + * Check if the link to create already exists on the database + * @param {Number} fromID + * @param {AES50} fromAES + * @param {Number} toID + * @param {AES50} toAES + * @returns + */ + checkIfExists(fromID, fromAES, toID, toAES) { + let existingLinks = this.links.filter((link) => + (link.getFromDeviceId() === fromID) && (link.getToDeviceId() === toID) && + (link.getFromAes50() === fromAES) && (link.getToAes50() === toAES) + ); + if (existingLinks.length > 0) { + return true; + } + else { + return false; + } + } +} + +// MARK: Private funcitons +/*----- Private functions ---------------------------------------------------------------------------------------------------*/ +/** + * Search an in an array and return the index (array[index].id) + * @param {Number} id + * @param {*} array + * @returns The ID in the array + */ +function id2index(id, array) { + return array.findIndex((element) => Number(element.getId()) === Number(id)); +} diff --git a/style.css b/src/styles/style.css similarity index 96% rename from style.css rename to src/styles/style.css index 993fde3..9246ced 100644 --- a/style.css +++ b/src/styles/style.css @@ -8,7 +8,7 @@ body { #canvas { min-width: 100%; - min-height: 100%; + min-height: 100vh; } .device { @@ -16,6 +16,7 @@ body { display: inline-block; position: absolute; padding-top: 5px; + user-select: none; } .device:hover { @@ -50,11 +51,15 @@ body { color: white; } -.x32c img { +.deviceimg object { + pointer-events: none; +} + +.x32c object { width: 260px; } -.sd16 img, .sd8 img { +.sd16 object, .sd8 object { width: 200px; } @@ -76,6 +81,8 @@ body { .line { stroke-width: 3px; + stroke: black; + fill: transparent; } /* MODAL STYLE */ From 0e62c6bc8f9b7f052105ecb2027ee95c8a5ac0b2 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:02:06 +0200 Subject: [PATCH 13/19] Share worker between windows --- package-lock.json | 20 +++++++++++++-- package.json | 3 ++- public/assets/images/SD16.svg | 8 +++--- public/assets/images/xlr-m.svg | 7 ------ src/device-detail.html | 19 ++++++++++----- src/js/ctrl/deviceDetailCtrl.js | 43 +++++++++++++++++++++++++++++++++ src/js/ctrl/indexCtrl.js | 3 +++ src/js/main.js | 14 +++++------ src/js/main/deviceDetailMain.js | 4 +++ src/js/main/indexMain.js | 2 +- src/js/wrk/indexWrk.js | 8 ++++++ 11 files changed, 102 insertions(+), 29 deletions(-) delete mode 100644 public/assets/images/xlr-m.svg create mode 100644 src/js/ctrl/deviceDetailCtrl.js create mode 100644 src/js/main/deviceDetailMain.js diff --git a/package-lock.json b/package-lock.json index f19754d..4836804 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "electron-squirrel-startup": "^1.0.0", "electron-updater": "^6.1.8", "fs": "^0.0.1-security", - "jquery": "^3.7.0" + "jquery": "^3.7.0", + "serialize-javascript": "^6.0.2" }, "devDependencies": { "@electron-forge/cli": "^6.0.5", @@ -5364,6 +5365,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rcedit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz", @@ -5709,7 +5718,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -5792,6 +5800,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", diff --git a/package.json b/package.json index b775f2f..cdc90f5 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "electron-squirrel-startup": "^1.0.0", "electron-updater": "^6.1.8", "fs": "^0.0.1-security", - "jquery": "^3.7.0" + "jquery": "^3.7.0", + "serialize-javascript": "^6.0.2" }, "build": { "extraResources": [ diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index 2cb04c6..486ee14 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -22,7 +22,7 @@ - + @@ -54,7 +54,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -170,7 +170,7 @@ - + diff --git a/public/assets/images/xlr-m.svg b/public/assets/images/xlr-m.svg deleted file mode 100644 index cc7af05..0000000 --- a/public/assets/images/xlr-m.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/device-detail.html b/src/device-detail.html index 91c401a..7da398b 100644 --- a/src/device-detail.html +++ b/src/device-detail.html @@ -7,11 +7,6 @@ -
@@ -58,5 +53,17 @@ - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js new file mode 100644 index 0000000..648faf4 --- /dev/null +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -0,0 +1,43 @@ +const { ipcRenderer } = require("electron"); + +const constants = new Const(); +class DeviceDetailCtrl { + + // MARK: Constructor + constructor() { + ipcRenderer.on('ready', (event, arg) => { + console.log(`[deviceDetailCtrl] window ready, load device ${arg.id}`); + this.initialize(arg); + }); + } + + // MARK: Event handling + + // MARK: IPC events + initialize(arg) { + deviceDetailWrk = this.reconstructObject(arg.worker); + console.log(deviceDetailWrk); + this.deviceID = arg.id; + this.drawCanvas(deviceDetailWrk.getDeviceFromId(this.deviceID)); + } + + // MARK: Functions + reconstructObject(worker) { + worker.addDevice = deviceDetailWrk.addDevice; + worker.addLink = deviceDetailWrk.addLink; + worker.getDeviceFromId = deviceDetailWrk.getDeviceFromId; + worker.getDeviceFromIndex = deviceDetailWrk.getDeviceFromIndex; + + return worker; + } + + drawCanvas(device) { + $('#canvas').empty(); + $('#canvas').append( + "
" + + "" + ) + } +} \ No newline at end of file diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js index de926e1..c9910bf 100644 --- a/src/js/ctrl/indexCtrl.js +++ b/src/js/ctrl/indexCtrl.js @@ -1,6 +1,7 @@ const { ipcRenderer } = require("electron"); const constants = new Const(); + class IndexCtrl { // MARK: Constructor @@ -102,6 +103,8 @@ class IndexCtrl { */ openDeviceDetail(e) { //TODO: Open new window + let id = $(e.target).parent().attr('deviceid'); + ipcRenderer.send('window', { id: id, worker: indexWrk }); } /** diff --git a/src/js/main.js b/src/js/main.js index 7c65ad0..f926d80 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -239,7 +239,6 @@ const createWindow = () => { contextIsolation: false, }, }); - ipcMain.handle('ping', () => 'pong'); win.loadFile(join(__dirname, '..', 'index.html')); }; @@ -270,11 +269,11 @@ var childWindow; // MARK: IPC events ipcMain.on('window', (event, arg) => { - createChildWindow(join("..", "device-detail.html"), "device-detail-preload.js") - childWindow.webContents.send('type', arg) - childWindow.setTitle('Mixo • ' + arg.name); - childWindow.webContents.send('ready'); -}) + createChildWindow(join(__dirname, '..', 'device-detail.html')); + childWindow.webContents.on('did-finish-load', () => { + childWindow.webContents.send('ready', arg); + }); +}); ipcMain.on('file', (event, arg) => { if ('saveas' == arg.function || ('save' == arg.function && filePath == "")) { @@ -336,14 +335,13 @@ function loadFile() { } // function to create a child window -function createChildWindow(fileName, preloadFileName) { +function createChildWindow(fileName) { childWindow = new BrowserWindow({ width: 700, height: 500, menuBarVisible: false, autoHideMenuBar: true, webPreferences: { - preload: join(__dirname, preloadFileName), nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, diff --git a/src/js/main/deviceDetailMain.js b/src/js/main/deviceDetailMain.js new file mode 100644 index 0000000..180d863 --- /dev/null +++ b/src/js/main/deviceDetailMain.js @@ -0,0 +1,4 @@ +$(document).ready(function () { + deviceDetailCtrl = new DeviceDetailCtrl(); + deviceDetailWrk = new IndexWrk(); +}); diff --git a/src/js/main/indexMain.js b/src/js/main/indexMain.js index 9a92ee2..c8268c6 100644 --- a/src/js/main/indexMain.js +++ b/src/js/main/indexMain.js @@ -1,4 +1,4 @@ $(document).ready(function () { indexCtrl = new IndexCtrl(); indexWrk = new IndexWrk(); -}); \ No newline at end of file +}); diff --git a/src/js/wrk/indexWrk.js b/src/js/wrk/indexWrk.js index 51f61f7..e04b360 100644 --- a/src/js/wrk/indexWrk.js +++ b/src/js/wrk/indexWrk.js @@ -115,6 +115,14 @@ class IndexWrk { this.devices[index].setName(name); } + getDeviceFromId(id) { + return this.getDeviceFromIndex(id2index(id, this.devices)); + } + + getDeviceFromIndex(index) { + return this.devices[index]; + } + /** * Get a device position in the devices array fom an id * @param {Number} id From aa44374301f3a8b8522cf294897016d756ecea2c Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Tue, 14 May 2024 12:48:07 +0200 Subject: [PATCH 14/19] Add IPC between windows --- package-lock.json | 148 +++++++++++++------------------- public/assets/icons | 2 +- public/assets/images/SD16.svg | 48 +++++------ src/js/beans/connector.js | 8 ++ src/js/ctrl/deviceDetailCtrl.js | 114 +++++++++++++++++++++--- src/js/ctrl/indexCtrl.js | 13 +++ src/js/main.js | 11 ++- src/js/main/deviceDetailMain.js | 2 +- src/js/wrk/indexWrk.js | 2 +- 9 files changed, 221 insertions(+), 127 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4836804..a8914fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -345,9 +345,9 @@ } }, "node_modules/@electron/asar": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.9.tgz", - "integrity": "sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz", + "integrity": "sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw==", "dev": true, "dependencies": { "commander": "^5.0.0", @@ -983,9 +983,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", - "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "dependencies": { "undici-types": "~5.26.4" } @@ -1886,22 +1886,22 @@ } }, "node_modules/config-file-ts/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.3.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", + "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.6", "minimatch": "^9.0.1", "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "path-scurry": "^1.11.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1923,9 +1923,9 @@ } }, "node_modules/config-file-ts/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -2292,9 +2292,9 @@ "dev": true }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -2307,9 +2307,9 @@ } }, "node_modules/electron": { - "version": "29.1.6", - "resolved": "https://registry.npmjs.org/electron/-/electron-29.1.6.tgz", - "integrity": "sha512-UIYfpHR9gRBFKHyejHuXUVQ7nNzZRnoPVOHlijkvqR+DSLwgJ2ZcVVt0LNduNeO8PhPkY1+6kHonL52OTC1cOw==", + "version": "29.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-29.3.3.tgz", + "integrity": "sha512-I/USTe9UsQUKb/iuiYnmt074vHxNHCJZWYiU4Xg6lNPKVBsPadAhZcc+g2gYLqC1rA7KT4AvKTmNsY8n7oEUCw==", "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -2736,9 +2736,9 @@ } }, "node_modules/electron-squirrel-startup": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz", - "integrity": "sha512-Oce8mvgGdFmwr+DsAcXBmFK8jFfN6yaFAP9IvyhTfupM3nFkBku/7VS/mdtJteWumImkC6P+BKGsxScoDDkv9Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz", + "integrity": "sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA==", "dependencies": { "debug": "^2.2.0" } @@ -2784,9 +2784,9 @@ } }, "node_modules/electron-winstaller": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.3.0.tgz", - "integrity": "sha512-ml77/OmeeLFFc+dk3YCwPQrl8rthwYcAea6mMZPFq7cGXlpWyRmmT0LY73XdCukPnevguXJFs+4Xu+aGHJwFDA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.3.1.tgz", + "integrity": "sha512-oM8BW3a8NEqG0XW+Vx3xywhk0DyDV4T0jT0zZfWt0IczNT3jHAAvQWBorF8osQDplSsCyXXyxrsrQ8cY0Slb/A==", "dev": true, "hasInstallScript": true, "optional": true, @@ -2794,7 +2794,7 @@ "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", - "lodash.template": "^4.2.2", + "lodash": "^4.17.21", "temp": "^0.9.0" }, "engines": { @@ -3543,12 +3543,13 @@ } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "optional": true, "dependencies": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4040,9 +4041,9 @@ } }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", "dev": true, "dependencies": { "async": "^3.2.3", @@ -4272,13 +4273,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", - "dev": true, - "optional": true - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -4323,27 +4317,6 @@ "dev": true, "peer": true }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "optional": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "optional": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -4428,6 +4401,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4725,9 +4699,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.56.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", - "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==", + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -5107,34 +5081,34 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" } }, "node_modules/path-scurry/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -5754,12 +5728,9 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -5878,9 +5849,9 @@ } }, "node_modules/socks": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", - "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { "ip-address": "^9.0.5", @@ -6322,9 +6293,9 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6550,7 +6521,8 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yargs": { "version": "17.7.2", diff --git a/public/assets/icons b/public/assets/icons index 4b19b5c..5ffada4 160000 --- a/public/assets/icons +++ b/public/assets/icons @@ -1 +1 @@ -Subproject commit 4b19b5ccb0febdeb301a9860a5545599817121bc +Subproject commit 5ffada450486a88e236b773dad8d60ce3b3981d9 diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index 486ee14..cc24712 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -17,7 +17,7 @@ - + 1 @@ -25,31 +25,31 @@ - + 2 - + 3 - + 4 - + 5 - + 6 @@ -57,62 +57,62 @@ - + 7 - + 8 - + 9 - + 10 - + 11 - + 12 - + 13 - + 14 - + 15 HI-Z - + 16 HI-Z @@ -123,49 +123,49 @@ - + OUT 1 - + OUT 2 - + OUT 3 - + OUT 4 - + OUT 5 - + OUT 6 - + OUT 7 - + OUT 8 diff --git a/src/js/beans/connector.js b/src/js/beans/connector.js index a8297e0..c8e1863 100644 --- a/src/js/beans/connector.js +++ b/src/js/beans/connector.js @@ -42,6 +42,14 @@ class Connector { return this._color + this._colorInvert ? "i" : ""; } + getIcon() { + return this._icon; + } + + setIcon(icon) { + this._icon = icon; + } + setPhantomPower(pwr) { this._pwr = pwr; } diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js index 648faf4..ff53326 100644 --- a/src/js/ctrl/deviceDetailCtrl.js +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -9,35 +9,127 @@ class DeviceDetailCtrl { console.log(`[deviceDetailCtrl] window ready, load device ${arg.id}`); this.initialize(arg); }); + /* ----- DOM Event Listeners ----- */ + $('.btn-close').on('click', (e) => { + this.closeModal(); + }); + $('.overlay').on('click', (e) => { + this.closeModal(); + }); } // MARK: Event handling - + closeModal() { + $('.modal').addClass("hidden"); + $('.overlay').addClass("hidden"); + }; + // MARK: IPC events initialize(arg) { - deviceDetailWrk = this.reconstructObject(arg.worker); - console.log(deviceDetailWrk); + this.reconstructObject(arg.worker); this.deviceID = arg.id; - this.drawCanvas(deviceDetailWrk.getDeviceFromId(this.deviceID)); + this.drawCanvas(indexWrk.getDeviceFromId(this.deviceID)); } // MARK: Functions reconstructObject(worker) { - worker.addDevice = deviceDetailWrk.addDevice; - worker.addLink = deviceDetailWrk.addLink; - worker.getDeviceFromId = deviceDetailWrk.getDeviceFromId; - worker.getDeviceFromIndex = deviceDetailWrk.getDeviceFromIndex; - - return worker; + worker.devices.forEach(device => { + indexWrk.devices.push(new Device(device._type, device._id, 0, 0)); + var devIdx = indexWrk.devices.length - 1; + indexWrk.devices[devIdx].x = device.x; + indexWrk.devices[devIdx].y = device.y; + indexWrk.devices[devIdx].name = device.name; + device.inputs.forEach(input => { + indexWrk.devices[devIdx].inputs.push(new Connector()); + var connIdx = indexWrk.devices[devIdx].inputs.length - 1; + indexWrk.devices[devIdx].inputs[connIdx]._name = input._name; + indexWrk.devices[devIdx].inputs[connIdx]._color = input._color; + indexWrk.devices[devIdx].inputs[connIdx]._colorInvert = input._colorInvert; + indexWrk.devices[devIdx].inputs[connIdx]._icon = input._icon; + }); + device.outputs.forEach(output => { + indexWrk.devices[devIdx].outputs.push(new Connector()); + var connIdx = indexWrk.devices[devIdx].outputs.length - 1; + indexWrk.devices[devIdx].outputs[connIdx]._name = output._name; + indexWrk.devices[devIdx].outputs[connIdx]._color = output._color; + indexWrk.devices[devIdx].outputs[connIdx]._colorInvert = output._colorInvert; + indexWrk.devices[devIdx].outputs[connIdx]._icon = output._icon; + }); + }); + indexWrk.links = worker.links; + indexWrk.devTypeLUT = worker.devTypeLUT; + indexWrk.id = worker.id; + console.log(indexWrk); } drawCanvas(device) { $('#canvas').empty(); $('#canvas').append( - "
" + "" ) + + var _this = this; + $('#svg').on("load", function (ev) { + var $svg = $(this).contents(); + + var $inputs = $svg.find('#Inputs').children(); + var $outputs = $svg.find("#Outputs").children(); + var $allElements = $inputs.add($outputs); + + $allElements.each(function() { + $(this).on("click", function (ev) { + _this.openModal(device, this.id.slice(3, 4), this.id.slice(5, 7)); + _this.changeXLR(this); + }); + }); + }); + } + + openModal(device, ioType, ioNbr) { + console.log(`[deviceDetailCtrl] opening io ${ioType}${ioNbr}`) + let connector = device.inputs[ioNbr - 1]; + let type = ioType == "i" ? "Input" : "Output"; + $('.modal').removeClass("hidden"); + $('.overlay').removeClass("hidden"); + $("#modal-title").html(device.getName() + " - " + type + " " + ioNbr); + $("#channel-name").val(connector.getName()); + $('#color-list').attr('data-selected', connector.getColor()); + this.selectColor(connector.getColor()); + this.selectIcon(connector.getIcon()); + $('#channel-phase').prop('checked', connector.getPhaseInvert()); + $('#channel-phase').prop('checked', connector.getColorInvert()); + }; + + selectColor(id) { + let $selected = $(document).find('#color-list').find(':button[value="' + id + '"]'); + let $selectedValue = $selected.val(); + let $icon = $selected.find('svg'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); + } + + selectIcon(id) { + let $selected = $(document).find('#icon-list').find(':button[value="' + id + '"]'); + let $selectedValue = $selected.val(); + let $icon = $selected.find('object'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); } } \ No newline at end of file diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js index c9910bf..d3e9a91 100644 --- a/src/js/ctrl/indexCtrl.js +++ b/src/js/ctrl/indexCtrl.js @@ -10,6 +10,9 @@ class IndexCtrl { ipcRenderer.on('menu', (event, arg) => { this.menuClick(arg); }) + ipcRenderer.on('request-data-changes', (event, arg) => { + this.requestDataChanges(arg); + }) /* ----- DOM Event Listeners ----- */ window.addEventListener("mousemove", (e) => { this.mouseMove(e); @@ -104,6 +107,7 @@ class IndexCtrl { openDeviceDetail(e) { //TODO: Open new window let id = $(e.target).parent().attr('deviceid'); + console.log(indexWrk); ipcRenderer.send('window', { id: id, worker: indexWrk }); } @@ -181,6 +185,14 @@ class IndexCtrl { } } + /** + * Data changes received from child windows IPC + * @param {*} arg + */ + requestDataChanges(arg) { + // TODO: Add IO parameters + } + // MARK: Functions /** * Refresh DOM @@ -196,6 +208,7 @@ class IndexCtrl { if (links.length > 0) { this.drawLines(links); } + ipcRenderer.send('forward-to-childs', indexWrk); } /** diff --git a/src/js/main.js b/src/js/main.js index f926d80..9c78c3e 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -303,6 +303,15 @@ ipcMain.on('file', (event, arg) => { } }) +// MARK: IPC windows +ipcMain.on('forward-to-main', (event, arg) => { + win.webContents.send('request-data-changes', arg); +}); + +ipcMain.on('forward-to-childs', (event, arg) => { + childWindow.webContents.send('new-data', arg); +}); + // MARK: Functions function loadFile() { dialog.showOpenDialog({ @@ -376,4 +385,4 @@ function aboutWindow() { adjust_window_size: true, description: "Routing simplified", }) -} \ No newline at end of file +} diff --git a/src/js/main/deviceDetailMain.js b/src/js/main/deviceDetailMain.js index 180d863..7c19349 100644 --- a/src/js/main/deviceDetailMain.js +++ b/src/js/main/deviceDetailMain.js @@ -1,4 +1,4 @@ $(document).ready(function () { deviceDetailCtrl = new DeviceDetailCtrl(); - deviceDetailWrk = new IndexWrk(); + indexWrk = new IndexWrk(); }); diff --git a/src/js/wrk/indexWrk.js b/src/js/wrk/indexWrk.js index e04b360..5c47e1c 100644 --- a/src/js/wrk/indexWrk.js +++ b/src/js/wrk/indexWrk.js @@ -172,4 +172,4 @@ class IndexWrk { */ function id2index(id, array) { return array.findIndex((element) => Number(element.getId()) === Number(id)); -} +} \ No newline at end of file From 7685f3ed043e62391284add6ef48cd2969fe6dd6 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Tue, 14 May 2024 22:13:30 +0200 Subject: [PATCH 15/19] Send connector data to main worker --- public/assets/images/SD16.svg | 16 +--- src/device-detail.html | 8 +- src/js/beans/connector.js | 6 +- src/js/const.js | 33 +++++++ src/js/ctrl/deviceDetailCtrl.js | 147 +++++++++++++++++++++++--------- src/js/ctrl/indexCtrl.js | 6 +- 6 files changed, 156 insertions(+), 60 deletions(-) diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index cc24712..c96a94e 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -20,9 +20,7 @@ 1 - - - + @@ -52,9 +50,7 @@ 6 - - - + @@ -116,9 +112,7 @@ 16 HI-Z - - - + @@ -168,9 +162,7 @@ OUT 8 - - - + diff --git a/src/device-detail.html b/src/device-detail.html index 7da398b..6965539 100644 --- a/src/device-detail.html +++ b/src/device-detail.html @@ -21,14 +21,14 @@ - @@ -47,8 +47,8 @@
- - + +
diff --git a/src/js/beans/connector.js b/src/js/beans/connector.js index c8e1863..9caf4af 100644 --- a/src/js/beans/connector.js +++ b/src/js/beans/connector.js @@ -3,7 +3,7 @@ class Connector { this._name = ""; this._color = "OFF"; this._colorInvert = false; - this._icon = "01"; + this._icon = "1"; } // MARK: RW properties @@ -21,8 +21,8 @@ class Connector { this._color = color; } - getColor(color) { - this._color = color; + getColor() { + return this._color; } setColorInvert(colorInvert) { diff --git a/src/js/const.js b/src/js/const.js index 2437a47..afed5f7 100644 --- a/src/js/const.js +++ b/src/js/const.js @@ -32,4 +32,37 @@ class Const { { Name: "White", Color: "#FFFFFF", ID: "WH" } ]; } + + reconstructIndexWrk(wrk) { + var ret = new IndexWrk(); + wrk.devices.forEach(device => { + ret.devices.push(new Device(device._type, device._id, 0, 0)); + var devIdx = wrk.devices.length - 1; + ret.devices[devIdx].x = device.x; + ret.devices[devIdx].y = device.y; + ret.devices[devIdx].name = device.name; + device.inputs.forEach(input => { + ret.devices[devIdx].inputs.push(new Connector()); + var connIdx = ret.devices[devIdx].inputs.length - 1; + ret.devices[devIdx].inputs[connIdx]._name = input._name; + ret.devices[devIdx].inputs[connIdx]._color = input._color; + ret.devices[devIdx].inputs[connIdx]._colorInvert = input._colorInvert; + ret.devices[devIdx].inputs[connIdx]._icon = input._icon; + }); + device.outputs.forEach(output => { + ret.devices[devIdx].outputs.push(new Connector()); + var connIdx = ret.devices[devIdx].outputs.length - 1; + ret.devices[devIdx].outputs[connIdx]._name = output._name; + ret.devices[devIdx].outputs[connIdx]._color = output._color; + ret.devices[devIdx].outputs[connIdx]._colorInvert = output._colorInvert; + ret.devices[devIdx].outputs[connIdx]._icon = output._icon; + }); + }); + wrk.links.forEach(link => { + ret.links.push(new link(link._device1, link._device2, link._aes50_1, link._aes50_2)) + }) + ret.devTypeLUT = new DeviceTypeLUT(); + ret.id = wrk.id; + return ret; + } } \ No newline at end of file diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js index ff53326..bcbf6f5 100644 --- a/src/js/ctrl/deviceDetailCtrl.js +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -5,17 +5,25 @@ class DeviceDetailCtrl { // MARK: Constructor constructor() { + /* ----- IPC ----- */ ipcRenderer.on('ready', (event, arg) => { console.log(`[deviceDetailCtrl] window ready, load device ${arg.id}`); this.initialize(arg); }); + ipcRenderer.on('new-data', (event, arg) => { + this.dataUpdated(arg); + }) /* ----- DOM Event Listeners ----- */ + $(document).ready(this.documentReady()); $('.btn-close').on('click', (e) => { this.closeModal(); }); $('.overlay').on('click', (e) => { this.closeModal(); }); + $('#save').on('click', (e) => { + this.saveConnector(); + }) } // MARK: Event handling @@ -24,44 +32,93 @@ class DeviceDetailCtrl { $('.overlay').addClass("hidden"); }; + documentReady() { + // Create dropdowns + constants.icons.forEach((icon, index, fullArray) => { + $('#icon-list').append( + "" + ) + }); + + constants.colors.forEach((color, index, fullArray) => { + $('#color-list').append( + "" + ) + }); + + $(document).click(function() { + $('.dropdown-menu.show').removeClass('show'); + }); + + $('body').on('click','.trigger-dropdown', function(e){ + e.stopPropagation(); + $(this).closest('.dropdown-wrapper').find('.dropdown-menu').toggleClass('show'); + }); + + $('body').on('click','.dropdown-item', function(e){ + e.stopPropagation(); + let $selectedValue = $(this).val(); + let $icon = $(this).find('svg'); + if ($icon.length <= 0) { + $icon = $(this).find('object'); + } + let $text = $(this).find('span'); + let $btn = $(this).closest('.dropdown-wrapper').find('.trigger-dropdown'); + + $(this).closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); + $btn.find('span').remove(); + $btn.find('svg').remove(); + $btn.find('object').remove(); + $btn.prepend($text[0].outerHTML); + $btn.prepend($icon[0].outerHTML); + }); + } + + saveConnector() { + if (this.selectedType == "i") { + this.selectedDevice.inputs[this.selectedIO - 1].setName($('#channel-name').val()); + this.selectedDevice.inputs[this.selectedIO - 1].setColor($('#color-list').attr('data-selected')); + this.selectedDevice.inputs[this.selectedIO - 1].setIcon($('#icon-list').attr('data-selected')); + this.selectedDevice.inputs[this.selectedIO - 1].setPhaseInvert($('#channel-phase').prop('checked')); + this.selectedDevice.inputs[this.selectedIO - 1].setColorInvert($('#channel-invert').prop('checked')); + } + else { + this.selectedDevice.outputs[this.selectedIO - 1].setName($('#channel-name').val()); + this.selectedDevice.outputs[this.selectedIO - 1].setColor($('#color-list').attr('data-selected')); + this.selectedDevice.outputs[this.selectedIO - 1].setIcon($('#icon-list').attr('data-selected')); + this.selectedDevice.outputs[this.selectedIO - 1].setPhaseInvert($('#channel-phase').prop('checked')); + this.selectedDevice.outputs[this.selectedIO - 1].setColorInvert($('#channel-invert').prop('checked')); + } + ipcRenderer.send('forward-to-main', { worker: indexWrk }); + this.closeModal(); + } + // MARK: IPC events initialize(arg) { - this.reconstructObject(arg.worker); + indexWrk = constants.reconstructIndexWrk(arg.worker); this.deviceID = arg.id; this.drawCanvas(indexWrk.getDeviceFromId(this.deviceID)); } - // MARK: Functions - reconstructObject(worker) { - worker.devices.forEach(device => { - indexWrk.devices.push(new Device(device._type, device._id, 0, 0)); - var devIdx = indexWrk.devices.length - 1; - indexWrk.devices[devIdx].x = device.x; - indexWrk.devices[devIdx].y = device.y; - indexWrk.devices[devIdx].name = device.name; - device.inputs.forEach(input => { - indexWrk.devices[devIdx].inputs.push(new Connector()); - var connIdx = indexWrk.devices[devIdx].inputs.length - 1; - indexWrk.devices[devIdx].inputs[connIdx]._name = input._name; - indexWrk.devices[devIdx].inputs[connIdx]._color = input._color; - indexWrk.devices[devIdx].inputs[connIdx]._colorInvert = input._colorInvert; - indexWrk.devices[devIdx].inputs[connIdx]._icon = input._icon; - }); - device.outputs.forEach(output => { - indexWrk.devices[devIdx].outputs.push(new Connector()); - var connIdx = indexWrk.devices[devIdx].outputs.length - 1; - indexWrk.devices[devIdx].outputs[connIdx]._name = output._name; - indexWrk.devices[devIdx].outputs[connIdx]._color = output._color; - indexWrk.devices[devIdx].outputs[connIdx]._colorInvert = output._colorInvert; - indexWrk.devices[devIdx].outputs[connIdx]._icon = output._icon; - }); - }); - indexWrk.links = worker.links; - indexWrk.devTypeLUT = worker.devTypeLUT; - indexWrk.id = worker.id; - console.log(indexWrk); + /** + * Data changes received from main windows IPC + * @param {*} arg + */ + dataUpdated(arg) { + console.log(`[deviceDetailCtrl] got worker from main`); + indexWrk = constants.reconstructIndexWrk(arg.worker); } + // MARK: Functions + drawCanvas(device) { $('#canvas').empty(); $('#canvas').append( @@ -81,26 +138,36 @@ class DeviceDetailCtrl { $allElements.each(function() { $(this).on("click", function (ev) { - _this.openModal(device, this.id.slice(3, 4), this.id.slice(5, 7)); - _this.changeXLR(this); + _this.selectedType = this.id.slice(3, 4); + _this.selectedIO = this.id.slice(5, 7); + _this.selectedDevice = device; + _this.openModal(); }); }); }); } - openModal(device, ioType, ioNbr) { - console.log(`[deviceDetailCtrl] opening io ${ioType}${ioNbr}`) - let connector = device.inputs[ioNbr - 1]; - let type = ioType == "i" ? "Input" : "Output"; + openModal() { + console.log(`[deviceDetailCtrl] opening io ${this.selectedType}${this.selectedIO}`) + let connector, type; + if (this.selectedType == "i") { + connector = this.selectedDevice.inputs[this.selectedIO - 1]; + type = "Input"; + } + else { + connector = this.selectedDevice.outputs[this.selectedIO - 1]; + type = "Output"; + } $('.modal').removeClass("hidden"); $('.overlay').removeClass("hidden"); - $("#modal-title").html(device.getName() + " - " + type + " " + ioNbr); + $("#modal-title").html(this.selectedDevice.getName() + " - " + type + " " + this.selectedIO); $("#channel-name").val(connector.getName()); $('#color-list').attr('data-selected', connector.getColor()); this.selectColor(connector.getColor()); + $('#icon-list').attr('data-selected', connector.getIcon()); this.selectIcon(connector.getIcon()); $('#channel-phase').prop('checked', connector.getPhaseInvert()); - $('#channel-phase').prop('checked', connector.getColorInvert()); + $('#channel-invert').prop('checked', connector.getColorInvert()); }; selectColor(id) { @@ -110,6 +177,8 @@ class DeviceDetailCtrl { let $text = $selected.find('span'); let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + console.log($selected) + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); $btn.find('span').remove(); $btn.find('svg').remove(); @@ -121,7 +190,7 @@ class DeviceDetailCtrl { selectIcon(id) { let $selected = $(document).find('#icon-list').find(':button[value="' + id + '"]'); let $selectedValue = $selected.val(); - let $icon = $selected.find('object'); + let $icon = $selected.find('object'); let $text = $selected.find('span'); let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js index d3e9a91..f7a1efe 100644 --- a/src/js/ctrl/indexCtrl.js +++ b/src/js/ctrl/indexCtrl.js @@ -190,7 +190,9 @@ class IndexCtrl { * @param {*} arg */ requestDataChanges(arg) { - // TODO: Add IO parameters + console.log(`[indexCtrl] got worker from child`); + indexWrk = constants.reconstructIndexWrk(arg.worker); + ipcRenderer.send('forward-to-childs', { worker: indexWrk }); } // MARK: Functions @@ -208,7 +210,7 @@ class IndexCtrl { if (links.length > 0) { this.drawLines(links); } - ipcRenderer.send('forward-to-childs', indexWrk); + ipcRenderer.send('forward-to-childs', { worker: indexWrk }); } /** From e9aea1fc849ddd6db4ed1dccd5d9fe2577f754ee Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Fri, 17 May 2024 08:44:50 +0200 Subject: [PATCH 16/19] Finish worker sharing between windows --- src/js/beans/deviceTypeLUT.js | 4 +- src/js/ctrl/deviceDetailCtrl.js | 72 +++++++++++++++------------------ src/js/ctrl/indexCtrl.js | 5 +-- src/js/main.js | 39 ++++++++++-------- src/js/wrk/indexWrk.js | 23 ++++++++--- 5 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/js/beans/deviceTypeLUT.js b/src/js/beans/deviceTypeLUT.js index c545033..949a5f0 100644 --- a/src/js/beans/deviceTypeLUT.js +++ b/src/js/beans/deviceTypeLUT.js @@ -11,13 +11,13 @@ class DeviceTypeLUT { getInputsCnt(type) { let inputCnt = this._deviceInfo.filter((device) => (device.ID === type))[0].Inputs; - console.log("[deviceTypeLUT] Input Count for " + type + " is " + inputCnt); + console.log(`[deviceTypeLUT] Input Count for ${type} is ${inputCnt}`); return inputCnt; } getOutputsCnt(type) { let outputCnt = this._deviceInfo.filter((device) => (device.ID === type))[0].Outputs; - console.log("[deviceTypeLUT] Output Count for " + type + " is " + outputCnt); + console.log(`[deviceTypeLUT] Output Count for ${type} is ${outputCnt}`); return outputCnt; } diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js index bcbf6f5..d0ea791 100644 --- a/src/js/ctrl/deviceDetailCtrl.js +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -37,7 +37,7 @@ class DeviceDetailCtrl { constants.icons.forEach((icon, index, fullArray) => { $('#icon-list').append( "" ) @@ -54,24 +54,24 @@ class DeviceDetailCtrl { ) }); - $(document).click(function() { + $(document).click(function () { $('.dropdown-menu.show').removeClass('show'); }); - - $('body').on('click','.trigger-dropdown', function(e){ + + $('body').on('click', '.trigger-dropdown', function (e) { e.stopPropagation(); $(this).closest('.dropdown-wrapper').find('.dropdown-menu').toggleClass('show'); }); - - $('body').on('click','.dropdown-item', function(e){ + + $('body').on('click', '.dropdown-item', function (e) { e.stopPropagation(); let $selectedValue = $(this).val(); - let $icon = $(this).find('svg'); + let $icon = $(this).find('svg'); if ($icon.length <= 0) { - $icon = $(this).find('object'); + $icon = $(this).find('object'); } - let $text = $(this).find('span'); - let $btn = $(this).closest('.dropdown-wrapper').find('.trigger-dropdown'); + let $text = $(this).find('span'); + let $btn = $(this).closest('.dropdown-wrapper').find('.trigger-dropdown'); $(this).closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); $btn.find('span').remove(); @@ -83,29 +83,24 @@ class DeviceDetailCtrl { } saveConnector() { - if (this.selectedType == "i") { - this.selectedDevice.inputs[this.selectedIO - 1].setName($('#channel-name').val()); - this.selectedDevice.inputs[this.selectedIO - 1].setColor($('#color-list').attr('data-selected')); - this.selectedDevice.inputs[this.selectedIO - 1].setIcon($('#icon-list').attr('data-selected')); - this.selectedDevice.inputs[this.selectedIO - 1].setPhaseInvert($('#channel-phase').prop('checked')); - this.selectedDevice.inputs[this.selectedIO - 1].setColorInvert($('#channel-invert').prop('checked')); - } - else { - this.selectedDevice.outputs[this.selectedIO - 1].setName($('#channel-name').val()); - this.selectedDevice.outputs[this.selectedIO - 1].setColor($('#color-list').attr('data-selected')); - this.selectedDevice.outputs[this.selectedIO - 1].setIcon($('#icon-list').attr('data-selected')); - this.selectedDevice.outputs[this.selectedIO - 1].setPhaseInvert($('#channel-phase').prop('checked')); - this.selectedDevice.outputs[this.selectedIO - 1].setColorInvert($('#channel-invert').prop('checked')); - } + indexWrk.updateConnector( + this.selectedDevice.getId(), + this.selectedType, + this.selectedIO, + $('#channel-name').val(), + $('#color-list').attr('data-selected'), + $('#icon-list').attr('data-selected'), + $('#channel-phase').prop('checked'), + $('#channel-invert').prop('checked') + ); ipcRenderer.send('forward-to-main', { worker: indexWrk }); this.closeModal(); } // MARK: IPC events initialize(arg) { - indexWrk = constants.reconstructIndexWrk(arg.worker); this.deviceID = arg.id; - this.drawCanvas(indexWrk.getDeviceFromId(this.deviceID)); + this.dataUpdated(arg); } /** @@ -115,6 +110,7 @@ class DeviceDetailCtrl { dataUpdated(arg) { console.log(`[deviceDetailCtrl] got worker from main`); indexWrk = constants.reconstructIndexWrk(arg.worker); + this.drawCanvas(indexWrk.getDeviceFromId(this.deviceID)); } // MARK: Functions @@ -127,7 +123,7 @@ class DeviceDetailCtrl { "" ) - + var _this = this; $('#svg').on("load", function (ev) { var $svg = $(this).contents(); @@ -136,7 +132,7 @@ class DeviceDetailCtrl { var $outputs = $svg.find("#Outputs").children(); var $allElements = $inputs.add($outputs); - $allElements.each(function() { + $allElements.each(function () { $(this).on("click", function (ev) { _this.selectedType = this.id.slice(3, 4); _this.selectedIO = this.id.slice(5, 7); @@ -172,12 +168,10 @@ class DeviceDetailCtrl { selectColor(id) { let $selected = $(document).find('#color-list').find(':button[value="' + id + '"]'); - let $selectedValue = $selected.val(); - let $icon = $selected.find('svg'); - let $text = $selected.find('span'); - let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); - - console.log($selected) + let $selectedValue = $selected.val(); + let $icon = $selected.find('svg'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); $btn.find('span').remove(); @@ -186,14 +180,14 @@ class DeviceDetailCtrl { $btn.prepend($text[0].outerHTML); $btn.prepend($icon[0].outerHTML); } - + selectIcon(id) { let $selected = $(document).find('#icon-list').find(':button[value="' + id + '"]'); let $selectedValue = $selected.val(); - let $icon = $selected.find('object'); - let $text = $selected.find('span'); - let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); - + let $icon = $selected.find('object'); + let $text = $selected.find('span'); + let $btn = $selected.closest('.dropdown-wrapper').find('.trigger-dropdown'); + $selected.closest('.dropdown-wrapper').find('.dropdown-menu').removeClass('show').attr('data-selected', $selectedValue); $btn.find('span').remove(); $btn.find('svg').remove(); diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js index f7a1efe..73649ee 100644 --- a/src/js/ctrl/indexCtrl.js +++ b/src/js/ctrl/indexCtrl.js @@ -53,7 +53,7 @@ class IndexCtrl { } } else if (this.selectedElement.Type === "Device") { - console.log(`new x ${this.selectedElement.NewX} new y ${this.selectedElement.NewY}`) + console.log(`[indexCtrl] new x ${this.selectedElement.NewX} new y ${this.selectedElement.NewY}`) indexWrk.moveDeviceId(this.selectedElement.Id, this.selectedElement.NewX, this.selectedElement.NewY); } this.selectedElement = {}; @@ -105,9 +105,7 @@ class IndexCtrl { * @param {Event} e */ openDeviceDetail(e) { - //TODO: Open new window let id = $(e.target).parent().attr('deviceid'); - console.log(indexWrk); ipcRenderer.send('window', { id: id, worker: indexWrk }); } @@ -153,7 +151,6 @@ class IndexCtrl { * @param {Event} e */ deviceSelect(e) { - console.log(e.target); let parent = $(e.target).parent(); let x = e.clientX; let y = e.clientY; diff --git a/src/js/main.js b/src/js/main.js index 9c78c3e..5a56de4 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -265,14 +265,17 @@ app.on('window-all-closed', () => { // } }); -var childWindow; +var childWindows = []; // MARK: IPC events ipcMain.on('window', (event, arg) => { createChildWindow(join(__dirname, '..', 'device-detail.html')); - childWindow.webContents.on('did-finish-load', () => { - childWindow.webContents.send('ready', arg); + childWindows[childWindows.length - 1].webContents.on('did-finish-load', () => { + childWindows[childWindows.length - 1].webContents.send('ready', arg); }); + childWindows[childWindows.length - 1].on('closed', function () { + childWindows.splice(childWindows[childWindows.length - 1].index, 1); + }) }); ipcMain.on('file', (event, arg) => { @@ -309,7 +312,9 @@ ipcMain.on('forward-to-main', (event, arg) => { }); ipcMain.on('forward-to-childs', (event, arg) => { - childWindow.webContents.send('new-data', arg); + childWindows.forEach(childWindow => { + childWindow.webContents.send('new-data', arg); + }); }); // MARK: Functions @@ -345,18 +350,20 @@ function loadFile() { // function to create a child window function createChildWindow(fileName) { - childWindow = new BrowserWindow({ - width: 700, - height: 500, - menuBarVisible: false, - autoHideMenuBar: true, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true, - } - }) - childWindow.loadFile(fileName) + childWindows.push(new BrowserWindow({ + width: 700, + height: 500, + menuBarVisible: false, + autoHideMenuBar: true, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true, + } + }) + ); + childWindows[childWindows.length - 1].index = childWindows.length - 1; + childWindows[childWindows.length - 1].loadFile(fileName) } function aboutWindow() { diff --git a/src/js/wrk/indexWrk.js b/src/js/wrk/indexWrk.js index 5c47e1c..7cd0d66 100644 --- a/src/js/wrk/indexWrk.js +++ b/src/js/wrk/indexWrk.js @@ -34,7 +34,6 @@ class IndexWrk { console.log(`[indexWrk] add link from id ${fromID} ${fromAES} to id ${toID} ${toAES}`); this.links.push(new Link(fromID, fromAES, toID, toAES)); } - console.log(this.links); indexCtrl.drawCanvas(this.devices, this.links); } @@ -50,10 +49,6 @@ class IndexWrk { for (let idx = 0; idx < 2; idx++) { this.links.forEach((link, index, fullArray) => { // if link already on aes50 - console.log((fromID == link.getFromDeviceId() && fromAES == link.getFromAes50())); - console.log((fromID == link.getToDeviceId() && fromAES == link.getToAes50())); - console.log((toID == link.getFromDeviceId() && toAES == link.getFromAes50())); - console.log((toID == link.getToDeviceId() && toAES == link.getToAes50())); if ((fromID == link.getFromDeviceId() && fromAES == link.getFromAes50()) || (fromID == link.getToDeviceId() && fromAES == link.getToAes50()) || (toID == link.getFromDeviceId() && toAES == link.getFromAes50()) || @@ -160,6 +155,24 @@ class IndexWrk { return false; } } + + updateConnector(deviceID, connectorType, connectorNbr, name, color, icon, phaseInvert, colorInvert) { + if (connectorType == "i") { + console.log(`[indexWrk] id ${deviceID}, index ${id2index(deviceID, this.devices)}`); + this.devices[id2index(deviceID, this.devices)].inputs[connectorNbr - 1].setName(name); + this.devices[id2index(deviceID, this.devices)].inputs[connectorNbr - 1].setColor(color); + this.devices[id2index(deviceID, this.devices)].inputs[connectorNbr - 1].setIcon(icon); + this.devices[id2index(deviceID, this.devices)].inputs[connectorNbr - 1].setPhaseInvert(phaseInvert); + this.devices[id2index(deviceID, this.devices)].inputs[connectorNbr - 1].setColorInvert(colorInvert); + } + else { + this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setName(name); + this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setColor(color); + this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setIcon(icon); + this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setPhaseInvert(phaseInvert); + this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setColorInvert(colorInvert); + } + } } // MARK: Private funcitons From 68841ad529548dce47eb4574e426a5d62cd08938 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Fri, 17 May 2024 09:43:01 +0200 Subject: [PATCH 17/19] Draw icon and color in device view --- src/js/const.js | 23 +++++++++++++++-------- src/js/ctrl/deviceDetailCtrl.js | 23 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/js/const.js b/src/js/const.js index afed5f7..5f9c13e 100644 --- a/src/js/const.js +++ b/src/js/const.js @@ -22,17 +22,24 @@ class Const { "MixBus", "Matrix", "Routing", "Smiley" ]; this.colors = [ - { Name: "Off", Color: "#000000", ID: "OFF" }, - { Name: "Red", Color: "#E72D2E", ID: "RD" }, - { Name: "Green", Color: "#35e72d", ID: "GN" }, - { Name: "Yellow", Color: "#FCF300", ID: "YE" }, - { Name: "Blue", Color: "#0060FF", ID: "BL" }, - { Name: "Magenta", Color: "#ED27AC", ID: "MG" }, - { Name: "Cyan", Color: "#2DE0E7", ID: "CY" }, - { Name: "White", Color: "#FFFFFF", ID: "WH" } + { Name: "Off", ColorBack: "#000000", ColorFront: "#FFFFFF", ID: "OFF" }, + { Name: "Red", ColorBack: "#E72D2E", ColorFront: "#000000", ID: "RD" }, + { Name: "Green", ColorBack: "#35E72D", ColorFront: "#000000", ID: "GN" }, + { Name: "Yellow", ColorBack: "#FCF300", ColorFront: "#000000", ID: "YE" }, + { Name: "Blue", ColorBack: "#0060FF", ColorFront: "#FFFFFF", ID: "BL" }, + { Name: "Magenta", ColorBack: "#ED27AC", ColorFront: "#000000", ID: "MG" }, + { Name: "Cyan", ColorBack: "#2DE0E7", ColorFront: "#000000", ID: "CY" }, + { Name: "White", ColorBack: "#FFFFFF", ColorFront: "#000000", ID: "WH" } ]; } + getColorCode(colorID) { + var color = this.colors.find(color => { + return color.ID == colorID; + }) + return { Back: color.ColorBack, Front: color.ColorFront }; + } + reconstructIndexWrk(wrk) { var ret = new IndexWrk(); wrk.devices.forEach(device => { diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js index d0ea791..8643f83 100644 --- a/src/js/ctrl/deviceDetailCtrl.js +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -47,7 +47,7 @@ class DeviceDetailCtrl { $('#color-list').append( "" @@ -116,6 +116,7 @@ class DeviceDetailCtrl { // MARK: Functions drawCanvas(device) { + console.log(`[deviceDetailCtrl] draw canvas`) $('#canvas').empty(); $('#canvas').append( "
" + + ""; + } + }); + $.each($outputs, function (idx, val) { + var output = indexWrk.getDeviceFromId(_this.deviceID).outputs[val.id.slice(5, 7) - 1]; + console.log(output) + var colors = constants.getColorCode(output.getColor()); + if ((output.getName() != "") || (output.getIcon() != "1") || (output.getColor() != "OFF")) { + val.querySelector('#connector').innerHTML = + "" + + ""; + } + }); }); } From 457b249eceec4266b697a63054fa5e94944c1054 Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Fri, 17 May 2024 09:57:32 +0200 Subject: [PATCH 18/19] Fix file menu functions --- src/js/ctrl/deviceDetailCtrl.js | 9 +++++++++ src/js/ctrl/indexCtrl.js | 28 ++++++++++++++++++++++++++++ src/js/main.js | 3 +-- src/js/wrk/indexWrk.js | 18 ++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/js/ctrl/deviceDetailCtrl.js b/src/js/ctrl/deviceDetailCtrl.js index 8643f83..7460199 100644 --- a/src/js/ctrl/deviceDetailCtrl.js +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -27,11 +27,17 @@ class DeviceDetailCtrl { } // MARK: Event handling + /** + * Close modal form + */ closeModal() { $('.modal').addClass("hidden"); $('.overlay').addClass("hidden"); }; + /** + * Document ready, prepare icon and color lists + */ documentReady() { // Create dropdowns constants.icons.forEach((icon, index, fullArray) => { @@ -82,6 +88,9 @@ class DeviceDetailCtrl { }); } + /** + * Save button pressed + */ saveConnector() { indexWrk.updateConnector( this.selectedDevice.getId(), diff --git a/src/js/ctrl/indexCtrl.js b/src/js/ctrl/indexCtrl.js index 73649ee..f887b69 100644 --- a/src/js/ctrl/indexCtrl.js +++ b/src/js/ctrl/indexCtrl.js @@ -13,6 +13,9 @@ class IndexCtrl { ipcRenderer.on('request-data-changes', (event, arg) => { this.requestDataChanges(arg); }) + ipcRenderer.on('file', (event, arg) => { + this.fileMenu(arg); + }) /* ----- DOM Event Listeners ----- */ window.addEventListener("mousemove", (e) => { this.mouseMove(e); @@ -94,6 +97,10 @@ class IndexCtrl { } } + /** + * Right click event -> delete device + * @param {Event} e + */ deleteDevice(e) { let id = $(e.target).parent().attr('deviceid'); indexWrk.removeDeviceId(id); @@ -192,6 +199,27 @@ class IndexCtrl { ipcRenderer.send('forward-to-childs', { worker: indexWrk }); } + /** + * File menu event. Send or receive worker + * @param {*} arg + */ + fileMenu(arg) { + if ('save' == arg.function || 'saveas' == arg.function) { + console.log(`[indexCtrl] save file`); + // Convert the object to JSON + let json = JSON.stringify(indexWrk, null, 2); + ipcRenderer.send('file', { + function: arg.function, + json: json + }); + } + else if ('load' == arg.function) { + console.log(`[indexCtrl] load file`); + indexWrk = constants.reconstructIndexWrk(arg.jsonData); + indexWrk.update(); + } + } + // MARK: Functions /** * Refresh DOM diff --git a/src/js/main.js b/src/js/main.js index 5a56de4..d0964a4 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -338,8 +338,7 @@ function loadFile() { // Extract the arrays win.webContents.send('file', { function: 'load', - devices: jsonData.devices, - links: jsonData.links + jsonData: jsonData }); }); } diff --git a/src/js/wrk/indexWrk.js b/src/js/wrk/indexWrk.js index 7cd0d66..97c6780 100644 --- a/src/js/wrk/indexWrk.js +++ b/src/js/wrk/indexWrk.js @@ -156,6 +156,17 @@ class IndexWrk { } } + /** + * Update a connector of device + * @param {Number} deviceID + * @param {String} connectorType + * @param {String} connectorNbr + * @param {String} name + * @param {String} color + * @param {String} icon + * @param {Boolean} phaseInvert + * @param {Boolean} colorInvert + */ updateConnector(deviceID, connectorType, connectorNbr, name, color, icon, phaseInvert, colorInvert) { if (connectorType == "i") { console.log(`[indexWrk] id ${deviceID}, index ${id2index(deviceID, this.devices)}`); @@ -173,6 +184,13 @@ class IndexWrk { this.devices[id2index(deviceID, this.devices)].outputs[connectorNbr - 1].setColorInvert(colorInvert); } } + + /** + * Update worker and canvas + */ + update() { + indexCtrl.drawCanvas(this.devices, this.links); + } } // MARK: Private funcitons From 05918b7895038c95636de8bb207317b9b40caccb Mon Sep 17 00:00:00 2001 From: mamarguerat <49449909+mamarguerat@users.noreply.github.com> Date: Fri, 17 May 2024 10:02:37 +0200 Subject: [PATCH 19/19] Change version to 0.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cdc90f5..6544413 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Mixo", - "version": "0.0.5", + "version": "0.0.6", "description": "Routing tool for behringer x32 based on hardware availabilities instead of software capabilities. Aim to simplify routing and create automatic documentation.", "main": "src/js/main.js", "scripts": {