diff --git a/Makefile b/Makefile index e2ee693ed83ea3eaba85fc86bb9f39cec2cbf59c..ba84005da40704b16d35b6a5a9b2da23ad25192b 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,6 @@ debug: $(YARN) test --no-sandbox style: - $(YARN) prettier --write instance common style client test >/dev/null + $(YARN) prettier --write instance common style client test main.js >/dev/null .PHONY: test debug run prepare style diff --git a/client/main.js b/client/main.js index 2d1d9db1973ba58428dff9bcb281f47c9d1ad94a..ab07310d81e38da80f1b8d2aa85fc4decbfafb15 100644 --- a/client/main.js +++ b/client/main.js @@ -1,20 +1,21 @@ -const logger = require('../common/logger.js'); +const rootDirectory = `${__dirname}/..`; +const logger = require(rootDirectory + '/common/logger.js'); const express = require('express'); -const port = 3000; +const packageConfig = require(rootDirectory + '/package.json').config; function launcher() { const app = express(); - logger.debug('express', `Express started on localhost:${port}`); + logger.debug('express', `Express started on localhost:${packageConfig.port}`); var publicRouter = require('./routes/public.js'); app.set('views', `${__dirname}/views`); app.set('view engine', 'ejs'); - app.use(express.static(`${__dirname}/../style`)); + app.use(express.static(`${rootDirectory}/style`)); app.use('/', publicRouter); - app.listen(port, () => { - logger.info('express', `Express listening on port ${port}`); + app.listen(packageConfig.port, () => { + logger.info('express', `Express listening on port ${packageConfig.port}`); }); } diff --git a/common/config.js b/common/config.js index 18263cb1922a00fb0a942bf2d828bafd0dceac4f..2a3552431d3018e76cbb43f256584aa408302125 100644 --- a/common/config.js +++ b/common/config.js @@ -2,6 +2,8 @@ const ini = require('ini'), os = require('os'), fs = require('fs'); +const packageConfig = require('../package.json').config; + const __configDir = `${os.homedir()}/.config/lektor`; const __configFile = `${__configDir}/amadeus.ini`; @@ -18,6 +20,7 @@ var config = { loglevel: null, loglektord: null, attachmode: null, + clientport: null, reload() { var fd = fs.openSync(__configFile, 'a'); fs.close(fd, err => {}); @@ -25,8 +28,12 @@ var config = { __config.database ??= {}; __config.lektord ??= {}; + __config.kurisu ??= {}; + __config.client ??= {}; __config.log ??= {}; + __config.kurisu.url ??= packageConfig.kurisu; + __config.client.port ??= packageConfig.port; __config.log.level ??= 'debug'; __config.log.lektord ??= true; __config.database.path ??= '/home/kara/kara.db'; @@ -34,6 +41,8 @@ var config = { __config.lektord.port ??= '6600'; __config.lektord.attach ??= true; + this.clientport = __config.client.port; + this.kurisuurl = __config.kurisu.url; this.database = __config.database.path; this.host = __config.lektord.host; this.port = __config.lektord.port; @@ -47,9 +56,15 @@ var config = { }, flush() { const __config = { + kurisu: { + url: this.kurisuurl, + }, database: { path: this.database, }, + client: { + port: this.clientport, + }, lektord: { host: this.host, port: this.port, diff --git a/common/db.js b/common/db.js index 1e795a30bbb803435a1809d45fc93dc15d7944e0..138ae129e5eb27f572125c38f6d7fe0ff6fd415c 100644 --- a/common/db.js +++ b/common/db.js @@ -123,6 +123,42 @@ class KaraDatabase { return __getRecords(this.m_db); } + /* List all the next karas in the queue. + * => a promize, callback with [(id: Int, string, cat, type, language, + * author, title, source, position: Int)] */ + queueAll() { + var __ret = []; + let __sqlQuery = `WITH content AS ( + SELECT kara.id AS id, string, + category AS cat, + (song_type || song_number) AS type, + language, author_name AS author, + song_name AS title, source_name AS source, + position + FROM queue + JOIN kara ON kara_id = kara.id + GROUP BY position ORDER BY position ASC, priority DESC) + SELECT id, string, position, cat, type, language, author, source, title + FROM content;`; + + function __getRecords(db) { + return new Promise(resolv => { + db.all(__sqlQuery, [], (err, rows) => { + if (err) { + logger.error('db', err); + throw err; + } + rows.forEach(row => { + __ret.push(row); + }); + resolv(__ret); + }); + }); + } + + return __getRecords(this.m_db); + } + /* List all the next karas in the queue. * - first: Integer => the first kara to consider being in the queue (the * current one, starts ad 0) diff --git a/common/isRunning.js b/common/isRunning.js new file mode 100644 index 0000000000000000000000000000000000000000..7ef4684696e1acb08c11b06688ed21f95f914ad8 --- /dev/null +++ b/common/isRunning.js @@ -0,0 +1,26 @@ +function isRunning(arg) { + const win = arg.win; + const mac = arg.mac; + const linux = arg.linux; + + return new Promise(function (resolve, reject) { + const exec = require('child_process').exec; + const plat = process.platform; + + /* prettier-ignore */ + const cmd = plat == 'win32' ? 'tasklist' + : plat == 'darwin' ? 'ps -ax | grep ' + mac + : plat == 'linux' ? 'ps -A' + : ''; + + const proc = plat == 'win32' ? win : plat == 'darwin' ? mac : plat == 'linux' ? linux : ''; + if (cmd === '' || proc === '') { + resolve(false); + } + exec(cmd, function (err, stdout, stderr) { + resolve(stdout.toLowerCase().indexOf(proc.toLowerCase()) > -1); + }); + }); +} + +module.exports = isRunning; diff --git a/common/lkt.js b/common/lkt.js index 2b0eb9006a3a87f02add9335e4395610c839cb87..a87d535061139a4cc6ebd6a7a1ea56e0a7fa7f2f 100644 --- a/common/lkt.js +++ b/common/lkt.js @@ -146,7 +146,7 @@ class LktClient { return new Promise(resolv => { client.m_socket.on('data', data => { if (!once) { - client.m_socket.write(`${command}\n`); + client.m_socket.write(`command_list_begin\n${command}\ncommand_list_end\n`); once = true; return null; } else { diff --git a/instance/index.ejs b/instance/index.ejs new file mode 100644 index 0000000000000000000000000000000000000000..e874260257035d6f605f535d6f3690ee3badd2ad --- /dev/null +++ b/instance/index.ejs @@ -0,0 +1,26 @@ +<%# vim: ts=4 syntax=html + The template for the main page %> +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8" /> + <title><%= app %></title> + <script> + window.$ = window.jQuery = require('../style/jquery/jquery-3.5.1.js'); + </script> + <script src="../style/bootstrap-4.5.2-dist/js/bootstrap.min.js"></script> + <link rel="stylesheet" href="../style/bootstrap-4.5.2-dist/css/bootstrap.min.css" /> + <link rel="stylesheet" href="../style/css/instance.css" /> + <link rel="stylesheet" href="../style/fontawesome-free-5.15.1-web/css/all.min.css" /> + <script src="./main.js"></script> + <script></script> + </head> + <body> + <%- include('views/settings.ejs'); %> + <div id="mainFrame" class="container-fluid"> + <%- include('views/menubar.ejs'); %> + <%- include('views/panels.ejs'); %> + <%- include('views/progressBar.ejs'); %> + </div> + </body> +</html> diff --git a/instance/index.html b/instance/index.html deleted file mode 100644 index 4df238530db9f5e46a911e408f6491a5b1b30c56..0000000000000000000000000000000000000000 --- a/instance/index.html +++ /dev/null @@ -1,188 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset="UTF-8" /> - <title>Amadeus</title> - <script> - window.$ = window.jQuery = require('../style/jquery/jquery-3.5.1.js'); - </script> - <script src="../style/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js"></script> - <link rel="stylesheet" href="../style/bootstrap-4.5.2-dist/css/bootstrap.min.css" /> - <link rel="stylesheet" href="../style/css/instance.css" /> - <link rel="stylesheet" href="../style/fontawesome-free-5.15.1-web/css/all.min.css" /> - <script src="./main.js"></script> - <script></script> - </head> - <body> - <div id="modal-settings" class="modal bd-example-modal-lg" tabindex="-1" role="dialog"> - <div class="modal-dialog modal-lg modal-dialog-centered" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h2 class="modal-title">Amadeus settings</h2> - <button type="button" class="btn btn-outline-primary btn-lg modal-close" aria-label="Close"> - <i class="fas fa-window-close"></i> - </button> - </div> - <div class="modal-body"> - <form> - <h3>Lektord settings</h3> - <p>Configure which lektord to use or if 'attach' mode is enabled.</p> - <div class="form-group row"> - <label for="inputHost" class="col-sm-2 col-form-label">Host</label> - <div class="col-sm-10"> - <input type="text" class="form-control" id="inputHost" placeholder="Host..." /> - </div> - </div> - <div class="form-group row"> - <label for="inputPort" class="col-sm-2 col-form-label">Port</label> - <div class="col-sm-10"> - <input type="int" class="form-control" id="inputPort" placeholder="6600" /> - </div> - </div> - <div class="form-group row"> - <div class="col-sm-2">Attach mode</div> - <div class="col-sm-10"> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="inputAttachMode" /> - <label class="form-check-label" for="inputAttachMode"> - Enable attach mode - </label> - </div> - </div> - </div> - <hr /> - - <h3>Database settings</h3> - <p>Which database to use, and other options.</p> - <div class="form-group row"> - <label for="inputDbPath" class="col-sm-2 col-form-label">Host</label> - <div class="col-sm-10"> - <input type="text" class="form-control" id="inputDbPath" placeholder="kara.db..." /> - </div> - </div> - <hr /> - - <h3>Log settings</h3> - <p>Log options, this is a developper section.</p> - <fieldset class="form-group"> - <div class="row"> - <legend class="col-form-label col-sm-2 pt-0">Log level</legend> - <div class="col-sm-10" id="inputLogLevel"> - <div class="form-check"> - <input - class="form-check-input" - type="radio" - name="gridRadios" - id="logERROR" - value="ERROR" - checked - /> - <label class="form-check-label" for="logERROR">Error</label> - </div> - <div class="form-check"> - <input - class="form-check-input" - type="radio" - name="gridRadios" - id="logWARNING" - value="WARNING" - /> - <label class="form-check-label" for="logWARNING">Warning</label> - </div> - <div class="form-check"> - <input - class="form-check-input" - type="radio" - name="gridRadios" - id="logINFO" - value="INFO" - /> - <label class="form-check-label" for="logINFO">Info</label> - </div> - <div class="form-check"> - <input - class="form-check-input" - type="radio" - name="gridRadios" - id="logDEBUG" - value="DEBUG" - /> - <label class="form-check-label" for="logDEBUG">Debug</label> - </div> - </div> - </div> - </fieldset> - <div class="form-group row"> - <div class="col-sm-2">Lektord logs</div> - <div class="col-sm-10"> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="inputLogLektord" /> - <label class="form-check-label" for="inputLogLektord"> - Enable lektord logs when possible - </label> - </div> - </div> - </div> - </form> - </div> - <div class="modal-footer"> For changes to be applied you need to restart Amadeus. </div> - </div> - </div> - </div> - - <div id="mainFrame" class="container-fluid"> - <div - id="buttonPanelListLeft" - class="d-flex flex-row bd-highlight mb-3 row card" - style="-webkit-app-region: drag" - ></div> - <div id="panelWrapper" class="row"> - <ul id="panelLeft" class="col panel d-flex flex-column bd-highlight mb-3"></ul> - <div id="panelRight" class="col panel d-flex flex-column bd-highlight mb-3"> - <ul class="nav nav-tabs mb-1" id="myTab" role="tablist"> - <li class="nav-item" role="presentation"> - <button class="nav-link active" id="queue-tab" data-toggle="tab" data-bs-target="#queue" type="button" role="tab" aria-controls="queue" aria-selected="true">Queue</button> - </li> - <li class="nav-item" role="presentation"> - <button class="nav-link active" id="playlist-tab" data-toggle="tab" data-bs-target="#playlist" type="button" role="tab" aria-controls="playlist" aria-selected="false">Playlist</button> - </li> - <li class="playlistChoice ml-4 mt-1"> - <select class="form-control bd-highlight" id="playlist-selector" aria-label="Default select example"> - <option selected>Select a playlist</option> - <option value="1">One</option> - <option value="2">Two</option> - <option value="3">Three</option> - </select> - </li> - </ul> - <div class="tab-content"> - <div id="queue-panel" class="tab-pane fade show active pl-0" role="tabpanel" aria-labelledby="queue-tab""> - <ul class="panel bd-highlight mb-3 pl-0" id="queueList"></ul> - </div> - <div id="playlist-panel" class="tab-pane fade" role="tabpanel" aria-labelledby="playlist-tab"> - <ul id="playlistList"> <head> Bonjour</head></ul> - </div> - </div> - </div> - </div> - <div - id="progress" - class="progress" - style="z-index: 666; position: fixed !important; right: 0; left: 0; bottom: 0" - > - <div - class="progress-bar" - id="progressBar" - role="progressbar" - style=" - -webkit-transition: none; - -moz-transition: none; - -ms-transition: none; - -o-transition: none; - transition: none; - " - ></div> - </div> - </div> - </body> -</html> diff --git a/instance/main.js b/instance/main.js index f6dd48e45dc569a69a523c2ff6418c2ada7fa26b..244ee5a203754fd5ca9a10f54212e1d4bc8882d6 100644 --- a/instance/main.js +++ b/instance/main.js @@ -5,24 +5,6 @@ const { ipcRenderer } = require('electron'), ejs = require('ejs'), e = require('express'); -/* prettier-ignore */ -const buttonList = [ - [ 'left', `<i class="fas fa-play"></i>`, 'commandPlay', 'Play or pause Lektor' ], - [ 'left', `<i class="fas fa-stop"></i>`, 'commandStop', 'Stop lektor' ], - [ 'left', `<i class="fas fa-eraser"></i>`, 'commandClear', 'Clear the queue' ], - - [ 'sleft', `<i class="fas fa-search"></i>`, 'selectAdvence', 'Advence search' ], - [ 'sleft', `<i class="fas fa-database"></i>`, 'selectDatabase', 'Database search' ], - [ 'sleft', `<i class="fas fa-tag"></i>`, 'selectPlaylist', 'Playlist search' ], - [ 'sleft', `<i class="fas fa-bookmark"></i>`, 'selectPool', 'Pool search' ], - - [ 'right', `<i class="fas fa-compass"></i>`, 'openKurisu', 'Kurisu in a new page' ], - [ 'right', `<i class="fas fa-user-friends"></i>`, 'openUserView', 'Open client view' ], - [ 'right', `<i class="fas fa-music"></i>`, 'openMdtView', 'Open metadata view' ], - [ 'right', `<i class="fas fa-sync-alt"></i>`, 'reloadQueue', 'Reload the queue' ], - [ 'right', `<i class="fas fa-window-close"></i>`, 'closeButton', 'Quit Amadeus' ], -]; - /* Global variables */ var countKaraInQueue = 0; var currentSong = 0; @@ -45,10 +27,14 @@ function autoFillSettings() { $('#inputDbPath').val(config.database); $('#inputLogLektord').prop('checked', config.loglektord); $(`#log${config.loglevel.toUpperCase()}`).prop('checked', true); + $('#inputKurisuUrl').val(config.kurisuurl); + $('#inputClientPort').val(config.clientport); } function flushFillSettings() { const set = { + kurisuurl: $('#inputKurisuUrl').val(), + clientport: $('#inputClientPort').val(), host: $('#inputHost').val(), port: $('#inputPort').val(), attachmode: $('#inputAttachMode').prop('checked'), @@ -65,8 +51,27 @@ function flushFillSettings() { } window.onload = () => { - createButtonList(buttonList); + /* Set things up for the settings modal*/ + var modal = document.getElementById('modal-settings'); + var btn = document.getElementById('btn-settings'); + var span = document.getElementsByClassName('modal-close')[0]; + + btn.onclick = function () { + modal.style.display = 'block'; + }; + span.onclick = function () { + modal.style.display = 'none'; + flushFillSettings(); + }; + + window.onclick = function (event) { + if (event.target == modal) { + modal.style.display = 'none'; + } + }; + + /* Add IPC to button list */ addIpcToButton('closeButton', ['close-app']); addIpcToButton('commandPlay', ['cmd-play']); @@ -81,20 +86,21 @@ window.onload = () => { addIpcToButton('openKurisu', ['toggle-kurisu']); addIpcToButton('openUserView', ['toggle-client-view']); - addIpcToButton('reloadQueue', ['reload-queue-request']); setTimeout(() => ipcRenderer.send('reload-queue-request'), 1000); //setTimeout(() => ipcRenderer.send('reload-playlist-request'),1000); setInterval(() => ipcRenderer.send('verify-queue-reloaded-request'), 50); //setInterval(() => ipcRenderer.send('verify-playlist-reloaded-request'),50); setInterval(() => ipcRenderer.send('get-song-time-data'), 50); + setInterval(() => ipcRenderer.send('get-runnings'), 10000); logger.debug('instance', 'Window loaded'); $('#filterInput').on('keypress', e => { /* On 'Return'. */ if (e.which != 13) return; + logger.debug('instance', `Send filter for: ${$('#filterInput').val()}`); ipcRenderer.send('reload-db-request', { - search: $('#filterInput').val(), + search: `${$('#filterInput').val()}`, }); }); @@ -102,8 +108,28 @@ window.onload = () => { 'keydown', e => { if (e.ctrlKey && e.keyCode == 70) { + /* ctrl + F */ logger.debug('instance', 'Focus find bar'); $('#filterInput').focus(); + } else if (e.ctrlKey && e.keyCode == 67) { + /* ctrl + C */ + logger.debug('instance', `Focus current kara in queue, id is "#pos-${currentSong + 1}"`); + $('#panelRight').animate( + { + scrollTop: 0, + }, + 0 + ); + var offset = $(window).height() / 2; + $('#panelRight').animate( + { + scrollTop: + $(`#pos-${currentSong + 1}`) + .parent() + .offset().top - offset, + }, + 0 + ); } }, false @@ -115,54 +141,6 @@ window.onload = () => { /* Create the button list */ function createButtonList(list) { - function action(ipc, argument) { - logger.error('instance', 'The action function builder for control buttons is not implemented'); - } - var renderHtmlLeft = ''; - var renderHtmlRight = ''; - var renderHtmlSLeft = ''; - list.forEach(btn => { - if (btn[0] == 'left') { - renderHtmlLeft = `${renderHtmlLeft} - <button id="${btn[2]}" type="button" class="btn btn-primary p-2 bd-highlight" - data-toggle="tooltip" data-placement="bottom" title="${btn[3]}" - style="-webkit-app-region: no-drag">${btn[1]}</button>`; - logger.debug('instance', `Create button for "${btn[1]}" with id "${btn[2]} at the left"`); - } else if (btn[0] == 'right') { - renderHtmlRight = `${renderHtmlRight} - <button id="${btn[2]}" type="button" class="btn btn-secondary p-2 bd-highlight" - data-toggle="tooltip" data-placement="bottom" title="${btn[3]}" - style="-webkit-app-region: no-drag">${btn[1]}</button>`; - logger.debug('instance', `Create button for "${btn[1]}" with id "${btn[2]} at the right"`); - } else if (btn[0] == 'sleft') { - renderHtmlSLeft = `${renderHtmlSLeft} - <button id="${btn[2]}" type="button" class="btn btn-secondary p-2 bd-highlight" - data-toggle="tooltip" data-placement="bottom" title="${btn[3]}" - style="-webkit-app-region: no-drag">${btn[1]}</button>`; - logger.debug('instance', `Create button for "${btn[1]}" with id "${btn[2]} at the right"`); - } else { - logger.warn('instance', `Unknown button type ${btn[0]} in top bar builder`); - } - }); - document.getElementById('buttonPanelListLeft').innerHTML = ` - <div class="btn-group" role="group">${renderHtmlLeft}</div> - <div class="btn-group" role="group">${renderHtmlSLeft}</div> - <div class="p-1 bd-highlight mb-1 mr-auto ml-auto" style="width: 60%;-webkit-app-region: no-drag;"> - <input id="filterInput" type="text" class="form-control filterInput" placeholder="Filter..."> - </div> - <div class="btn-group" role="group"> - <button id="btn-settings" type="button" class="btn btn-secondary p-2 bd-highlight" - style="-webkit-app-region: no-drag" data-toggle="tooltip" - data-placement="bottom" title="Open settings"> - <i class="fas fa-cogs"></i> - </button> - ${renderHtmlRight} - </div>`; - - /* Set things up for the settings modal*/ - var modal = document.getElementById('modal-settings'); - var btn = document.getElementById('btn-settings'); - var span = document.getElementsByClassName('modal-close')[0]; /* Setup queue/playlist tabls*/ var tabQueue = document.getElementById("queue-tab"); @@ -200,20 +178,7 @@ function createButtonList(list) { logger.info("cc",newValue); logger.info("cc",oldValue); }); - btn.onclick = function () { - modal.style.display = 'block'; - }; - - span.onclick = function () { - modal.style.display = 'none'; - flushFillSettings(); - }; - - window.onclick = function (event) { - if (event.target == modal) { - modal.style.display = 'none'; - } - }; + } /* Create the left panel */ @@ -223,9 +188,7 @@ ipcRenderer.on('reload-db-responce', (event, arg) => { arg.forEach(kara => { ejs.renderFile(__dirname + '/views/karaDBListItem.ejs', { kara: kara }, (err, data) => { if (err) logger.error('instance', err); - document.getElementById('panelLeft').innerHTML = ` - ${document.getElementById('panelLeft').innerHTML} - ${data}`; + $('#panelLeft').append(data); }); }); [].forEach.call(document.querySelectorAll('#panelLeft .karaCard'), addDBKaraEventHandlers); @@ -259,10 +222,17 @@ ipcRenderer.on('send-song-time-data', (event, timeData) => { } document.getElementsByClassName('karaQueue')[timeData.song].style.background = '#6b7d8e'; } else { - document.getElementsByClassName('karaQueue')[timeData.song].style.background = '#4e5d6c'; + if (document.getElementsByClassName('karaQueue')[timeData.song]) { + document.getElementsByClassName('karaQueue')[timeData.song].style.background = '#4e5d6c'; + } } }); +ipcRenderer.on('send-runnings', (event, arg) => { + $('#running-lektord').prop('checked', arg.lektord); + $('#running-klkt').prop('checked', arg.klkt); +}); + function addDBKaraEventHandlers(element) { element.addEventListener('dragstart', onDragStartDB, false); element.addEventListener('dragend', onDragEnd, false); @@ -312,8 +282,8 @@ function onDragStartQueue(event) { function onDragEnter(event) { if (event.currentTarget) { - if (leavedElement != event.currentTarget) { - leavedElement.style.borderTop = ''; + if (leavedElement != event.currentTarget && leavedElement) { + leavedElement.style.borderTop = ''; // style is undefined ! } event.currentTarget.style.borderTop = '3px solid grey'; dragCounter++; diff --git a/instance/views/kara.ejs b/instance/views/kara.ejs index cba6f7214336dca3e1d6864927d3756e05c47eba..d02ed28f57f54135df872021f00e8f3f924ed87c 100644 --- a/instance/views/kara.ejs +++ b/instance/views/kara.ejs @@ -5,5 +5,5 @@ <%= kara.type %> - <b><%= kara.title %></b> <i>[<%= kara.author %>]</i> </span> <% if (kara.hasOwnProperty('position')) { %> - <span class="karaElement p-3 bd-highlight karaPosition"><b><%= kara.position %></b></span> + <span id="pos-<%= kara.position %>" class="karaElement p-3 bd-highlight karaPosition"><b><%= kara.position %></b></span> <% } %> diff --git a/instance/views/menubar.ejs b/instance/views/menubar.ejs new file mode 100644 index 0000000000000000000000000000000000000000..b62e64c7b7db74428129ca6fc600a205ced284b1 --- /dev/null +++ b/instance/views/menubar.ejs @@ -0,0 +1,25 @@ +<%# vim: ts=4 syntax=html + The button list at the top of the window %> +<div id="buttonPanelListLeft" class="d-flex flex-row bd-highlight mb-3 row card" style="-webkit-app-region: drag"> + <div class="btn-group" role="group"><%- include('menubar/buttons.ejs', {type: 'primary', buttons: [ + {id: 'commandPlay', tooltip: 'Play or pause Lektor', name: 'play'}, + {id: 'commandStop', tooltip: 'Stop Lektor', name: 'stop'}, + {id: 'commandClear', tooltip: 'Clear the queue', name: 'eraser'}, + ]}); %></div> + <div class="btn-group" role="group"><%- include('menubar/buttons.ejs', {type: 'secondary', buttons: [ + {id: 'selectAdvence', tooltip: 'Advence search', name: 'search'}, + {id: 'selectDatabase', tooltip: 'Database search', name: 'database'}, + {id: 'selectPlaylist', tooltip: 'Playlist search', name: 'tag'}, + {id: 'selectPool', tooltip: "Kurisu's pool search", name: 'bookmark'}, + ]}); %></div> + <div class="p-1 bd-highlight mb-1 mr-auto ml-auto" style="width: 60%;-webkit-app-region: no-drag;"> + <input id="filterInput" type="text" class="form-control filterInput" placeholder="Filter..."> + </div> + <div class="btn-group" role="group"><%- include('menubar/buttons.ejs', {type: 'secondary', buttons: [ + {id: 'btn-settings', tooltip: 'Open settings', name: 'cogs'}, + {id: 'openKurisu', tooltip: 'Open Kurisu in a new page', name: 'compass'}, + {id: 'openUserView', tooltip: 'Open client view in a new page', name: 'user-friends'}, + {id: 'openMdtView', tooltip: 'Open metadata view', name: 'music'}, + {id: 'closeButton', tooltip: 'Quit Amadeus', name: 'window-close'}, + ]}); %></div> +</div> diff --git a/instance/views/menubar/buttons.ejs b/instance/views/menubar/buttons.ejs new file mode 100644 index 0000000000000000000000000000000000000000..4c6f60d891f093a944918c76fe6d67887dd1d364 --- /dev/null +++ b/instance/views/menubar/buttons.ejs @@ -0,0 +1,11 @@ +<%# vim: ts=4 syntax=html + The buttons for the menubar at the top of the window %> +<% buttons.forEach(function(btn){ %> +<button + id="<%= btn.id %>" type="button" + class="btn btn-<%= type %> p-2 bd-highlight" + data-toggle="tooltip" data-placement="bottom" title="<%= btn.tooltip %>" + style="-webkit-app-region: no-drag"> + <i class="fas fa-<%= btn.name %>"></i> +</button> +<% })%> diff --git a/instance/views/panels.ejs b/instance/views/panels.ejs new file mode 100644 index 0000000000000000000000000000000000000000..3c652594f9356cd61d5d6fc59a576097a81d4e09 --- /dev/null +++ b/instance/views/panels.ejs @@ -0,0 +1,40 @@ +<%# vim: ts=4 syntax=html + The template for the main panels %> +<div id="panelWrapper" class="row" style="margin-bottom: 13px"> + <ul + id="panelLeft" + style="padding: 0px; margin-right: 10px" + class="col panel d-flex flex-column bd-highlight mb-3" + ></ul> + <div + id="panelRight" + style="padding: 0px; margin-left: 10px" + class="col panel d-flex flex-column bd-highlight mb-3" + > + <ul class="nav nav-tabs mb-1" id="myTab" role="tablist"> + <li class="nav-item" role="presentation"> + <button class="nav-link active" id="queue-tab" data-toggle="tab" data-bs-target="#queue" type="button" role="tab" aria-controls="queue" aria-selected="true">Queue</button> + </li> + <li class="nav-item" role="presentation"> + <button class="nav-link active" id="playlist-tab" data-toggle="tab" data-bs-target="#playlist" type="button" role="tab" aria-controls="playlist" aria-selected="false">Playlist</button> + </li> + <li class="playlistChoice ml-4 mt-1"> + <select class="form-control bd-highlight" id="playlist-selector" aria-label="Default select example"> + <option selected>Select a playlist</option> + <option value="1">One</option> + <option value="2">Two</option> + <option value="3">Three</option> + </select> + </li> + </ul> + <div class="tab-content"> + <div id="queue-panel" class="tab-pane fade show active pl-0" role="tabpanel" aria-labelledby="queue-tab""> + <ul class="panel bd-highlight mb-3 pl-0" id="queueList"></ul> + </div> + <div id="playlist-panel" class="tab-pane fade" role="tabpanel" aria-labelledby="playlist-tab"> + <ul id="playlistList"> <head> Bonjour</head></ul> + </div> + </div> + </div> + +</div> diff --git a/instance/views/progressBar.ejs b/instance/views/progressBar.ejs new file mode 100644 index 0000000000000000000000000000000000000000..fefec9b813b67f153d00ebbcea4231cc21a6f295 --- /dev/null +++ b/instance/views/progressBar.ejs @@ -0,0 +1,7 @@ +<%# vim: ts=4 syntax=html + The template for the progress bar at the bottom of the screen %> +<div id="progress" class="progress" style="z-index: 666; position: fixed !important; right: 0; left: 0; bottom: 0; height: 14px"> + <div class="progress-bar" id="progressBar" role="progressbar" + style="-webkit-transition: none; -moz-transition: none; -ms-transition: none; -o-transition: none; transition: none;"> + </div> +</div> diff --git a/instance/views/settings.ejs b/instance/views/settings.ejs new file mode 100644 index 0000000000000000000000000000000000000000..226735c508bb1f7400b716f2317eb6640f942d6b --- /dev/null +++ b/instance/views/settings.ejs @@ -0,0 +1,56 @@ +<%# vim: ts=4 syntax=html + The template for the settings modal %> +<div id="modal-settings" class="modal bd-example-modal-lg" tabindex="-1" role="dialog" style="overflow-y: scroll"> + <div class="modal-dialog modal-lg modal-dialog-centered" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h2 class="modal-title">Amadeus settings</h2> + <button type="button" class="btn btn-outline-primary btn-lg modal-close" aria-label="Close"> + <i class="fas fa-window-close"></i> + </button> + </div> + <div class="modal-body"> + <form> + <h3>Running processes</h3> + <p>Here is a list of related processes that may run. The box is checked when the process is running.</p> + <%- include('settings/checkBox.ejs', {id: 'running-lektord', name: 'Lektord', disable: true}); %> + <%- include('settings/checkBox.ejs', {id: 'running-klkt', name: 'Klkt', disable: true}); %> + <hr /> + + <h3>Lektord settings</h3> + <p>Configure which lektord to use or if 'attach' mode is enabled.</p> + <%- include('settings/input.ejs', {id: 'inputHost', name: 'Host', type: 'text'}); %> + <%- include('settings/input.ejs', {id: 'inputPort', name: 'Port', type: 'int'}); %> + <%- include('settings/checkBox.ejs', {id: 'inputAttachMode', name: 'Attach mode', label: 'Enable attach mode'}); %> + <hr /> + + <h3>Client interface settings</h3> + <p>Express server settings for the client interface.</p> + <%- include('settings/input.ejs', {id: 'inputClientPort', name: 'Port', type: 'int'}); %> + <hr /> + + <h3>Database settings</h3> + <p>Which database to use, and other options.</p> + <%- include('settings/input.ejs', {id: 'inputDbPath', name: 'Database', type: 'text'}); %> + <hr /> + + <h3>Kurisu settings</h3> + <p>Where to find Kurisu.</p> + <%- include('settings/input.ejs', {id: 'inputKurisuUrl', name: 'URL', type: 'text'}); %> + <hr /> + + <h3>Log settings</h3> + <p>Log options, this is a developper section.</p> + <%- include('settings/radioButtons.ejs', {id: 'inputLogLevel', name: 'Log level', buttons: [ + {name: 'Error', value: 'ERROR', id: 'logERROR', checked: true}, + {name: 'Warning', value: 'WARNING', id: 'logWARNING'}, + {name: 'Info', value: 'INFO', id: 'logINFO'}, + {name: 'Debug', value: 'DEBUG', id: 'logDEBUG'} + ]}); %> + <%- include('settings/checkBox.ejs', {id: 'inputLogLektord', name: 'Lektord logs', label: 'Enable lektord logs when possible'}); %> + </form> + </div> + <div class="modal-footer"> For changes to be applied you need to restart Amadeus. </div> + </div> + </div> +</div> diff --git a/instance/views/settings/checkBox.ejs b/instance/views/settings/checkBox.ejs new file mode 100644 index 0000000000000000000000000000000000000000..19c871d58b6402d4cca8aaa55a6619f4b0d1cbdd --- /dev/null +++ b/instance/views/settings/checkBox.ejs @@ -0,0 +1,11 @@ +<%# vim: ts=4 syntax=html + The template for a checkbox setting %> +<div class="form-group row"> + <div class="col-sm-2"><%= name %></div> + <div class="col-sm-10"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" id="<%= id %>" <% if (typeof disable != 'undefined') { %> onclick="return false;" <% } %>/> + <label class="form-check-label" for="<%= id %>"><% if (typeof label != 'undefined') { %> <%= label %> <% } %></label> + </div> + </div> +</div> diff --git a/instance/views/settings/input.ejs b/instance/views/settings/input.ejs new file mode 100644 index 0000000000000000000000000000000000000000..390aac1512516c8d3f6e2224c63a674f2794dbe5 --- /dev/null +++ b/instance/views/settings/input.ejs @@ -0,0 +1,8 @@ +<%# vim: ts=4 syntax=html + The template for a text/int field setting %> +<div class="form-group row"> + <label for="<%= id %>" class="col-sm-2 col-form-label"><%= name %></label> + <div class="col-sm-10"> + <input type="<%= type %>" class="form-control" id="<%= id %>" placeholder="<%= name %>..." /> + </div> +</div> diff --git a/instance/views/settings/radioButtons.ejs b/instance/views/settings/radioButtons.ejs new file mode 100644 index 0000000000000000000000000000000000000000..1dbab580594d402202eb000a60cf6f8c9c3821a0 --- /dev/null +++ b/instance/views/settings/radioButtons.ejs @@ -0,0 +1,15 @@ +<%# vim: ts=4 syntax=html + The template for radio settings %> +<fieldset class="form-group"> + <div class="row"> + <legend class="col-form-label col-sm-2 pt-0"><%= name %></legend> + <div class="col-sm-10" id="<%= id %>"> + <% buttons.forEach(function(btn) { %> + <div class="form-check"> + <input class="form-check-input" type="radio" name="gridRadios" id="<%= btn.id %>" value="<%= btn.value %>" <% if (typeof btn.checked != 'undefined' && btn.checked) { %> checked <% } %> /> + <label class="form-check-label" for="<%= btn.id %>"><%= btn.name %></label> + </div> + <% }); %> + </div> + </div> +</fieldset> diff --git a/main.js b/main.js index 86115a67648a6bbd8f235fe6d8eb7f5516469713..6b23b6bc0a5d375d31730c5ea812b1a76b9da3b0 100644 --- a/main.js +++ b/main.js @@ -3,15 +3,56 @@ const logger = require.main.require('./common/logger.js'), { app, BrowserWindow, globalShortcut, ipcMain } = require('electron'), { fork, spawn } = require('child_process'), fs = require('fs'), - lkt = require.main.require('./common/lkt.js'); + lkt = require.main.require('./common/lkt.js'), + isRunning = require('./common/isRunning.js'); + +let electronEjs = require('electron-ejs'); +let ejs = new electronEjs({ app: 'Amadeus' }, {}); + +const packageConfig = require('./package.json').config; +logger.debug('main', 'Package config is ' + JSON.stringify(packageConfig)); var config = require('./common/config.js'); var db = require.main.require('./common/db.js'); var tail = require('tail').Tail; var client; /* Sub process for the express server */ -var lektor; /* Sub process, the lektord player */ -var lektor_closed = false; /* Is lektord closed? */ + +class Lektor { + constructor() { + this.closed = true; + isRunning({ win: 'lektord.exe', linux: 'lektord', mac: 'lektord' }).then(v => { + if (v && config.attachmode) { + logger.info('main', `Lektord is running! Attach to it`); + } else if (v) { + logger.error('error', `Lektord is running and attach mode is disable, exit!`); + app.quit(); + } else { + logger.debug('main', `Lektord not running, start it!`); + this.closed = false; + this.process = spawn('lektord', ['-F']); + this.process.stderr.on('data', data => { + logger.lektord(data); + }); + this.process.on('close', code => { + logger.warn('main', `Lektor exited with code ${code}`); + this.lektor_closed = true; + app.quit(); + }); + } + }); + } + + exit() { + if (this.closed) { + logger.warning('main', 'Lektord is already closed'); + return; + } + logger.info('main', 'Closing lektord'); + this.closed = true; + this.process.kill('SIGTERM'); + } +} /* The last thing we got from the search bar */ var __lastFilter = ''; @@ -37,14 +78,39 @@ function createInstanceWindow() { win = new BrowserWindow({ width: 1280, height: 720, + minWidth: 1000, + minHeight: 360, hasShadow: false, frame: false, menuBarVisible: true, webPreferences: { nodeIntegration: true, + worldSafeExecuteJavaScript: true, + contextIsolation: false /* XXX: Otherwise 'require' is not defined in instance/index.html */, }, }); - win.loadFile('instance/index.html'); + win.loadURL(`file://${__dirname}/instance/index.ejs`); + // win.loadFile('instance/index.html'); +} + +function createUserViewWindow() { + /* User view window */ + const uv = new BrowserWindow({ + width: 720, + title: 'Kurisu', + height: 360, + frame: false, + parent: win, + webPreferences: { + nodeIntegration: true, + worldSafeExecuteJavaScript: true, + contextIsolation: true, + }, + }); + uv.loadURL(`http://localhost:${config.clientport}/`); + uv.once('ready-to-show', () => { + uv.show(); + }); } function createKurisuWindow() { @@ -57,9 +123,11 @@ function createKurisuWindow() { parent: win, webPreferences: { nodeIntegration: true, + worldSafeExecuteJavaScript: true, + contextIsolation: true, }, }); - kurisu.loadURL('https://kurisu.iiens.net'); + kurisu.loadURL(config.kurisuurl); kurisu.once('ready-to-show', () => { kurisu.show(); }); @@ -74,14 +142,9 @@ ipcMain.on('close-app', (evt, arg) => { }); app.on('quit', () => { - logger.info( - 'main', - 'Send SIGTERM to express process, lektord and tailler process' - ); + logger.info('main', 'Send SIGTERM to express process, lektord and tailler process'); client.kill('SIGTERM'); - if (!lektor_closed) { - lektor.kill('SIGTERM'); - } + lektor.exit(); }); /********************************* @@ -92,7 +155,7 @@ app.on('ready', () => { logger.info('main', 'Main window is ready'); createInstanceWindow(); client = fork('client/main.js'); - lektor = spawn('lektord', ['-F']); + lektor = new Lektor(); globalShortcut.register('CommandOrControl+D', () => { var win = BrowserWindow.getFocusedWindow(); @@ -112,15 +175,13 @@ app.on('ready', () => { } }); - lektor.stderr.on('data', data => { - logger.lektord(data); - }); setTimeout(() => { lkt.idleActualisation(); }, 1000); setTimeout(() => { lkt.statusActualisation(); }, 1500); +<<<<<<< HEAD setTimeout(() => { lkt.commandPlaylistList(); }, 1500); @@ -129,6 +190,8 @@ app.on('ready', () => { lektor_closed = true; app.quit(); }); +======= +>>>>>>> master }); /*************** @@ -136,14 +199,22 @@ app.on('ready', () => { ***************/ ipcMain.on('toggle-kurisu', (event, arg) => { - logger.debug('main', 'Toggle kurisu view'); + logger.debug('main', 'Open kurisu view'); createKurisuWindow(); }); +ipcMain.on('toggle-client-view', (event, arg) => { + logger.debug('main', 'Open client view'); + createUserViewWindow(); +}); + /********************************* * Messages from the main window * *********************************/ +var songTimeData = { elapsed: 0, total: 100, state: 'stop', song: '0' }; +var counterTime = 0; + ipcMain.on('cmd-play', (event, arg) => { lkt.commandPlay().then(arg => { logger.debug('main', 'Returned from cmd-play'); @@ -167,20 +238,20 @@ ipcMain.on('reload-db-request', (event, arg) => { var callback = karas => { event.reply('reload-db-responce', karas); }; - if (arg && arg.search) { + if (arg && (arg.search || arg.search == '')) { __lastFilter = arg.search; logger.debug('main', `Reload DB with search '${arg}'`); - db.search(__lastFilter, 0, 15).then(callback); + db.search(__lastFilter, 0, 100).then(callback); } else { logger.debug('main', `Reload DB with last filter '${__lastFilter}'`); - db.search(__lastFilter, 0, 15).then(callback); + db.search(__lastFilter, 0, 100).then(callback); } }); /* Send the queue to the webpage when asked to */ ipcMain.on('reload-queue-request', (event, arg) => { logger.info('main', 'Reloading next karas in queue'); - db.queue(0, 100).then(karas => { + db.queueAll().then(karas => { event.reply('reload-queue-responce', karas); }); }); @@ -188,7 +259,7 @@ ipcMain.on('reload-queue-request', (event, arg) => { ipcMain.on('verify-queue-reloaded-request', (event, arg) => { if (lkt.isQueueUpdated()) { lkt.setQueueUpdated(false); - db.queue(0, 100).then(karas => { + db.queueAll().then(karas => { event.reply('reload-queue-responce', karas); }); } @@ -223,19 +294,12 @@ ipcMain.on('delete-kara-queue-pos', (event, arg) => { }); ipcMain.on('add-kara-queue-pos', (event, addparams) => { - lkt.commandQueueAddId(addparams.id).then(() => - lkt.commandMove(addparams.queueSize + 1, addparams.position) - ); + lkt.commandQueueAddId(addparams.id).then(() => lkt.commandMove(addparams.queueSize + 1, addparams.position)); }); -var songTimeData = { elapsed: 0, total: 100, state: 'stop', song: '0' }; -var counterTime = 0; ipcMain.on('get-song-time-data', (event, arg) => { var newSongTimeData = lkt.getSongTimeData(); - if ( - newSongTimeData.elapsed != songTimeData.elapsed || - newSongTimeData.song != songTimeData.song - ) { + if (newSongTimeData.elapsed != songTimeData.elapsed || newSongTimeData.song != songTimeData.song) { songTimeData = newSongTimeData; event.reply('send-song-time-data', { elapsed: songTimeData.elapsed, @@ -262,6 +326,17 @@ ipcMain.on('get-song-time-data', (event, arg) => { } }); +ipcMain.on('get-runnings', (event, arg) => { + isRunning({ win: 'klkt.exe', mac: 'klkt', linux: 'klkt' }).then(vklkt => { + isRunning({ win: 'lektord.exe', mac: 'lektord', linux: 'lektord' }).then(vlektord => { + event.reply('send-runnings', { + klkt: vklkt, + lektord: vlektord, + }); + }); + }); +}); + ipcMain.on('set-settings', (event, arg) => { logger.info('main', `Settings are now: ${JSON.stringify(arg)}`); config.host = arg.host; @@ -269,6 +344,8 @@ ipcMain.on('set-settings', (event, arg) => { config.database = arg.db; config.loglektord = arg.loglektord; config.attachmode = arg.attachmode; + config.kurisuurl = arg.kurisuurl; + config.clientport = arg.clientport; /* prettier-ignore */ config.loglevel = arg.loglevel.ERROR ? 'error' diff --git a/package.json b/package.json index f7df599600f8c3892a9176445b3a595bc4eba510..40aaa64f365d4b9870f274735cbac1719f00f527 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,17 @@ "author": "Bakaclub", "contributors": [ "Kubat <mael.martin31@gmail.com>", - "Sting <>" + "Sting <lallegre26@gmail.com>", + "Deurstann <tristan.derouet@gmail.com>" ], "license": "ISC", "scripts": { - "start": "electron .", - "test": "ELECTRON_ENABLE_LOGGING=0 ELECTRON_NO_ATTACH_CONSOLE=true electron --trace-uncaught ." + "start": "ELECTRON_ENABLE_LOGGING=0 ELECTRON_NO_ATTACH_CONSOLE=true electron --trace-uncaught ." }, "dependencies": { "ejs": "^3.1.5", "electron": "^10.1.1", + "electron-ejs": "^1.2.1", "express": "^4.17.1", "ini": "^2.0.0", "node": "<14.0.0 ", @@ -26,8 +27,7 @@ }, "config": { "port": "8080", - "kurisu": "https://kurisu.iiens.net/", - "lektord": "/usr/local/bin/lektord" + "kurisu": "https://kurisu.iiens.net/" }, "devDependencies": { "prettier": "2.1.2" diff --git a/style/css/instance.css b/style/css/instance.css index 0d364945d91b4cc77c6c3ef0d50c7ad9d888067e..b96953fc15dae96c0f03a954ad7aaab494b83605 100644 --- a/style/css/instance.css +++ b/style/css/instance.css @@ -5,13 +5,28 @@ url("../fonts/LinotypeIPACustom3.svg") format('svg'); } +/* Scrollbar */ ::-webkit-scrollbar { - display: none; + width: 13px; } +::-webkit-scrollbar-track { + background: content-box; +} + +::-webkit-scrollbar-thumb { + background: #4e5d6c; +} + +::-webkit-scrollbar-thumb:hover { + background: #df691a; +} + +/* Overall style */ body { overflow: hidden; height: 100vh; + background: #343a40; font-family: "LTFinneganCustom"; }