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/README.md b/README.md index f66bcaf..57b72e3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ +

diff --git a/device-detail-preload.js b/device-detail-preload.js deleted file mode 100644 index 0fd52f2..0000000 --- a/device-detail-preload.js +++ /dev/null @@ -1,32 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron') -const path = require('path') - -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") { - ele = ele.parentElement; - } - return ele; -} - -function enableDoubleClick(ele) { - ele.ondblclick = function (ev) { - current = selectTopDiv(ev.target); - } -} - -function enableRightClick(ele) { - ele.oncontextmenu = function (ev) { - current = selectTopDiv(ev.target); - } -} - -ipcRenderer.on('type', (event, arg) => { - console.log(arg) - document.getElementById("canvas").innerHTML += "

"; -}); \ No newline at end of file diff --git a/device-detail.html b/device-detail.html deleted file mode 100644 index cfb885c..0000000 --- a/device-detail.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Device detail - - - -
-
- - - \ 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/main.js b/main.js deleted file mode 100644 index 1503027..0000000 --- a/main.js +++ /dev/null @@ -1,233 +0,0 @@ -const { dialog, app, BrowserWindow, ipcMain, Menu } = require('electron'); -const path = require('path'); -const fs = require('fs'); -let win -let filePath = ""; - -// Autoupdater from https://samuelmeuli.com/blog/2019-04-07-packaging-and-publishing-an-electron-app/ -const { autoUpdater } = require("electron-updater") - -app.on("ready", () => { - autoUpdater.checkForUpdatesAndNotify(); -}); - -const menuTemplate = [ - { role: 'appMenu' }, - { - label: 'File', - submenu: [ - { - label: 'Load', - accelerator: 'CmdOrCtrl+O', - click: () => loadFile(), - }, - { - label: 'Save', - accelerator: 'CmdOrCtrl+S', - click: () => win.webContents.send('file', { function: 'save' }), - }, - { - label: 'Save as', - click: () => win.webContents.send('file', { function: 'saveas' }), - }, - { - label: 'Export documentation', - submenu: [ - { - label: 'PDF', - accelerator: 'Shift+CmdOrCtrl+E', - } - ] - } - ] - }, - { - label: 'Add', - submenu: [ - { - label: 'Behringer', - submenu: [ - { - label: 'X32', - }, - { - label: 'X32 Compact', - accelerator: 'CmdOrCtrl+M', - click: () => win.webContents.send('menu', 'x32c'), - }, - { - label: 'X32 Producer' - }, - { - label: 'X32 Rack' - }, - { - label: 'X32 Core' - }, - { type: 'separator' }, - { - label: 'SD8', - click: () => win.webContents.send('menu', 'sd8'), - }, - { - label: 'SD16', - click: () => win.webContents.send('menu', 'sd16'), - }, - { - label: 'S32' - } - ] - }, - { - label: 'Midas', - submenu: [ - { - label: 'M32 Live' - }, - { - label: 'M32R' - }, - { - label: 'M32R Live' - }, - { - label: 'M32C' - }, - { type: 'separator' }, - { - label: 'DL16' - }, - { - label: 'DL32' - }, - { - label: 'DL231' - }, - { - label: 'DL251' - } - ] - } - ] - }, - { role: 'viewMenu' } -]; -const menu = Menu.buildFromTemplate(menuTemplate) - -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'); -}; - -app.whenReady().then(() => { - if (!app.isPackaged) - { - process.env.NODE_ENV = 'development'; - } - - Menu.setApplicationMenu(menu); - createWindow(); - - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - Menu.setApplicationMenu(menu); - createWindow(); - } - }); -}); - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } -}); - -var childWindow; - -ipcMain.on('window', (event, arg) => { - createChildWindow("device-detail.html", "device-detail-preload.js") - childWindow.webContents.send('type', arg) -}) - -ipcMain.on('file', (event, arg) => { - if ('saveas' == arg.function || ('save' == arg.function && filePath == "")) { - dialog.showSaveDialog({ - title: 'Save Mixo project', - filters: [ - { name: 'Mixo project', extensions: ['mixo_prj'] }, - { name: 'All Files', extensions: ['*'] } - ] - }).then(result => { - if (!result.canceled) { - filePath = result.filePath; - win.setTitle('Mixo • ' + filePath.replace(/^.*[\\\/]/, '').slice(0, -9)); - // Write the JSON to the chosen file - fs.writeFile(result.filePath, arg.json, (err) => { - if (err) throw err; - }); - } - }).catch(err => { - console.log(err); - }); - } - else if ('save' == arg.function && filePath != "") { - fs.writeFile(filePath, arg.json, (err) => { - if (err) throw err; - }) - } -}) -function loadFile() { - dialog.showOpenDialog({ - title: 'Open Mixo project', - filters: [ - { name: 'Mixo project', extensions: ['mixo_prj'] }, - { name: 'All Files', extensions: ['*'] } - ], - properties: ['openFile'] - }).then(result => { - if (!result.canceled) { - filePath = result.filePaths[0]; - win.setTitle('Mixo • ' + filePath.replace(/^.*[\\\/]/, '').slice(0, -9)); - // Read the chosen file - fs.readFile(result.filePaths[0], 'utf-8', (err, data) => { - if (err) throw err; - // Parse the JSON data - let jsonData = JSON.parse(data); - // Extract the arrays - win.webContents.send('file', { - function: 'load', - devices: jsonData.devices, - links: jsonData.links - }); - }); - } - }).catch(err => { - console.log(err); - }); -} - -// function to create a child window -function createChildWindow(fileName, preloadFileName) { - childWindow = new BrowserWindow({ - width: 700, - height: 500, - menuBarVisible: false, - autoHideMenuBar: true, - webPreferences: { - preload: path.join(__dirname, preloadFileName), - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true, - } - }) - childWindow.loadFile(fileName) -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 49f8eea..a8914fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,21 @@ { "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", - "jquery": "^3.7.0" + "jquery": "^3.7.0", + "serialize-javascript": "^6.0.2" }, "devDependencies": { "@electron-forge/cli": "^6.0.5", @@ -343,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", @@ -981,9 +983,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.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "dependencies": { "undici-types": "~5.26.4" } @@ -1008,9 +1010,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 +1046,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,31 +1886,31 @@ } }, "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.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.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.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" } }, "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" @@ -1916,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" @@ -2174,9 +2181,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" @@ -2285,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" @@ -2300,9 +2307,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.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", @@ -2729,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" } @@ -2777,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, @@ -2787,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": { @@ -3536,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" @@ -3643,9 +3651,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" @@ -4033,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", @@ -4265,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", @@ -4316,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", @@ -4421,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" }, @@ -4718,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" @@ -5100,34 +5081,34 @@ "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.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "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": { - "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" @@ -5358,6 +5339,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", @@ -5703,7 +5692,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", @@ -5740,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" }, @@ -5786,6 +5771,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", @@ -5856,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", @@ -6105,9 +6098,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 +6293,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.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", @@ -6528,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/package.json b/package.json index 30b23af..cdc90f5 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,10 +43,12 @@ }, "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", - "jquery": "^3.7.0" + "jquery": "^3.7.0", + "serialize-javascript": "^6.0.2" }, "build": { "extraResources": [ diff --git a/preload.js b/preload.js deleted file mode 100644 index a81daa9..0000000 --- a/preload.js +++ /dev/null @@ -1,283 +0,0 @@ -const { contextBridge, ipcRenderer } = require('electron'); -const { fs } = require('fs'); -const path = require('path') - -// Uncomment for npm start command -// process.env.NODE_ENV = 'development' - -const publicPath = - process.env.NODE_ENV === 'development' - ? './public' - : path.join(process.resourcesPath, 'public'); -const imagesPath = path.join(publicPath, "assets", "images"); - -const AES50 = { - A: "A", - 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, - chrome: () => process.versions.chrome, - electron: () => process.versions.electron, - ping: () => ipcRenderer.invoke('ping'), - // we can also expose variables, not just functions -}) -*/ - -let devices = []; -let links = []; -let selectedElement = null; -let originX, originY, mouseX, mouseY; -let idCnt = 0; - -function id2index(id, list) { - return list.findIndex((element) => Number(element.id) === Number(id)); -} - -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(); - }) -} - -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; - console.log(deviceId) - devices[id2index(deviceId, devices)].name = ev.target.value; - console.log(devices) - }); -} - -window.addEventListener("mousemove", (ev) => { - drawLine() - if (selectedElement == null) return; - if (selectedElement.classList.contains('AES50')) { - fromID = selectedElement.parentElement.id - fromAES50 = selectedElement.classList[1] - xoffset = fromAES50 == AES50.A ? 58 : 78; - document.getElementById("lines").innerHTML = ""; - links.forEach(link => { - document.getElementById("lines").innerHTML += link.show(); - }) - } - 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 = 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[id2index(fromID, devices)].id, selectedElement.classList[1], devices[id2index(toID, devices)].id, target.classList[1])) - } - drawLine(); - } - else if (selectedElement.classList.contains('device')) { - index = 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[current.id].type); - } -} - -function enableRightClick(ele) { - ele.oncontextmenu = function (ev) { - current = selectTopDiv(ev.target); - devices[id2index(current.id, devices)].delete(); - devices.splice(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)); - }) - draw(); - } -}) \ No newline at end of file diff --git a/public/assets/icon.png b/public/assets/icon.png new file mode 100644 index 0000000..9aa4f9a Binary files /dev/null and b/public/assets/icon.png differ diff --git a/public/assets/icons b/public/assets/icons new file mode 160000 index 0000000..5ffada4 --- /dev/null +++ b/public/assets/icons @@ -0,0 +1 @@ +Subproject commit 5ffada450486a88e236b773dad8d60ce3b3981d9 diff --git a/public/assets/images/SD16.svg b/public/assets/images/SD16.svg index 1e133ee..c96a94e 100644 --- a/public/assets/images/SD16.svg +++ b/public/assets/images/SD16.svg @@ -1,319 +1,185 @@ - + + - - - - - - + + + 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 + + - - - - - - + - - + - - + - - + \ No newline at end of file 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 + + + + + + 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 0000000..0753ea7 Binary files /dev/null and b/public/assets/images/Screenshot 2024-03-19 132902.png differ 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.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/src/device-detail.html b/src/device-detail.html new file mode 100644 index 0000000..6965539 --- /dev/null +++ b/src/device-detail.html @@ -0,0 +1,69 @@ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..a29ee0d --- /dev/null +++ b/src/index.html @@ -0,0 +1,31 @@ + + + + + + + Mixo + + + +
+ +
+ + + + + + + + + + + + + + + + \ 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..9caf4af --- /dev/null +++ b/src/js/beans/connector.js @@ -0,0 +1,68 @@ +class Connector { + constructor() { + this._name = ""; + this._color = "OFF"; + this._colorInvert = false; + this._icon = "1"; + } + + // MARK: RW properties + /*----- Read/Write properties ---------------------------------------------------------------------------------------------*/ + + setName(name) { + this._name = name.slice(0, 12); + } + + getName() { + return this._name; + } + + setColor(color) { + this._color = color; + } + + getColor() { + return this._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" : ""; + } + + getIcon() { + return this._icon; + } + + setIcon(icon) { + this._icon = icon; + } + + 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..949a5f0 --- /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..5f9c13e --- /dev/null +++ b/src/js/const.js @@ -0,0 +1,75 @@ +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", 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 => { + 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 new file mode 100644 index 0000000..7460199 --- /dev/null +++ b/src/js/ctrl/deviceDetailCtrl.js @@ -0,0 +1,228 @@ +const { ipcRenderer } = require("electron"); + +const constants = new Const(); +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 + /** + * 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) => { + $('#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); + }); + } + + /** + * Save button pressed + */ + saveConnector() { + 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) { + this.deviceID = arg.id; + this.dataUpdated(arg); + } + + /** + * Data changes received from main windows IPC + * @param {*} arg + */ + dataUpdated(arg) { + console.log(`[deviceDetailCtrl] got worker from main`); + indexWrk = constants.reconstructIndexWrk(arg.worker); + this.drawCanvas(indexWrk.getDeviceFromId(this.deviceID)); + } + + // MARK: Functions + + drawCanvas(device) { + console.log(`[deviceDetailCtrl] draw canvas`) + $('#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.selectedType = this.id.slice(3, 4); + _this.selectedIO = this.id.slice(5, 7); + _this.selectedDevice = device; + _this.openModal(); + }); + }); + + $.each($inputs, function (idx, val) { + var input = indexWrk.getDeviceFromId(_this.deviceID).inputs[val.id.slice(5, 7) - 1]; + var colors = constants.getColorCode(input.getColor()); + if ((input.getName() != "") || (input.getIcon() != "1") || (input.getColor() != "OFF")) { + val.querySelector('#connector').innerHTML = + "" + + ""; + } + }); + $.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 = + "" + + ""; + } + }); + }); + } + + 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(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-invert').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 new file mode 100644 index 0000000..f887b69 --- /dev/null +++ b/src/js/ctrl/indexCtrl.js @@ -0,0 +1,295 @@ +const { ipcRenderer } = require("electron"); + +const constants = new Const(); + +class IndexCtrl { + + // MARK: Constructor + constructor() { + /* ----- IPC ----- */ + ipcRenderer.on('menu', (event, arg) => { + this.menuClick(arg); + }) + 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); + }) + 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(`[indexCtrl] 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); + } + } + + /** + * Right click event -> delete device + * @param {Event} e + */ + deleteDevice(e) { + let id = $(e.target).parent().attr('deviceid'); + indexWrk.removeDeviceId(id); + this.selectedElement = {}; + } + + /** + * Open window with device detail + * @param {Event} e + */ + openDeviceDetail(e) { + let id = $(e.target).parent().attr('deviceid'); + ipcRenderer.send('window', { id: id, worker: indexWrk }); + } + + /** + * 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) { + 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); + } + } + + /** + * Data changes received from child windows IPC + * @param {*} arg + */ + requestDataChanges(arg) { + console.log(`[indexCtrl] got worker from child`); + indexWrk = constants.reconstructIndexWrk(arg.worker); + 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 + * @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); + } + ipcRenderer.send('forward-to-childs', { worker: indexWrk }); + } + + /** + * 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/src/js/device-detail-preload.js b/src/js/device-detail-preload.js new file mode 100644 index 0000000..c242b1c --- /dev/null +++ b/src/js/device-detail-preload.js @@ -0,0 +1,115 @@ +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") { + ele = ele.parentElement; + } + 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); + } +} + +function enableRightClick(ele) { + ele.oncontextmenu = function (ev) { + current = selectTopDiv(ev.target); + } +} + +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 += "
"; + + 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/src/js/device-detail-renderer.js b/src/js/device-detail-renderer.js new file mode 100644 index 0000000..b844123 --- /dev/null +++ b/src/js/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/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..d0964a4 --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,394 @@ +const { dialog, app, BrowserWindow, ipcMain, Menu } = require('electron'); +const join = require('path').join; +const fs = require('fs'); +const openAboutWindow = require('about-window').default; +const isMac = process.platform === 'darwin' +let win +let filePath = ""; + +// Autoupdater from https://samuelmeuli.com/blog/2019-04-07-packaging-and-publishing-an-electron-app/ +const { autoUpdater } = require("electron-updater") + +app.on("ready", () => { + autoUpdater.checkForUpdatesAndNotify(); +}); + +// MARK: Menu template +const menuTemplate = [ + // { 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: [ + { + label: 'Load', + accelerator: 'CmdOrCtrl+O', + click: () => loadFile(), + }, + { + label: 'Save', + accelerator: 'CmdOrCtrl+S', + click: () => win.webContents.send('file', { function: 'save' }), + }, + { + label: 'Save as', + click: () => win.webContents.send('file', { function: 'saveas' }), + }, + { + label: 'Export documentation', + submenu: [ + { + label: 'PDF', + 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' } + ]) + ] + }, + { + label: 'Add', + submenu: [ + { + label: 'Behringer', + submenu: [ + { + label: 'X32', + }, + { + label: 'X32 Compact', + accelerator: 'CmdOrCtrl+M', + click: () => win.webContents.send('menu', { action: 'add', type: 'x32c' }), + }, + { + label: 'X32 Producer' + }, + { + label: 'X32 Rack' + }, + { + label: 'X32 Core' + }, + { type: 'separator' }, + { + label: 'SD8', + click: () => win.webContents.send('menu', { action: 'add', type: 'sd8' }), + }, + { + label: 'SD16', + accelerator: 'CmdOrCtrl+Shift+M', + click: () => win.webContents.send('menu', { action: 'add', type: 'sd16' }), + }, + { + label: 'S32' + } + ] + }, + { + label: 'Midas', + submenu: [ + { + label: 'M32 Live' + }, + { + label: 'M32R' + }, + { + label: 'M32R Live' + }, + { + label: 'M32C' + }, + { type: 'separator' }, + { + label: 'DL16' + }, + { + label: 'DL32' + }, + { + label: 'DL231' + }, + { + label: 'DL251' + } + ] + } + ] + }, + // { 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: { + nodeIntegration: true, + contextIsolation: false, + }, + }); + win.loadFile(join(__dirname, '..', 'index.html')); +}; + +app.whenReady().then(() => { + if (!app.isPackaged) + { + process.env.NODE_ENV = 'development'; + } + + Menu.setApplicationMenu(menu); + createWindow(); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + Menu.setApplicationMenu(menu); + createWindow(); + } + }); +}); + +app.on('window-all-closed', () => { + // if (process.platform !== 'darwin') { + app.quit(); + // } +}); + +var childWindows = []; + +// MARK: IPC events +ipcMain.on('window', (event, arg) => { + createChildWindow(join(__dirname, '..', 'device-detail.html')); + 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) => { + if ('saveas' == arg.function || ('save' == arg.function && filePath == "")) { + dialog.showSaveDialog({ + title: 'Save Mixo project', + filters: [ + { name: 'Mixo project', extensions: ['mixo_prj'] }, + { name: 'All Files', extensions: ['*'] } + ] + }).then(result => { + if (!result.canceled) { + filePath = result.filePath; + win.setTitle('Mixo • ' + filePath.replace(/^.*[\\\/]/, '').slice(0, -9)); + // Write the JSON to the chosen file + fs.writeFile(result.filePath, arg.json, (err) => { + if (err) throw err; + }); + } + }).catch(err => { + console.log(err); + }); + } + else if ('save' == arg.function && filePath != "") { + fs.writeFile(filePath, arg.json, (err) => { + if (err) throw err; + }) + } +}) + +// MARK: IPC windows +ipcMain.on('forward-to-main', (event, arg) => { + win.webContents.send('request-data-changes', arg); +}); + +ipcMain.on('forward-to-childs', (event, arg) => { + childWindows.forEach(childWindow => { + childWindow.webContents.send('new-data', arg); + }); +}); + +// MARK: Functions +function loadFile() { + dialog.showOpenDialog({ + title: 'Open Mixo project', + filters: [ + { name: 'Mixo project', extensions: ['mixo_prj'] }, + { name: 'All Files', extensions: ['*'] } + ], + properties: ['openFile'] + }).then(result => { + if (!result.canceled) { + filePath = result.filePaths[0]; + win.setTitle('Mixo • ' + filePath.replace(/^.*[\\\/]/, '').slice(0, -9)); + // Read the chosen file + fs.readFile(result.filePaths[0], 'utf-8', (err, data) => { + if (err) throw err; + // Parse the JSON data + let jsonData = JSON.parse(data); + // Extract the arrays + win.webContents.send('file', { + function: 'load', + jsonData: jsonData + }); + }); + } + }).catch(err => { + console.log(err); + }); +} + +// function to create a child window +function createChildWindow(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() { + 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", + }) +} diff --git a/src/js/main/deviceDetailMain.js b/src/js/main/deviceDetailMain.js new file mode 100644 index 0000000..7c19349 --- /dev/null +++ b/src/js/main/deviceDetailMain.js @@ -0,0 +1,4 @@ +$(document).ready(function () { + deviceDetailCtrl = new DeviceDetailCtrl(); + indexWrk = new IndexWrk(); +}); diff --git a/src/js/main/indexMain.js b/src/js/main/indexMain.js new file mode 100644 index 0000000..c8268c6 --- /dev/null +++ b/src/js/main/indexMain.js @@ -0,0 +1,4 @@ +$(document).ready(function () { + indexCtrl = new IndexCtrl(); + indexWrk = new IndexWrk(); +}); 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..97c6780 --- /dev/null +++ b/src/js/wrk/indexWrk.js @@ -0,0 +1,206 @@ +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)); + } + 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 + 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); + } + + 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 + */ + 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; + } + } + + /** + * 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)}`); + 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); + } + } + + /** + * Update worker and canvas + */ + update() { + indexCtrl.drawCanvas(this.devices, this.links); + } +} + +// 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)); +} \ No newline at end of file diff --git a/src/styles/style.css b/src/styles/style.css new file mode 100644 index 0000000..9246ced --- /dev/null +++ b/src/styles/style.css @@ -0,0 +1,328 @@ +html, +body { + margin: 0; + min-height: 100%; + overflow: hidden; + font-family: sans-serif; +} + +#canvas { + min-width: 100%; + min-height: 100vh; +} + +.device { + width: min-content; + display: inline-block; + position: absolute; + padding-top: 5px; + user-select: none; +} + +.device:hover { + cursor: move; +} + +.device input { + width: 100%; + border: none; + /*border-bottom: 1px solid gray;*/ + text-align: center; + background: transparent; +} + +.device .AES50 { + border: 1px solid black; + background: white; + height: 1em; + width: 1em; + text-align: center; + font-family: Arial, Helvetica, sans-serif; + position: absolute; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: crosshair !important; +} + +.device .AES50:hover { + background: black; + color: white; +} + +.deviceimg object { + pointer-events: none; +} + +.x32c object { + width: 260px; +} + +.sd16 object, .sd8 object { + width: 200px; +} + +.detail { + width: 100% !important; + height: 100% !important; + padding-top: 0px !important; +} + +#canvas svg { + height: 100%; + width: 100%; +} + +.lines { + position: absolute; + z-index: -1; +} + +.line { + stroke-width: 3px; + stroke: black; + fill: transparent; +} + +/* MODAL STYLE */ +.modal { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.4rem; + width: 450px; + padding: 1.3rem; + min-height: 200px; + position: absolute; + top: 15vh; + 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-title { + margin: 0px 0px 10px 0px; +} + +.modal .flex { + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal input, .modal select { + 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; +} + +.buttons { + margin-top: 10px; +} + +.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; + border: 2px solid black; + flex-grow: 3; +} + +.cancel { + background-color: white; + color: black; + flex-grow: 1 !important; +} + +.btn-open { + position: absolute; + bottom: 150px; +} + +.btn-close { + transform: translate(0px, -7px); + padding: 0.5rem 0.7rem; + 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; +} + +/* Columns */ +.column-container { + display: flex; + width: 100%; + gap: 10px; +} + +.column { + flex-grow: 1; +} + +/* CUSTOM DROPDOWN */ +.dropdown-wrapper { + width: 100%; + height: 45px; + float: left; + position: relative; + font-size: 0.9em; + flex-grow: 1; +} + +.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; + text-align: left; + display: flex; + gap: 10px; + align-items: center; +} + +.trigger-dropdown:hover { + background-color:#eee; +} + +.trigger-dropdown .fa-caret-down { + float: right; + line-height: 22px; +} + +.trigger-dropdown svg { + width: 22px; + min-width: 22px; + float: left; + height: 22px; + 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; + line-height: normal; + flex-grow: 1; + padding-top: 2px; +} + +.dropdown-menu { + 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: calc(100vh - 30vh - 150px); + min-height: 100px; + overflow-y:scroll; + border: 1px solid #ddd; + border-radius: 5px; + flex-wrap: wrap; + 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; +} + +.dropdown-item svg { + width: 22px; + min-width: 22px; + min-height: 22px; + height: 22px; +} + +.dropdown-item .icon { + width: 22px; + min-width: 22px; + min-height: 22px; + height: 22px; +} + +.dropdown-item { + width: 33.33%; + height: 45px; + line-height: 25px; + border: 0; + padding: 0.7rem; + cursor: pointer; + transition:0.2s ease-in; + background-color:#fff; + color: #5a616c; + text-align: left; +} + +.dropdown-item span { + margin: 0px; + line-height: normal; + padding-top: 2px; +} + +.dropdown-item:hover { + background-color:#e5e5e5; +} diff --git a/style.css b/style.css deleted file mode 100644 index e4445d5..0000000 --- a/style.css +++ /dev/null @@ -1,76 +0,0 @@ -html, -body { - height: 100%; - margin: 0; -} - -#canvas { - min-width: 100%; - min-height: 100%; -} - -.device { - width: min-content; - display: inline-block; - position: absolute; - padding-top: 5px; -} - -.device:hover { - cursor: move; -} - -.device input { - width: 100%; - border: none; - /*border-bottom: 1px solid gray;*/ - text-align: center; - background: transparent; -} - -.device .AES50 { - border: 1px solid black; - background: white; - height: 1em; - width: 1em; - text-align: center; - font-family: Arial, Helvetica, sans-serif; - position: absolute; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: crosshair !important; -} - -.device .AES50:hover { - background: black; - color: white; -} - -.x32c img { - width: 260px; -} - -.sd16 img, .sd8 img { - width: 200px; -} - -.detail { - width: 100% !important; - height: 100% !important; -} - -#canvas svg { - height: 100%; - width: 100%; -} - -.lines { - position: absolute; - z-index: -1; -} - -.line { - stroke-width: 3px; -} \ No newline at end of file