diff options
-rw-r--r-- | README | 15 | ||||
-rw-r--r-- | contrib/Pflichtenheft-Peter.md | 2 | ||||
-rwxr-xr-x | rh-bin/authtoken.json | 6 | ||||
-rw-r--r-- | www/index.html | 118 | ||||
-rw-r--r-- | www/js/apps.js | 154 | ||||
-rw-r--r-- | www/js/auth.js | 160 | ||||
-rw-r--r-- | www/js/clock.js | 34 | ||||
-rw-r--r-- | www/js/importer.js | 86 | ||||
-rw-r--r-- | www/js/jingles.js | 10 | ||||
-rw-r--r-- | www/js/musicgrid.js | 12 | ||||
-rw-r--r-- | www/js/musicpools.js | 59 | ||||
-rw-r--r-- | www/js/router.js | 176 | ||||
-rw-r--r-- | www/js/shows.js | 76 | ||||
-rw-r--r-- | www/js/utils.js | 8 | ||||
-rw-r--r-- | www/styles/jingles.css | 10 | ||||
-rw-r--r-- | www/styles/main-style.css | 11 | ||||
-rw-r--r-- | www/styles/musicgrid.css | 6 | ||||
-rw-r--r-- | www/styles/musicpools.css | 8 | ||||
-rw-r--r-- | www/styles/shows.css | 10 |
19 files changed, 583 insertions, 378 deletions
@@ -31,7 +31,7 @@ LICENSE Installation ============ -# sudo aptitude install apache2 libapache2-mpm-itk libapache2-mod-perl2 libjson-any-perl libxml-quote-perl librhrd-perl libjs-jquery libjs-bootstrap rivendell-server +# sudo aptitude install apache2 libapache2-mpm-itk libapache2-mod-perl2 libjson-maybexs-perl libxml-quote-perl librhrd-perl libjs-jquery libjs-bootstrap rivendell-server # sudo a2enmod ssl authnz_ldap perl proxy_wstunnel # sudo /etc/init.d/apache2 restart @@ -71,8 +71,9 @@ add the following to the virtualhost config: </Files> </Directory> - ProxyPass "/ntp" "ws://localhost:3000/ntp" - ProxyPass "/rhimportd" "ws://localhost:4080/public/socket" + ProxyPass "/ntp" "ws://localhost:3000/ntp" + ProxyPass "/rhimportd" "ws://localhost:4080/public/socket" + ProxyPass "/upload" "http://localhost:4080/public/upload" DocumentRoot /var/www/rhwebimport/www/ <Directory /var/www/rhwebimport/> @@ -89,3 +90,11 @@ add the following to the virtualhost config: ~~~/snip~~~ # ./build.sh + + +Pflichtenheft +============= + +To generate to PDF from markdown use pandoc + +# pandoc -o Pflichtenheft-Peter.pdf Pflichtenheft-Peter.md diff --git a/contrib/Pflichtenheft-Peter.md b/contrib/Pflichtenheft-Peter.md index 74362b8..c9bebd8 100644 --- a/contrib/Pflichtenheft-Peter.md +++ b/contrib/Pflichtenheft-Peter.md @@ -59,7 +59,7 @@ Es ist ein Interface zu entwickeln, mit dessen Hilfe die Zuweisung der Musikpool Es soll möglich sein Dateien direkt aus verschiednen exteren Quellen zu importieren. Zu diesem Zweck gibt es serverseitig die Komponente `rhimportd`. Diese wird über eine Websocket-API gesteuert und kümmert sich um den tatsächlichen Import. `rhwebimport` initiiert den Import und visualisiert den Fortschritt. Ausserdem soll es möglich sein den Fortschritt etwaiger laufender Import-Prozesse nach einem erneuten Einloggen (zb. nach einem Verbindungsabbruch) weiter anzuzeigen. `rhimportd` bietet alle dafür notwendigen API Funktionen. Sollten zu diese Zweck Änderungen an der API notwendig sein sollen diese gemeinsam mit dem Radio Helsinki Technik-Team erarbeitet werden. Die Änderungen an `rhimportd` werden von Radio Helsinki erledigt. Folgende externe Quellen sollen unterstützt werden: - - archiv://YYYY/MM/DD/hh/00 - import aus dem Sendungsarchiv von Radio Helsinki (immer volle Stunden) + - archiv://YYYY/MM/DD/hh/mm - import aus dem Sendungsarchiv von Radio Helsinki (derzeit nur für volle Stunden, dh. mm muss immer 00 sein) - http://, https://, ftp://, ftps:// - import von der entsprechenden URL ### zustätliche Importquelle: `public://<username>/` diff --git a/rh-bin/authtoken.json b/rh-bin/authtoken.json index 9e4c332..13a73d3 100755 --- a/rh-bin/authtoken.json +++ b/rh-bin/authtoken.json @@ -23,7 +23,7 @@ use strict; use RHRD::rddb; -use JSON; +use JSON::MaybeXS; my $status = 'ERROR'; my $errorstring = 'unknown'; @@ -57,6 +57,8 @@ $answer{'username'} = $username; $answer{'fullname'} = $fullname; $answer{'token'} = $token; +my $j = JSON::MaybeXS->new(utf8 => 0); + print "Content-type: application/json; charset=UTF-8\n"; print "Status: $responsecode\n\n"; -print JSON->new->encode(\%answer); +print $j->encode(\%answer); diff --git a/www/index.html b/www/index.html index eb8e3d2..56130cc 100644 --- a/www/index.html +++ b/www/index.html @@ -15,20 +15,6 @@ <link href="/styles/jingles.css" rel="stylesheet"> <link href="/styles/musicpools.css" rel="stylesheet"> <link href="/styles/musicgrid.css" rel="stylesheet"> - <script src="/javascript/jquery/jquery.min.js"></script> - <script src="/javascript/bootstrap/js/bootstrap.min.js"></script> - <script src="/js/dropzone.js"></script> - <script src="/js/rdxport.js"></script> - <script src="/js/rdxport.rh.js"></script> - <script src="/js/utils.js"></script> - <script src="/js/clock.js"></script> - <script src="/js/importer.js"></script> - <script src="/js/auth.js"></script> - <script src="/js/apps.js"></script> - <script src="/js/shows.js"></script> - <script src="/js/jingles.js"></script> - <script src="/js/musicpools.js"></script> - <script src="/js/musicgrid.js"></script> </head> <body> @@ -40,7 +26,7 @@ <img class="visible-xs-block" src="/img/helsinki-small.png" alt="radio helsinki logo" /> <div class="loginspacer hidden-xs"> </div> <div class="loginspacer hidden-xs hidden-sm"> </div> - <img class="hidden-xs" src="/img/helsinki.png" alt="radio helsinki logo" /> + <img class="hidden-xs" src="/img/helsinki.png" alt="radio helsinki logo" width="176" height="176" /> <h1 class="form-auth-heading">Radio Helsinki - Import</h1> <input id="username" type="text" class="form-control" placeholder="Benutzername" required autofocus> <input id="password" type="password" class="form-control" placeholder="Passwort" required> @@ -67,16 +53,16 @@ <div class="collapse navbar-collapse" id="navbar-collapse-1"> <div class="navbar-left"> <ul class="nav navbar-nav"> - <li id="nav-btn-shows"><a href="/shows/" onclick="event.preventDefault(); apps_select('shows')">Sendungen</a></li> - <li id="nav-btn-jingles"><a href="/jingles/" onclick="event.preventDefault(); apps_select('jingles')">Jingles</a></li> - <li id="nav-btn-musicpools"><a href="/musicpools/" onclick="event.preventDefault(); apps_select('musicpools')">Musikpools</a></li> - <li id="nav-btn-musicgrid"><a href="/musicgrid/" onclick="event.preventDefault(); apps_select('musicgrid')">Musikgrid</a></li> + <li id="nav-btn-shows"><a href="/shows/">Sendungen</a></li> + <li id="nav-btn-jingles"><a href="/jingles/">Jingles</a></li> + <li id="nav-btn-musicpools"><a href="/musicpools/">Musikpools</a></li> + <li id="nav-btn-musicgrid"><a href="/musicgrid/">Musikgrid</a></li> </ul> </div> <div class="navbar-right"> <p class="navbar-text">angemeldet als <strong id="username-field">UNKNOWN</strong></p> - <button type="button" class="btn btn-danger navbar-btn" onclick="auth_logout()"> + <button type="button" class="btn btn-danger navbar-btn logout"> <span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> Abmelden </button> </div> @@ -128,18 +114,28 @@ </div> </div> - <div id="app-shows" class="container-fluid"> + <div id="app-shows" class="container-fluid app-tab"> <div class="alertbox"></div> <div class="row"> <div class="col-md-10"> - <form class="well form-inline"> - <!-- todo: fix html error: h3 is not allowed in label --> - <label class="control-label" for="show-selector"> - <h3>Sendung auswählen</h3> - </label> - <select id="show-selector" class="main-selector"> - </select> - </form> + <div class="row"> + <div class="col-md-12" id="show-header-spacer"> + </div> + </div> + <div class="row"> + <div class="col-md-3"> + <div class="dropdown"> + <button type="button" class="btn btn-lg btn-primary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Sendung auswählen <span class="caret"></span> + </button> + <ul class="dropdown-menu" id="show-selector"> + </ul> + </div> + </div> + <div class="col-md-9"> + <h2 id="show-title"></h2> + </div> + </div> </div> <div class="col-md-2"> <div id="clock"> @@ -149,18 +145,16 @@ </div> </div> </div> - <div class="row"> - <div class="col-md-12"> - <h2 id="show-title"></h2> - </div> - </div> <div class="row" id="show-details"> <div class="col-md-2"> </div> <div class="col-md-2"> <strong>Tag:</strong> <span id="show-dow"></span> </div> <div class="col-md-2"> - <strong>Rythmus:</strong> <span id="show-rhythm"></span> + <strong>Rythmus:</strong> <span id="show-rhythm-w1" class="label label-default">1</span> + <span id="show-rhythm-w2" class="label label-default">2</span> + <span id="show-rhythm-w3" class="label label-default">3</span> + <span id="show-rhythm-w4" class="label label-default">4</span> </div> <div class="col-md-2"> <strong>Startzeit:</strong> <span id="show-starttime"></span> @@ -191,7 +185,7 @@ </div> </div> - <div id="app-jingles" class="container-fluid"> + <div id="app-jingles" class="container-fluid app-tab"> <div class="alertbox"></div> <div class="groups"> @@ -200,26 +194,34 @@ </div> - <div id="app-musicpools" class="container-fluid"> + <div id="app-musicpools" class="container-fluid app-tab"> <div class="alertbox"></div> <div class="row"> - <form class="well form-inline"> - <!-- todo: fix html error: h3 is not allowed in label --> - <label class="control-label" for="musicpool-selector"> - <h3>Musikpool auswählen</h3> - </label> - <select id="musicpool-selector" class="main-selector"> - </select> - </form> + <div class="col-md-12" id="musicpool-header-spacer"> + </div> + </div> + <div class="row"> + <div class="col-md-3"> + <div class="dropdown"> + <button type="button" class="btn btn-lg btn-primary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Musikpool auswählen <span class="caret"></span> + </button> + <ul class="dropdown-menu" id="musicpool-selector"> + </ul> + </div> + </div> + <div class="col-md-9"> + <h2 id="musicpool-title"></h2> + </div> </div> <div class="musicpoolContainer"> </div> </div> - <div id="app-musicgrid" class="container-fluid"> + <div id="app-musicgrid" class="container-fluid app-tab"> <div class="alertbox"></div> - <h1>Musikgrid</h1> + <h2 id="musicgrid-title">Musikgrid</h2> <table class="table table-striped"> <thead> <tr> @@ -306,7 +308,7 @@ <div class="row group jingleGroupTemplate"> <div class="col-md-12"> - <h2></h2> + <h2 class="jingle-title"></h2> <table class="table table-striped"> <thead> <tr> @@ -329,7 +331,6 @@ <div class="row musicpool musicpoolTemplate"> <div class="col-md-12"> - <h2></h2> <table class="table table-striped"> <thead> <tr> @@ -427,11 +428,20 @@ </div> - <script type="text/javascript"> - auth_init(); - apps_init(); - clock_init(); - </script> + <script src="/javascript/jquery/jquery.min.js"></script> + <script src="/javascript/bootstrap/js/bootstrap.min.js"></script> + <script src="/js/dropzone.js"></script> + <script src="/js/rdxport.js"></script> + <script src="/js/rdxport.rh.js"></script> + <script src="/js/utils.js"></script> + <script src="/js/clock.js"></script> + <script src="/js/importer.js"></script> + <script src="/js/auth.js"></script> + <script src="/js/shows.js"></script> + <script src="/js/jingles.js"></script> + <script src="/js/musicpools.js"></script> + <script src="/js/musicgrid.js"></script> + <script src="/js/router.js"></script> </body> </html> diff --git a/www/js/apps.js b/www/js/apps.js deleted file mode 100644 index dd5b337..0000000 --- a/www/js/apps.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - * rhwebimport - * - * Copyright (C) 2014-2016 Christian Pointner <equinox@helsinki.at> - * Copyright (C) 2015-2016 Peter Grassberger <petertheone@gmail.com> - * - * This file is part of rhwebimport. - * - * rhwebimport is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * rhwebimport is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with rhwebimport. If not, see <http://www.gnu.org/licenses/>. - */ - -'use strict'; - -var Rdxport = Rdxport || {}; - -var apps_current = null; -var rdxport = null; -var importer = null; - -function apps_select(app) { - if (importer && importer.isUploading()) { - alert('Achtung: Es laufen noch imports.'); - return; - } - - $('.container').removeClass('fullWidth'); - - shows_cleanup(); - jingles_cleanup(); - musicpools_cleanup(); - musicgrid_cleanup(); - - switch(app) { - case "musicgrid": - $('#app-shows').hide(); - $('#nav-btn-shows').removeClass('active'); - $('#app-jingles').hide(); - $('#nav-btn-jingles').removeClass('active'); - $('#app-musicpools').hide(); - $('#nav-btn-musicpools').removeClass('active'); - - $('.container').addClass('fullWidth'); - $('#app-musicgrid').show(); - $('#nav-btn-musicgrid').addClass('active'); - - apps_current = app; - musicgrid_init(); - break; - case "musicpools": - $('#app-shows').hide(); - $('#nav-btn-shows').removeClass('active'); - $('#app-jingles').hide(); - $('#nav-btn-jingles').removeClass('active'); - $('#app-musicgrid').hide(); - $('#nav-btn-musicgrid').removeClass('active'); - - $('#app-musicpools').show(); - $('#nav-btn-musicpools').addClass('active'); - - apps_current = app; - musicpools_init(); - break; - case "jingles": - $('#app-shows').hide(); - $('#nav-btn-shows').removeClass('active'); - $('#app-musicpools').hide(); - $('#nav-btn-musicpools').removeClass('active'); - $('#app-musicgrid').hide(); - $('#nav-btn-musicgrid').removeClass('active'); - - $('#app-jingles').show(); - $('#nav-btn-jingles').addClass('active'); - - apps_current = app; - jingles_init(); - break; - default: - $('#app-jingles').hide(); - $('#nav-btn-jingles').removeClass('active'); - $('#app-musicpools').hide(); - $('#nav-btn-musicpools').removeClass('active'); - $('#app-musicgrid').hide(); - $('#nav-btn-musicgrid').removeClass('active'); - - $('#app-shows').show(); - $('#nav-btn-shows').addClass('active'); - - apps_current = app = 'shows'; - shows_init(); - } - if (locationHrefValue() !== app) { - history.pushState(null, null, '/' + app + '/'); - } -} - -function apps_init() { - importer = new Rdxport.Importer(); - - apps_current = locationHrefValue(); - - if(auth_token && auth_username) { - // todo: do this at a central place - rdxport = new Rdxport.Rdxport(auth_username, auth_token, '/rd-bin/rdxport.cgi'); - rdxport.setListDropboxesEndpoint('/rh-bin/listdropboxes.cgi'); - rdxport.setMusicgridEndpoint('/rh-bin/musicgrid.cgi'); - - apps_select(apps_current); - } - - $(window).on('popstate', function(event) { - if(auth_token && auth_username) { - // todo: do this at a central place - rdxport = new Rdxport.Rdxport(auth_username, auth_token, '/rd-bin/rdxport.cgi'); - rdxport.setListDropboxesEndpoint('/rh-bin/listdropboxes.cgi'); - rdxport.setMusicgridEndpoint('/rh-bin/musicgrid.cgi'); - - apps_select(locationHrefValue()); - } - }); - - $(document).ajaxError(function(event, jqXHR, settings, thrownError) { - //todo: add errors - }); - - window.onbeforeunload = function(e) { - if (importer && importer.isUploading()) { - return 'Achtung: Es laufen noch imports.'; - } - }; -} - -function apps_cleanup() { - shows_cleanup(); - jingles_cleanup(); - musicpools_cleanup(); - musicgrid_cleanup(); - - $(window).off('popstate'); - - importer = null; - rdxport = null; - apps_current = null; -} diff --git a/www/js/auth.js b/www/js/auth.js index d01b70d..691a19e 100644 --- a/www/js/auth.js +++ b/www/js/auth.js @@ -24,94 +24,84 @@ var Rdxport = Rdxport || {}; -var auth_username = null; -var auth_fullname = null; -var auth_token = null; - -function auth_loginSuccess(data) { - if (data.status == 'OK') { - auth_username = data.username; - auth_fullname = data.fullname; - auth_token = data.token; - - sessionStorage.setItem("auth_username", auth_username); - sessionStorage.setItem("auth_fullname", auth_fullname); - sessionStorage.setItem("auth_token", auth_token); - - // todo: do this at a central place - rdxport = new Rdxport.Rdxport(auth_username, auth_token, '/rd-bin/rdxport.cgi'); - rdxport.setListDropboxesEndpoint('/rh-bin/listdropboxes.cgi'); - rdxport.setMusicgridEndpoint('/rh-bin/musicgrid.cgi'); - - apps_select(apps_current); - - $('#username-field').html(auth_fullname + ' (' + auth_username + ')'); - $('#loginbox').slideUp(); - $('#mainwindow').fadeIn(); - } else { - alertbox.error('loginbox', "Fehler beim Login", data.errorstring); - auth_cleanup(); - } -} - -function auth_loginError(req, status, error) { - var message = req.status + ': ' + error; - if(req.status == 401) { - message = "Benutzer und/oder Passwort sind falsch!"; - } - alertbox.error('loginbox', "Fehler beim Login", message); - $("#password").val(''); -} - -function auth_logout() { - if (importer && importer.isUploading()) { - alert('Achtung: Es laufen noch imports.'); - return; - } - - auth_cleanup(); - apps_cleanup(); +Rdxport.Auth = function() { + this.username = sessionStorage.getItem('auth_username'); + this.fullname = sessionStorage.getItem('auth_fullname'); + this.token = sessionStorage.getItem('auth_token'); +}; + +Rdxport.Auth.prototype.isLoggedIn = function() { + return this.username && this.fullname && this.token; +}; + +Rdxport.Auth.prototype.set = function(username, fullname, token) { + this.username = username; + this.fullname = fullname; + this.token = token; + + sessionStorage.setItem('auth_username', this.username); + sessionStorage.setItem('auth_fullname', this.fullname); + sessionStorage.setItem('auth_token', this.token); +}; + +Rdxport.Auth.prototype.cleanup = function() { + sessionStorage.removeItem('auth_username'); + sessionStorage.removeItem('auth_fullname'); + sessionStorage.removeItem('auth_token'); + + this.username = null; + this.fullname = null; + this.token = null; +}; + +Rdxport.AuthView = function(model) { + this.model = model; +}; + +Rdxport.AuthView.prototype.renderLoggedIn = function() { + $('#loginbox').slideUp(); + $('#mainwindow').fadeIn(); + $('#username-field').text(this.model.fullname + ' (' + this.model.username + ')'); + + $('button.logout').off().on('click', function() { + router.route('logout'); + }); +}; - $(".alert").alert('close'); - $("#username").val(''); - $("#password").val(''); - $("#mainwindow").fadeOut(); - $('#username-field').html(''); +Rdxport.AuthView.prototype.renderLoginForm = function() { + $('.alert').alert('close'); $('#loginbox').slideDown(); -} + $('#mainwindow').fadeOut(); + $('#username-field').empty(); -function auth_init() { - auth_username = sessionStorage.getItem("auth_username"); - auth_fullname = sessionStorage.getItem("auth_fullname"); - auth_token = sessionStorage.getItem("auth_token"); - - if(auth_token && auth_username && auth_fullname) { - $("#loginbox").hide(); - $('#username-field').html(auth_fullname + ' (' + auth_username + ')'); - } else { - $("#mainwindow").hide(); - } - $("#loginform").submit(function(event) { + var self = this; + $('#loginform').on('submit', function(event) { event.preventDefault(); - Rdxport.Rdxport.authLogin( - '/rh-bin/authtoken.json', - $("#username").val(), - $("#password").val(), - auth_loginSuccess - ).fail(auth_loginError); + '/rh-bin/authtoken.json', + $("#username").val(), + $("#password").val(), + function(data) { + if (data.status == 'OK') { + self.model.set( + data.username, + data.fullname, + data.token + ); + + router.route(); + } else { + alertbox.error('loginbox', "Fehler beim Login", data.errorstring); + self.model.cleanup(); + } + } + ).fail(function(req, status, error) { + var message = req.status + ': ' + error; + if(req.status == 401) { + message = "Benutzer und/oder Passwort sind falsch!"; + } + alertbox.error('loginbox', "Fehler beim Login", message); + $("#password").val(''); + }); }); -} - -function auth_cleanup() { - sessionStorage.removeItem("auth_username"); - sessionStorage.removeItem("auth_fullname"); - sessionStorage.removeItem("auth_token"); - - auth_username = null; - auth_fullname = null; - auth_token = null; - - $("#username").val('').focus(); - $("#password").val(''); -} +}; diff --git a/www/js/clock.js b/www/js/clock.js index 96ec9e4..458d1c7 100644 --- a/www/js/clock.js +++ b/www/js/clock.js @@ -32,8 +32,16 @@ function Clock() { this.clock_rtt = 0; this.state = 'NEW'; + this.getRDTimeMS = function() { + return (+new Date()) + (this.last_message.tz_offset * 1000) + this.clock_offset; + } + + this.now = function() { + return new Date(this.getRDTimeMS()); + } + this.redraw = function() { - var rdtime_ms = (+new Date()) + (this.last_message.tz_offset * 1000) + this.clock_offset; + var rdtime_ms = this.getRDTimeMS(); var rdtime = new Date(rdtime_ms); var date_str = weekday_short[rdtime.getUTCDay()] + ', '; @@ -44,11 +52,11 @@ function Clock() { time_str += (rdtime.getUTCSeconds() > 9 ? ':' : ':0') + rdtime.getUTCSeconds(); this.draw_callbacks.fireWith(window, [date_str, time_str, get_rd_week(rdtime_ms)]); - } + }; this.addCallback = function(cb) { this.draw_callbacks.add(cb); - } + }; this.ntp_update = function(event) { var t4 = (+new Date()); @@ -59,18 +67,18 @@ function Clock() { this.clock_offset = ((msg.t2 - msg.t1) + (msg.t3 - msg.t4)) / 2; this.clock_rtt = (msg.t4 - msg.t1) - (msg.t3 - msg.t2); // console.log('got new ntp message from rhrdtime (rtt=' + this.clock_rtt + ' ms): new offset = ' + this.clock_offset + ' ms'); - } + }; this.ntp_request = function() { this.sock.send(JSON.stringify({ t1: (+new Date()), t2: 0, t3: 0, t4: 0, tz_offset: 0, week: 0 })); - } + }; this.sock_onopen = function() { // console.log('clock websocket connection established'); this.state = 'CONNECTED'; this.ntp_request(); this.interval_request = setInterval(this.ntp_request.bind(this), 2000); - } + }; this.sock_onclose = function(event) { if(this.state == 'STOPPED') { @@ -84,7 +92,7 @@ function Clock() { setTimeout(this.connect.bind(this), 1000); this.state = 'RECONNECTING'; } - } + }; this.connect = function() { this.sock = new WebSocket('wss://' + window.location.host + '/ntp'); @@ -92,12 +100,12 @@ function Clock() { this.sock.onopen = this.sock_onopen.bind(this); this.sock.onclose = this.sock_onclose.bind(this); this.state = 'CONNECTING'; - } + }; this.start = function() { this.connect(); this.interval_redraw = setInterval(this.redraw.bind(this), 200); - } + }; this.stop = function() { this.state = 'STOPPED'; @@ -106,13 +114,7 @@ function Clock() { clearInterval(this.interval_request); delete this.interval_request; this.sock.close(); - } -} - -var clock = new Clock(); - -function clock_init() { - clock.start(); + }; } function clock_add_callback(cb) { diff --git a/www/js/importer.js b/www/js/importer.js index 4ea55b3..b34c4b6 100644 --- a/www/js/importer.js +++ b/www/js/importer.js @@ -24,10 +24,88 @@ var Rdxport = Rdxport || {}; -Rdxport.Importer = function() { - this.$el = $('#uploadModal'); +Rdxport.Importer = function(username, token) { + this.username = username; + this.token = token; + this.$el = $('#uploadModal'); this.uploads = []; + this.webSocket = null; + + this.initWebSocket(); +}; + +Rdxport.Importer.CMD_LIST = 'list'; +Rdxport.Importer.CMD_NEW = 'new'; +Rdxport.Importer.CMD_RECONNECT = 'reconnect'; + +Rdxport.Importer.prototype.initWebSocket = function() { + var importer = this; + + var webSocket = new WebSocket('wss://import.helsinki.at/rhimportd'); + + webSocket.onclose = function(code, reason) { + console.log('close'); + console.log(code); + console.log(reason); + }; + + webSocket.onerror = function() { + console.log('error'); + }; + + webSocket.onopen = function() { + console.log('open'); + + console.log('send new'); + var sendOptions = { + COMMAND: Rdxport.Importer.CMD_NEW, + LOGIN_NAME: importer.username, + PASSWORD: importer.token, + TIMEOUT: 200, + REFERENCE_ID: "999", + SHOW_ID: 10000, + CLEAR_SHOW_CARTS: true, + SOURCE_URI: 'archiv://2016/03/31/05' + }; + console.log(sendOptions); + this.send(JSON.stringify(sendOptions)); + }; + + webSocket.onmessage = function(event) { + console.log('message'); + console.log(event.data); + }; + + /*this.webSocket = new WebSocket('wss://import.helsinki.at/rhimportd'); + + this.webSocket.onclose = function(code, reason) { + console.log('close'); + console.log(code); + console.log(reason); + }; + + this.webSocket.onerror = function() { + console.log('error'); + }; + + this.webSocket.onopen = function() { + console.log('open'); + + console.log('send reconnect'); + var reconnectOptions = { + COMMAND: Rdxport.Importer.CMD_RECONNECT, + LOGIN_NAME: importer.username, + PASSWORD: importer.token + }; + console.log(reconnectOptions); + this.send(JSON.stringify(reconnectOptions)); + }; + + this.webSocket.onmessage = function(event) { + console.log('message'); + console.log(event.data); + };*/ }; Rdxport.Importer.prototype.resetModal = function() { @@ -258,8 +336,8 @@ Rdxport.Upload.prototype.addCut = function(file) { file.cutNumber = cutNumberLeading; formData.append('COMMAND', 2); - formData.append('LOGIN_NAME', auth_username); - formData.append('PASSWORD', auth_token); + formData.append('LOGIN_NAME', auth.username); + formData.append('PASSWORD', auth.token); formData.append('CART_NUMBER', self.cart.number); formData.append('CUT_NUMBER', cutNumber); formData.append('CHANNELS', 2); diff --git a/www/js/jingles.js b/www/js/jingles.js index 02070ae..e819570 100644 --- a/www/js/jingles.js +++ b/www/js/jingles.js @@ -44,7 +44,7 @@ Rdxport.JingleGroupListView = function(model) { this.jingleGroupViews = []; - $('#app-jingles .groups').html(''); + $('#app-jingles .groups').empty(); var self = this; $(this.model).on('update', function() { @@ -87,7 +87,7 @@ Rdxport.JingleGroupView = function(model) { var self = this; $(this.model).on('update', function() { - $('table > tbody', self.$el).html(''); + $('table > tbody', self.$el).empty(); self.model.mainCart = self.model.carts[0]; self.mainCartView = new Rdxport.JingleCartView(self.model.mainCart, self, true); @@ -105,7 +105,7 @@ Rdxport.JingleGroupView.prototype.render = function() { this.$el = $('#hiddenTemplates .jingleGroupTemplate').clone().removeClass('jingleGroupTemplate'); this.$el.appendTo('#app-jingles .groups'); - $('h2', this.$el).html(this.model.title); + $('h2', this.$el).text(this.model.title); $('table tbody tr', this.$el).remove(); $('.uploadButton', this.$el).on('click', function() { @@ -114,7 +114,7 @@ Rdxport.JingleGroupView.prototype.render = function() { }; Rdxport.JingleGroupView.prototype.destroy = function() { - $('table > tbody', this.$el).html(''); + $('table > tbody', this.$el).empty(); }; Rdxport.JingleGroupView.prototype.uploadProgress = function(upload, file) { @@ -149,7 +149,7 @@ Rdxport.JingleGroupView.prototype.uploadError = function(upload, file, msg, xhr, //var msg = $(xmlDoc); //var responseCode = msg.find('ResponseCode').text(); //var errorString = msg.find('ErrorString').text(); - var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').html(' ' + errorString)); + var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').text(' ' + errorString)); var dismiss_button = '<button class="btn btn-info btn-xs">' + '<span class="glyphicon glyphicon-remove"></span> Ok</button>'; diff --git a/www/js/musicgrid.js b/www/js/musicgrid.js index dbb2210..a0a0c10 100644 --- a/www/js/musicgrid.js +++ b/www/js/musicgrid.js @@ -58,7 +58,7 @@ Rdxport.MusicgridView = function(model) { Rdxport.MusicgridView.prototype.clean = function() { $('tr td', this.$el) - .html('') + .empty() .removeClass('clock') .css('background-color', '') .css('color', '') @@ -71,7 +71,7 @@ Rdxport.MusicgridView.prototype.update = function() { $(this.model.clocks).each(function(index, clock) { var $td = $('tr[data-dow="' + clock.dow + '"] td[data-hour="' + clock.hour +'"]', this.$el); $td.addClass('clock'); - $td.html(clock.name); + $td.text(clock.name); $td.attr('title', clock.title); $td.css('background-color', clock.color); if($td.isBackgroundDark()) { @@ -105,9 +105,9 @@ Rdxport.MusicpoolModal.prototype.selectClock = function(dow, hour, clockName) { var $modalHeader = $('#musicpoolModal .modal-header'); var $modalBody = $('#musicpoolModal .modal-body'); - $('h4', $modalHeader).html('Musikpool auswählen für Tag: ' + dow + ' Stunde: ' + hour + '.'); + $('h4', $modalHeader).text('Musikpool auswählen für Tag: ' + dow + ' Stunde: ' + hour + '.'); - $('tbody', $modalBody).html(''); + $('tbody', $modalBody).empty(); var self = this; $(this.model).off().on('update', function() { @@ -128,8 +128,8 @@ Rdxport.MusicpoolModal.prototype.selectClock = function(dow, hour, clockName) { }); var $tr = $('<tr>'); - $tr.append($('<td>').html(musicpool.clock)); - $tr.append($('<td>').html(musicpool.title)); + $tr.append($('<td>').text(musicpool.clock)); + $tr.append($('<td>').text(musicpool.title)); $tr.append($('<td>').html($button)); $('tbody', $modalBody).append($tr); diff --git a/www/js/musicpools.js b/www/js/musicpools.js index 400c0bd..5b3d491 100644 --- a/www/js/musicpools.js +++ b/www/js/musicpools.js @@ -26,9 +26,9 @@ var Rdxport = Rdxport || {}; var musicpoolsView = null; -function musicpools_init() { +function musicpools_init(subpage) { var musicpools = new Rdxport.GroupList(); - musicpoolsView = new Rdxport.MusicpoolsView(musicpools); + musicpoolsView = new Rdxport.MusicpoolsView(musicpools, subpage); } function musicpools_cleanup() { @@ -36,11 +36,13 @@ function musicpools_cleanup() { musicpoolsView = null; } -Rdxport.MusicpoolsView = function(model) { +Rdxport.MusicpoolsView = function(model, subpage) { this.model = model; this.musicpoolViews = []; - this.currentPoolId = sessionStorage.getItem('currentPoolId'); + this.currentPoolId = null; + + this.setCurrentPoolId(subpage); var self = this; $(this.model).on('update', function() { @@ -54,6 +56,16 @@ Rdxport.MusicpoolsView = function(model) { }; Rdxport.MusicpoolsView.prototype.setCurrentPoolId = function(currentPoolId) { + if (!currentPoolId) { + return; + } + if (this.currentPoolId !== currentPoolId) { + if (this.currentPoolId) { + history.pushState(null, null, '/musicpools/' + currentPoolId + '/'); + } else { + history.replaceState(null, null, '/musicpools/' + currentPoolId + '/'); + } + } this.currentPoolId = currentPoolId; sessionStorage.setItem('currentPoolId', this.currentPoolId); }; @@ -84,22 +96,31 @@ Rdxport.MusicpoolsView.prototype.updateSelector = function() { var $musicpoolSelector = $('#musicpool-selector'); $musicpoolSelector.off(); - $('option', $musicpoolSelector).remove(); - - $(this.model.groups).each(function(index, musicpool) { - var name = musicpool.title + ' (' + musicpool.clock + ')'; - $musicpoolSelector.append($('<option>').attr('value', musicpool.clock).text(name)); + $('li', $musicpoolSelector).remove(); + + $(this.model.groups).sort(function(a, b) { + return a.title.toLowerCase() >= b.title.toLowerCase() + }).each(function(index, musicpool) { + var name = '<strong>' + musicpool.title + '</strong> (' + musicpool.clock + ')'; + var link = $('<a>').attr('href', '#').html(name).click(function() { + self.setCurrentPoolId(musicpool.clock); + self.getCurrentPoolView().model.fetchCarts(); + }); + $musicpoolSelector.append($('<li>').append(link)); }); - - if (this.currentPoolId === null) { - this.setCurrentPoolId(this.model.groups[0].clock); + if($musicpoolSelector.children().length == 0) { + $musicpoolSelector.append($('<li>').append($('<a>').text('Keinen Musikpool gefunden!'))); } - $('option[value="' + this.currentPoolId + '"]', $musicpoolSelector).attr('selected', 'selected'); - $musicpoolSelector.on('change', function() { - self.setCurrentPoolId($('option:selected', $musicpoolSelector).attr('value')); - self.getCurrentPoolView().model.fetchCarts(); - }); + // todo: maybe integrate this into setCurrentShowId? + if (!this.currentPoolId) { + var currentPoolId = sessionStorage.getItem('currentPoolId'); + if (currentPoolId) { + this.setCurrentPoolId(currentPoolId); + } else { + this.setCurrentPoolId(this.model.groups[0].id); + } + } this.getCurrentPoolView().model.fetchCarts(); }; @@ -136,7 +157,7 @@ Rdxport.MusicpoolView.prototype.render = function() { this.$el = $('#hiddenTemplates .musicpoolTemplate').clone().removeClass('musicpoolTemplate'); $('#app-musicpools .musicpoolContainer').html(this.$el); - $('h2', this.$el).html(this.model.title); + $('#musicpool-title').text(this.model.title); $('table tbody tr', this.$el).remove(); this.cartViews = []; @@ -184,7 +205,7 @@ Rdxport.MusicpoolView.prototype.uploadError = function(upload, file, msg, xhr, a //var msg = $(xmlDoc); //var responseCode = msg.find('ResponseCode').text(); //var errorString = msg.find('ErrorString').text(); - var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').html(' ' + errorString)); + var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').text(' ' + errorString)); var dismiss_button = '<button class="btn btn-info btn-xs">' + '<span class="glyphicon glyphicon-remove"></span> Ok</button>'; diff --git a/www/js/router.js b/www/js/router.js new file mode 100644 index 0000000..cfeaf23 --- /dev/null +++ b/www/js/router.js @@ -0,0 +1,176 @@ +/* + * rhwebimport + * + * Copyright (C) 2014-2016 Christian Pointner <equinox@helsinki.at> + * Copyright (C) 2015-2016 Peter Grassberger <petertheone@gmail.com> + * + * This file is part of rhwebimport. + * + * rhwebimport is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhwebimport is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with rhwebimport. If not, see <http://www.gnu.org/licenses/>. + */ + +'use strict'; + +var clock = null; +var auth = null; +var router = null; +var importer = null; +var rdxport = null; + +$(document).ready(function() { + clock = new Clock(); + clock.start(); + auth = new Rdxport.Auth(); + router = new Rdxport.Router(auth); + router.route(); +}); + +Rdxport.Router = function(auth) { + this.auth = auth; + this.authView = new Rdxport.AuthView(this.auth); +}; + +Rdxport.Router.prototype.route = function(page, subpage) { + if (!this.auth.isLoggedIn()) { + this.login(); + return; + } + if (importer && importer.isUploading()) { + alert('Achtung: Es laufen noch imports.'); + return; + } + + if (!importer) { + importer = new Rdxport.Importer(this.auth.username, this.auth.token); + window.onbeforeunload = function(event) { + if (importer.isUploading()) { + return 'Achtung: Es laufen noch imports.'; + } + }; + } + if (!rdxport) { + rdxport = new Rdxport.Rdxport(this.auth.username, this.auth.token, '/rd-bin/rdxport.cgi'); + rdxport.setListDropboxesEndpoint('/rh-bin/listdropboxes.cgi'); + rdxport.setMusicgridEndpoint('/rh-bin/musicgrid.cgi'); + } + + /*$(document).ajaxError(function(event, jqXHR, settings, thrownError) { + //todo: add errors + });*/ + + var href = ['', '']; + if (!page && !subpage) { + href = locationHrefValue(); + page = href[1]; + subpage = href[2]; + } + + shows_cleanup(); + jingles_cleanup(); + musicpools_cleanup(); + musicgrid_cleanup(); + + this.authView.renderLoggedIn(); + $('.navbar-nav li').removeClass('active'); + $('.app-tab').hide(); + $('.container').removeClass('fullWidth'); + + var self = this; + $('.navbar-nav li a').off().on('click', function(event) { + event.preventDefault(); + var href = $(this).attr('href').split('/'); + self.route(href[1], href[2]); + }); + $(window).off('popstate').on('popstate', function(event) { + var href = locationHrefValue(); + self.route(href[1], href[2]); + }); + + switch (page) { + default : + page = 'shows'; + // fallthrough + case 'shows': + this.shows(subpage); + break; + case 'jingles': + this.jingles(); + break; + case 'musicpools': + this.musicpools(subpage); + break; + case 'musicgrid': + this.musicgrid(); + break; + case 'logout': + this.logout(); + break; + } + + href = locationHrefValue(); + if (href[1] !== page && page !== 'logout' && (!href[2] || href[2] !== subpage)) { + var url = '/' + page + '/'; + if (subpage) { + url += subpage + '/'; + } + history.pushState(null, null, url); + } +}; + +Rdxport.Router.prototype.login = function() { + this.authView.renderLoginForm(); +}; + +Rdxport.Router.prototype.logout = function() { + if (importer && importer.isUploading()) { + alert('Achtung: Es laufen noch imports.'); + return; + } + + shows_cleanup(); + jingles_cleanup(); + musicpools_cleanup(); + musicgrid_cleanup(); + this.auth.cleanup(); + + importer = null; + rdxport = null; + + this.login(); +}; + +Rdxport.Router.prototype.shows = function(subpage) { + $('#app-shows').show(); + $('#nav-btn-shows').addClass('active'); + shows_init(subpage); +}; + +Rdxport.Router.prototype.jingles = function() { + $('#app-jingles').show(); + $('#nav-btn-jingles').addClass('active'); + jingles_init(); +}; + +Rdxport.Router.prototype.musicpools = function(subpage) { + $('#app-musicpools').show(); + $('#nav-btn-musicpools').addClass('active'); + musicpools_init(subpage); +}; + +Rdxport.Router.prototype.musicgrid = function() { + $('.container').addClass('fullWidth'); + $('#app-musicgrid').show(); + $('#nav-btn-musicgrid').addClass('active'); + musicgrid_init(); +}; diff --git a/www/js/shows.js b/www/js/shows.js index 8fcba8b..233a5c3 100644 --- a/www/js/shows.js +++ b/www/js/shows.js @@ -26,9 +26,9 @@ var Rdxport = Rdxport || {}; var showListView = null; -function shows_init() { +function shows_init(subpage) { var showList = new Rdxport.GroupList(); - showListView = new Rdxport.ShowListView(showList); + showListView = new Rdxport.ShowListView(showList, subpage); drawClock('Do, 1.1.1970', '00:00:00', 0); clock_add_callback(drawClock); @@ -39,11 +39,13 @@ function shows_cleanup() { importer.cancelAllUploads(); } -Rdxport.ShowListView = function(model) { +Rdxport.ShowListView = function(model, subpage) { this.model = model; this.showViews = []; - this.currentShowId = sessionStorage.getItem('currentShowId'); + this.currentShowId = null; + + this.setCurrentShowId(subpage); var self = this; $(this.model).on('update', function() { @@ -57,6 +59,16 @@ Rdxport.ShowListView = function(model) { }; Rdxport.ShowListView.prototype.setCurrentShowId = function(currentShowId) { + if (!currentShowId) { + return; + } + if (this.currentShowId !== currentShowId) { + if (this.currentShowId) { + history.pushState(null, null, '/shows/' + currentShowId + '/'); + } else { + history.replaceState(null, null, '/shows/' + currentShowId + '/'); + } + } this.currentShowId = currentShowId; sessionStorage.setItem('currentShowId', this.currentShowId); }; @@ -68,9 +80,6 @@ Rdxport.ShowListView.prototype.getCurrentShowView = function() { if (this.showViews.length === 0) { return null; } - if (this.currentShowId === null) { - this.setCurrentShowId(this.model.groups[0].id); - } var self = this; var showViewFound = null; $(this.showViews).each(function(index, showView) { @@ -87,22 +96,34 @@ Rdxport.ShowListView.prototype.updateSelector = function() { var $showSelector = $('#show-selector'); $showSelector.off(); - $('option', $showSelector).remove(); + $('li', $showSelector).remove(); - $(this.model.groups).each(function(index, show) { - var name = show.title + ' (' + show.rhythm + ', ' + weekday[show.dayofweek] + ', ' + show.starttime + ', ' + show.length + ' Min.)'; - $showSelector.append($('<option>').attr('value', show.id).text(name)); + $(this.model.groups).sort(function(a, b) { + if(a.title.toLowerCase() == b.title.toLowerCase()) { + return b.rhythm - a.rhythm; + } + return a.title.toLowerCase() >= b.title.toLowerCase() + }).each(function(index, show) { + var name = show.id + ' | <strong>' + show.title + '</strong> (' + show.rhythm + ', ' + weekday[show.dayofweek] + ', ' + show.starttime + ', ' + show.length + ' Min.)'; + var link = $('<a>').attr('href', '#').html(name).click(function() { + self.setCurrentShowId(show.id); + self.getCurrentShowView().model.fetchCarts(); + }); + $showSelector.append($('<li>').append(link)); }); - - if (this.currentShowId === null) { - this.setCurrentShowId(this.model.groups[0].id); + if($showSelector.children().length == 0) { + $showSelector.append($('<li>').append($('<a>').text('Keine Sendung gefunden!'))); } - $('option[value="' + this.currentShowId + '"]', $showSelector).attr('selected', 'selected'); - $showSelector.on('change', function() { - self.setCurrentShowId($('option:selected', $showSelector).attr('value')); - self.getCurrentShowView().model.fetchCarts(); - }); + // todo: maybe integrate this into setCurrentShowId? + if (!this.currentShowId) { + var currentShowId = sessionStorage.getItem('currentShowId'); + if (currentShowId) { + this.setCurrentShowId(currentShowId); + } else { + this.setCurrentShowId(this.model.groups[0].id); + } + } this.getCurrentShowView().model.fetchCarts(); }; @@ -189,10 +210,23 @@ Rdxport.ShowView = function(model) { Rdxport.ShowView.prototype.render = function() { $('#show-title').text(this.model.title); $('#show-dow').text(weekday[this.model.dayofweek]); - $('#show-rhythm').text(this.model.rhythm); $('#show-starttime').text(this.model.starttime); $('#show-length').text(this.model.length + ' Min.'); + for(var w = 0; w < 4; w++) { + if(this.model.rhythm.charAt(w) == '1') { + var s = $('#show-rhythm-w' + (w+1)).attr('class', 'label') + switch(w+1) { + case 1: s.addClass('label-info'); break; + case 2: s.addClass('label-warning'); break; + case 3: s.addClass('label-success'); break; + case 4: s.addClass('label-danger'); break; + } + } else { + $('#show-rhythm-w' + (w+1)).attr('class', 'label label-disabled') + } + } + var $tableBody = $('#app-shows table tbody'); $('tr', $tableBody).remove(); @@ -249,7 +283,7 @@ Rdxport.ShowView.prototype.uploadError = function(upload, file, msg, xhr, acknow //var msg = $(xmlDoc); //var responseCode = msg.find('ResponseCode').text(); //var errorString = msg.find('ErrorString').text(); - var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').html(' ' + errorString)); + var reason = $('<span>').addClass('label').addClass('label-danger').text(responseCode).after($('<b>').text(' ' + errorString)); var dismiss_button = '<button class="btn btn-info btn-xs">' + '<span class="glyphicon glyphicon-remove"></span> Ok</button>'; diff --git a/www/js/utils.js b/www/js/utils.js index 26b0822..7e7a656 100644 --- a/www/js/utils.js +++ b/www/js/utils.js @@ -108,8 +108,8 @@ function get_rd_week(msEpoch) { } function locationHrefValue() { - var value = window.location.href.match(/import.helsinki.at\/([a-z]+)\/?.*/); - return value ? value[1] : ''; + var value = window.location.href.match(/import.helsinki.at\/([a-z]+)\/?([a-z0-9]+)?\/?.*/); + return value ? value : ''; } /* @@ -141,6 +141,10 @@ jQuery.fn.brightness = function() { } }; +jQuery.fn.sort = function() { + return this.pushStack(jQuery.makeArray([].sort.apply(this, arguments))); +}; + function updateProgressBar($el, upload) { if(upload.uploadprogress.progress < 99) { var bytes_str = Number((upload.uploadprogress.bytesSent/1024)/1024).toFixed(1) + " von " + diff --git a/www/styles/jingles.css b/www/styles/jingles.css index 1dc50c1..d8d5116 100644 --- a/www/styles/jingles.css +++ b/www/styles/jingles.css @@ -21,10 +21,14 @@ */ #app-jingles .group { - margin-bottom: 40px; + margin-bottom: 40px; } #app-jingles table .btn { - margin-top: 0.3em; - margin-left: 0.6em; + margin-top: 0.3em; + margin-left: 0.6em; +} + +h2.jingle-title { + font-weight: bold; } diff --git a/www/styles/main-style.css b/www/styles/main-style.css index fc7e357..a3e0c7a 100644 --- a/www/styles/main-style.css +++ b/www/styles/main-style.css @@ -32,6 +32,14 @@ body { width: 100% !important; } +#loginbox, #mainwindow { + display: none; +} + +#app-shows, #app-jingles, #app-musicpools, #app-musicgrid { + display: none; +} + .progress { margin-bottom: 0; } @@ -68,9 +76,8 @@ body { } #clock span.clock-time { - padding: 0.2em; font-weight: bold; - font-size: 1.6em; + font-size: 1.8em; } #uploadModal div.modal-body { diff --git a/www/styles/musicgrid.css b/www/styles/musicgrid.css index 44e72c0..53e2c01 100644 --- a/www/styles/musicgrid.css +++ b/www/styles/musicgrid.css @@ -21,5 +21,9 @@ */ #app-musicgrid { - padding: 0; + padding: 0; +} + +#musicgrid-title { + font-weight: bold; } diff --git a/www/styles/musicpools.css b/www/styles/musicpools.css index beca7bc..c0652f1 100644 --- a/www/styles/musicpools.css +++ b/www/styles/musicpools.css @@ -19,3 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with rhwebimport. If not, see <http://www.gnu.org/licenses/>. */ + +#musicpool-title { + font-weight: bold; +} + +#musicpool-header-spacer { + margin-top: 1.3em; +} diff --git a/www/styles/shows.css b/www/styles/shows.css index 2daa8b6..045ae9c 100644 --- a/www/styles/shows.css +++ b/www/styles/shows.css @@ -22,9 +22,19 @@ #show-title { text-align: center; + font-weight: bold; } #show-details { margin-top: 1.5em; margin-bottom: 1em; } + +.label-disabled { + background-color: #EEE; + color: #888; +} + +#show-header-spacer { + margin-top: 1.3em; +} |