Commit 26cf5bc8 authored by david's avatar david
Browse files

Improved loading behaviour. #1

parent 4869c19e
html, body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}
...@@ -19,9 +19,10 @@ ...@@ -19,9 +19,10 @@
logo="https://o94.at/themes/custom/radio_orange/logo1.png" logo="https://o94.at/themes/custom/radio_orange/logo1.png"
logosize="180px" logosize="180px"
api="http://localhost:8008/api/v1/" api="http://localhost:8008/api/v1/"
css="http://localhost:8008/css/aura.css" css="/sample/o94.css"
unknowntitle="Unbekannter Titel" unknowntitle="Unbekannter Titel"
noschedulemessage="Keine weiteren Sendungen!" nocurrentschedule="Derzeit keine Sendung"
nonextschedule="Keine weiteren Sendungen"
playoffset=3> playoffset=3>
</aura-clock> </aura-clock>
......
<svelte:options tag="aura-clock"/> <svelte:options tag="aura-clock"/>
<main bind:this={rootElement}></main> <main bind:this={rootElement}></main>
<script> <script>
import { onMount } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
export let css = ""; export let css = "";
export let api = "http://localhost:8008/api/v1/"; export let api = "http://localhost:8008/api/v1/";
export let name = "Studio Clock"; export let name = "Studio Clock";
export let logo = "https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png"; export let logo = "https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png";
export let logosize = "100px"; export let logosize = "100px";
export let noschedulemessage = "Nothing scheduled next!"; export let nocurrentschedule = "Right now, there's no show playing";
export let nonextschedule = "Nothing scheduled next!";
export let unknowntitle = "Unknown Title"; export let unknowntitle = "Unknown Title";
export let playoffset = 3; export let playoffset = 3;
let time = new Date(); let time = new Date();
let queryCurrent = "clock"; let queryCurrent = "clock";
let rootElement; let rootElement;
let data; let promise;
let currentTrack = null; let prevClockData = null;
let clockData = null;
let currentTrackElement = null;
let timeLeft; let timeLeft;
let scheduleTimeLeft = 0;
let reloadTime = 10;
let reloadWait = 0;
// these automatically update when `time` // these automatically update when `time`
// changes, because of the `$:` prefix // changes, because of the `$:` prefix
...@@ -25,27 +30,41 @@ ...@@ -25,27 +30,41 @@
$: minutes = time.getMinutes(); $: minutes = time.getMinutes();
$: seconds = time.getSeconds(); $: seconds = time.getSeconds();
data = fetchApi(queryCurrent); promise = fetchApi(queryCurrent);
/* When component is mounted to the DOM */ /* When component is mounted to the DOM */
onMount(() => { onMount(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
time = new Date(); time = new Date();
timeLeft -= 1; timeLeft -= 1;
scheduleTimeLeft -= 1;
if (timeLeft <= 0 || data == null) {
currentTrack = null; /* End of track or end of schedule - load new data */
data = null; if (timeLeft <= 0 || scheduleTimeLeft <= 0) {
data = fetchApi(queryCurrent); /* For some seconds refresh every second, to work around API timing delays */
if (timeLeft <= 0 && timeLeft >= -3 || scheduleTimeLeft <= 0 && scheduleTimeLeft >= -3 || reloadWait == 0) {
promise = fetchApi(queryCurrent);
reloadWait = reloadTime;
}
reloadWait -= 1;
} }
}, 1000); }, 1000);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}); });
/* Load Clock data from the API */
/* Called after the component has been updated */
afterUpdate(async () => {
scrollToActiveTrack();
});
/* Load clock data from the API */
async function fetchApi(query) { async function fetchApi(query) {
let response; let response;
let data; let data;
...@@ -71,26 +90,61 @@ ...@@ -71,26 +90,61 @@
} }
} }
/* Initialize the component */ /* Initialize the component */
function initComponent(info) { function initComponent(value) {
/* Load external CSS */ /* Load external CSS */
if (css != null) if (css != null)
loadExternalCss(rootElement, css); loadExternalCss(rootElement, css);
/* Set currently loaded data */ /* Set currently loaded data */
if (currentTrack == null && info != null && info.current_track != null) { if (value != null) {
currentTrack = info; clockData = value;
console.log("Current Data", value);
if (value.current_track != null) {
let t = time - Date.parse(value.current_track.track_start);
t = parseInt(t/1000);
timeLeft = value.current_track.track_duration - t - playoffset;
}
let t = time - Date.parse(info.current_track.track_start); if (value.current_schedule != null) {
t = parseInt(t/1000); let schedule_end = Date.parse(value.current_schedule.schedule_end);
schedule_end = parseInt(schedule_end/1000);
timeLeft = info.current_track.track_duration - t - playoffset; scheduleTimeLeft = schedule_end - time;
console.log("Current Data", info); } else {
/* Decrease time left in any case to avoid reloading too often */
scheduleTimeLeft -= 1;
}
} }
return ""; return "";
} }
/* Checks if there's an existing, valid schedule */
function hasValidSchedule(value) {
if (value.current_schedule != null) {
if (value.current_schedule.schedule_end != null) {
let schedule_end = Date.parse(value.current_schedule.schedule_end);
let diff = schedule_end - time;
if (diff >= 0)
return true;
}
}
return false;
}
/* Checks if there is an existing valid playlist */
function hasValidPlaylist(value) {
if (hasValidSchedule(value))
if (value.current_playlist != null)
return true;
return false;
}
/* Display the title of a track */ /* Display the title of a track */
function displayTitle(track) { function displayTitle(track) {
if (track != null) { if (track != null) {
...@@ -105,6 +159,7 @@ ...@@ -105,6 +159,7 @@
return ""; return "";
} }
/* Format the time */ /* Format the time */
function formatTime(seconds) { function formatTime(seconds) {
if (seconds != null && Number.isInteger(seconds)) { if (seconds != null && Number.isInteger(seconds)) {
...@@ -121,17 +176,19 @@ ...@@ -121,17 +176,19 @@
return ""; return "";
} }
/* Display the name of a show */ /* Display the name of a show */
function displayShowName(schedule) { function displayShowName(schedule) {
let name = "" let name = ""
if (schedule == null || schedule.show_name == null) { if (schedule == null || schedule.show_name == null) {
name = '<span class="error">'+noschedulemessage+'</span>'; name = '<span class="error">'+nonextschedule+'</span>';
} else { } else {
name = schedule.show_name; name = schedule.show_name;
} }
return name; return name;
} }
/* Display the schedule of a show */ /* Display the schedule of a show */
function displayShowSchedule(schedule) { function displayShowSchedule(schedule) {
let str = ""; let str = "";
...@@ -146,7 +203,7 @@ ...@@ -146,7 +203,7 @@
hour: '2-digit', hour: '2-digit',
minute:'2-digit' minute:'2-digit'
}); });
str = "(" + scheduleStart; str = "" + scheduleStart;
} }
if (schedule.schedule_end != null) { if (schedule.schedule_end != null) {
scheduleEnd = new Date(Date.parse(schedule.schedule_end)); scheduleEnd = new Date(Date.parse(schedule.schedule_end));
...@@ -154,30 +211,24 @@ ...@@ -154,30 +211,24 @@
hour: '2-digit', hour: '2-digit',
minute:'2-digit' minute:'2-digit'
}); });
str = str + " - " + scheduleEnd + ")"; str = str + " - " + scheduleEnd + "";
} else { } else {
str += ")"; str += "";
} }
} }
return str; return str;
} }
/* Check if the given track is currently playing */ /* Check if the given track is currently playing */
function isActive(entry, currentTrack) { function isActive(entry, currentTrack) {
if (currentTrack != null && entry.track_num == currentTrack.track_num) { if (currentTrack != null && entry.track_num == currentTrack.track_num) {
// Scroll to current playlist entry
// location.hash = "#current-playlist-entry";
var element = document.querySelector("#current-playlist-entry");
if (element != null)
element.scrollIntoView();
return true; return true;
} }
return false; return false;
} }
/* Hack to load external CSS into the Web Component */ /* Hack to load external CSS into the Web Component */
function loadExternalCss(root, file) { function loadExternalCss(root, file) {
let element = document.createElement("link"); let element = document.createElement("link");
...@@ -187,15 +238,13 @@ ...@@ -187,15 +238,13 @@
root.appendChild(element); root.appendChild(element);
} }
/* Called when the clock finished rendering */
function finalize_rendering() {
/* Sroll to currently playing track */ /* Scrolls to the track currently playing */
var element = document.querySelector("#current-playlist-entry"); function scrollToActiveTrack() {
if (element != null) if (currentTrackElement != null)
element.scrollIntoView(); currentTrackElement.scrollIntoView();
return ""
} }
</script> </script>
<style> <style>
...@@ -210,13 +259,14 @@ ...@@ -210,13 +259,14 @@
margin: 0; margin: 0;
font-size: 3em; font-size: 3em;
line-height: 80px; line-height: 80px;
color: #AAA;
} }
#station-logo { #station-logo {
align-content: left; align-content: left;
text-align: right; text-align: right;
margin: 0 40px 0 10px; margin: 0 40px 0 10px;
opacity: 0.5; opacity: 0.88;
filter: invert(100%); filter: invert(100%);
} }
...@@ -240,6 +290,13 @@ ...@@ -240,6 +290,13 @@
padding: 25px 25px 25px 50px; padding: 25px 25px 25px 50px;
} }
#current-schedule {
border: 2px solid #333;
margin: 20px 20px 40px 20px;
background-color: #111;
height: 100%;
}
#current-schedule, #current-schedule,
#next-schedule { #next-schedule {
margin: 0 0 40px 20px; margin: 0 0 40px 20px;
...@@ -252,25 +309,31 @@ ...@@ -252,25 +309,31 @@
} }
#current-schedule .schedule-title { #current-schedule .schedule-title {
color: #ccc;
font-size: 3.3em;
text-align: center; text-align: center;
height: 100px;
}
#current-schedule .schedule-title h1 {
color: #ccc;
font-size: 2.8em;
position: relative;
top: 30%;
transform: translateY(-50%);
} }
#next-schedule .schedule-title { #next-schedule .schedule-title {
color: gray !important; color: gray !important;
font-size: 2em; font-size: 2em;
} }
#playlist { #playlist {
border: 2px solid #333; height: calc(100% - 155px);
margin: 20px 20px 40px 20px;
padding: 10px;
height: calc(80% - 100px);
overflow-y: auto; overflow-y: auto;
scroll-behavior: smooth; scroll-behavior: smooth;
background-color: #111; padding: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
border-top: 1px solid #333;;
} }
#playlist::-webkit-scrollbar-track #playlist::-webkit-scrollbar-track
...@@ -295,12 +358,14 @@ ...@@ -295,12 +358,14 @@
#playlist ol { #playlist ol {
margin-left: 33px; margin-left: 33px;
height: 95%;
} }
.playlist-entry { .playlist-entry {
font-size: 1.9em; font-size: 1.9em;
padding-left: 53px; padding-left: 53px;
padding-bottom: 13px; padding-bottom: 13px;
color: #AAA;
} }
#current-track * { #current-track * {
...@@ -331,7 +396,7 @@ ...@@ -331,7 +396,7 @@
} }
.error { .error {
font-size: 1.3em; font-size: 1.1em;
color:red; color:red;
height:100%; height:100%;
display : flex; display : flex;
...@@ -384,11 +449,6 @@ ...@@ -384,11 +449,6 @@
text-decoration: underline; text-decoration: underline;
} }
footer #aura-logo {
filter: invert(100%);
width: 75px;
margin: 0 0 20px 0;
}
</style> </style>
...@@ -449,66 +509,75 @@ ...@@ -449,66 +509,75 @@
<div id="right-column" class="column"> <div id="right-column" class="column">
{#await data} {#await promise}
<div class="spinner-border mt-5" role="status"> <div class="spinner-border mt-5" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
{:then value} {:then value}
{initComponent(value)} {initComponent(value)}
{#if value.current_schedule}
<div id="current-schedule">
<h1 class="schedule-title">{@html displayShowName(value.current_schedule)} {displayShowSchedule(value.current_schedule)}</h1>
<!-- <div class="schedule-details">
<b>Type:</b> {value.current_schedule}, <b>Host:</b> {value.current_schedule}</div>-->
</div>
<div id="playlist">
{#if value.current_playlist}
<ol>
{#each value.current_playlist.entries as entry, index}
{#if isActive(entry, value.current_track)}
<li id="current-playlist-entry" class="playlist-entry is-active">
<!-- <span class="play-icon">&#9654;</span> -->
<span class="track-title">{displayTitle(entry)}</span>
<span class="track-time-left">({formatTime(timeLeft)})</span>
</li>
<div id="current-schedule">
<div class="schedule-title">
<h1>
{#if hasValidSchedule(value)}
{@html displayShowName(value.current_schedule)} {displayShowSchedule(value.current_schedule)}
{:else}
<span class="error">{nocurrentschedule}</span>
{/if}
</h1>
</div>
<div id="playlist">
{#if hasValidPlaylist(value)}
<ol>
{#each value.current_playlist.entries as entry, index}
{#if isActive(entry, value.current_track)}
<li id="current-playlist-entry" class="playlist-entry is-active" bind:this={currentTrackElement}>
<!-- <span class="play-icon">&#9654;</span> -->
<span class="track-title">{displayTitle(entry)}</span>
<span class="track-time-left">({formatTime(timeLeft)})</span>
</li>
{:else}
<li class="playlist-entry">
<span class="track-title">{displayTitle(entry)}</span>
<span class="track-duration">({formatTime(entry.track_duration)})</span>
</li>