From f6800c86683df31c3a2080ba2b0657a97315a980 Mon Sep 17 00:00:00 2001
From: Daniel Friesel <derf@finalrewind.org>
Date: Fri, 10 May 2019 17:58:13 +0200
Subject: [PATCH] javascript-based refresh of journey progress and countdown

---
 public/service-worker.js                  | 18 ++++----
 public/static/css/material-icons.css      |  8 ++--
 public/static/js/travelynx-actions.js     | 51 ++++++++++++++++++++---
 public/static/js/travelynx-actions.min.js |  2 +-
 public/static/manifest.json               | 12 +++---
 public/static/{v15 => v17}                |  0
 templates/_checked_in.html.ep             |  4 +-
 templates/_public_status_card.html.ep     |  4 +-
 templates/layouts/default.html.ep         |  2 +-
 9 files changed, 72 insertions(+), 29 deletions(-)
 rename public/static/{v15 => v17} (100%)

diff --git a/public/service-worker.js b/public/service-worker.js
index 09dca864..ce9653d0 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -1,14 +1,14 @@
-const CACHE_NAME = 'static-cache-v16';
+const CACHE_NAME = 'static-cache-v17';
 const FILES_TO_CACHE = [
   '/offline.html',
-  '/static/v16/css/materialize.min.css',
-  '/static/v16/css/material-icons.css',
-  '/static/v16/css/local.css',
-  '/static/v16/js/jquery-3.4.1.min.js',
-  '/static/v16/js/materialize.min.js',
-  '/static/v16/js/travelynx-actions.min.js',
-  '/static/v16/js/autocomplete.min.js',
-  '/static/v16/js/geolocation.min.js',
+  '/static/v17/css/materialize.min.css',
+  '/static/v17/css/material-icons.css',
+  '/static/v17/css/local.css',
+  '/static/v17/js/jquery-3.4.1.min.js',
+  '/static/v17/js/materialize.min.js',
+  '/static/v17/js/travelynx-actions.min.js',
+  '/static/v17/js/autocomplete.min.js',
+  '/static/v17/js/geolocation.min.js',
 ];
 
 self.addEventListener('install', (evt) => {
diff --git a/public/static/css/material-icons.css b/public/static/css/material-icons.css
index 9404262d..024ac27d 100644
--- a/public/static/css/material-icons.css
+++ b/public/static/css/material-icons.css
@@ -2,12 +2,12 @@
   font-family: 'Material Icons';
   font-style: normal;
   font-weight: 400;
-  src: url(/static/v16/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
+  src: url(/static/v17/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
   src: local('Material Icons'),
        local('MaterialIcons-Regular'),
-       url(/static/v16/fonts/MaterialIcons-Regular.woff2) format('woff2'),
-       url(/static/v16/fonts/MaterialIcons-Regular.woff) format('woff'),
-       url(/static/v16/fonts/MaterialIcons-Regular.ttf) format('truetype');
+       url(/static/v17/fonts/MaterialIcons-Regular.woff2) format('woff2'),
+       url(/static/v17/fonts/MaterialIcons-Regular.woff) format('woff'),
+       url(/static/v17/fonts/MaterialIcons-Regular.ttf) format('truetype');
 }
 
 .material-icons {
diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js
index d3e2a7da..fd5df023 100644
--- a/public/static/js/travelynx-actions.js
+++ b/public/static/js/travelynx-actions.js
@@ -1,3 +1,21 @@
+var j_duration = 0;
+var j_arrival = 0;
+function upd_journey_data() {
+	$('.countdown').each(function() {
+		j_duration = $(this).data('duration');
+		j_arrival = $(this).data('arrival');
+	});
+}
+function upd_countdown() {
+	var now = Date.now() / 1000;
+	if (j_arrival > 0) {
+		if (j_arrival > now) {
+			$('.countdown').text('Ankunft in ' + Math.round((j_arrival - now)/60) + ' Minuten');
+		} else {
+			$('.countdown').text('Ziel erreicht');
+		}
+	}
+}
 function tvly_run(link, req, err_callback) {
 	var error_icon = '<i class="material-icons">error</i>';
 	var progressbar = $('<div class="progress"><div class="indeterminate"></div></div>');
@@ -21,10 +39,11 @@ function tvly_update() {
 	$.get('/ajax/status_card.html', function(data) {
 		$('.statuscol').html(data);
 		tvly_reg_handlers();
-		setTimeout(tvly_update, 20000);
+		upd_journey_data();
+		setTimeout(tvly_update, 40000);
 	}).fail(function() {
 		$('.sync-failed-marker').css('display', 'block');
-		$('.countdown').html('&nbsp;');
+		upd_countdown();
 		setTimeout(tvly_update, 5000);
 	});
 }
@@ -35,13 +54,29 @@ function tvly_update_public() {
 	});
 	$.get('/ajax/status/' + user_name + '.html', function(data) {
 		$('.publicstatuscol').html(data);
-		setTimeout(tvly_update_public, 20000);
+		upd_journey_data();
+		setTimeout(tvly_update_public, 40000);
 	}).fail(function() {
 		$('.sync-failed-marker').css('display', 'block');
-		$('.countdown').html('&nbsp;');
+		upd_countdown();
 		setTimeout(tvly_update_public, 5000);
 	});
 }
+function tvly_journey_progress() {
+	var now = Date.now() / 1000;
+	var progress = 0;
+	if (j_duration > 0) {
+		progress = 1 - ((j_arrival - now) / j_duration);
+		if (progress < 0) {
+			progress = 0;
+		}
+		if (progress > 1) {
+			progress = 1;
+		}
+		$('.progress .determinate').css('width', (progress * 100) + '%');
+		setTimeout(tvly_journey_progress, 5000);
+	}
+}
 function tvly_reg_handlers() {
 	$('.action-checkin').click(function() {
 		var link = $(this);
@@ -107,10 +142,14 @@ function tvly_reg_handlers() {
 $(document).ready(function() {
 	tvly_reg_handlers();
 	if ($('.statuscol .autorefresh').length) {
-		setTimeout(tvly_update, 20000);
+		upd_journey_data();
+		setTimeout(tvly_update, 40000);
+		setTimeout(tvly_journey_progress, 5000);
 	}
 	if ($('.publicstatuscol .autorefresh').length) {
-		setTimeout(tvly_update_public, 20000);
+		upd_journey_data();
+		setTimeout(tvly_update_public, 40000);
+		setTimeout(tvly_journey_progress, 5000);
 	}
 	$('a[href]').click(function() {
 		$('nav .preloader-wrapper').addClass('active');
diff --git a/public/static/js/travelynx-actions.min.js b/public/static/js/travelynx-actions.min.js
index 376f348d..a249bdf4 100644
--- a/public/static/js/travelynx-actions.min.js
+++ b/public/static/js/travelynx-actions.min.js
@@ -1 +1 @@
-function tvly_run(a,t,e){var n='<i class="material-icons">error</i>',c=$('<div class="progress"><div class="indeterminate"></div></div>');a.hide(),a.after(c),$.post("/action",t,function(t){t.success?$(location).attr("href",t.redirect_to):(M.toast({html:n+" "+t.error}),c.remove(),e&&e(),a.append(" "+n),a.show())})}function tvly_update(){$.get("/ajax/status_card.html",function(t){$(".statuscol").html(t),tvly_reg_handlers(),setTimeout(tvly_update,2e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),$(".countdown").html("&nbsp;"),setTimeout(tvly_update,5e3)})}function tvly_update_public(){var t;$(".publicstatuscol").each(function(){t=$(this).data("user")}),$.get("/ajax/status/"+t+".html",function(t){$(".publicstatuscol").html(t),setTimeout(tvly_update_public,2e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),$(".countdown").html("&nbsp;"),setTimeout(tvly_update_public,5e3)})}function tvly_reg_handlers(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")})}),$(".action-checkout").click(function(){var t=$(this),a={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,a,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){var t=$(this);tvly_run(t,{action:"undo",undo_id:t.data("id")})}),$(".action-cancelled-from").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_from",station:t.data("station"),train:t.data("train")})}),$(".action-cancelled-to").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_to",station:t.data("station"),force:!0})}),$(".action-delete").click(function(){var t=$(this),a={action:"delete",id:t.data("id"),checkin:t.data("checkin"),checkout:t.data("checkout")};really_delete=confirm("Diese Zugfahrt wirklich löschen? Der Eintrag wird sofort aus der Datenbank entfernt und kann nicht wiederhergestellt werden."),really_delete&&tvly_run(t,a)})}$(document).ready(function(){tvly_reg_handlers(),$(".statuscol .autorefresh").length&&setTimeout(tvly_update,2e4),$(".publicstatuscol .autorefresh").length&&setTimeout(tvly_update_public,2e4),$("a[href]").click(function(){$("nav .preloader-wrapper").addClass("active")})});
+function upd_journey_data(){$(".countdown").each(function(){j_duration=$(this).data("duration"),j_arrival=$(this).data("arrival")})}function upd_countdown(){var t=Date.now()/1e3;j_arrival>0&&(j_arrival>t?$(".countdown").text("Ankunft in "+Math.round((j_arrival-t)/60)+" Minuten"):$(".countdown").text("Ziel erreicht"))}function tvly_run(t,a,e){var n='<i class="material-icons">error</i>',i=$('<div class="progress"><div class="indeterminate"></div></div>');t.hide(),t.after(i),$.post("/action",a,function(a){a.success?$(location).attr("href",a.redirect_to):(M.toast({html:n+" "+a.error}),i.remove(),e&&e(),t.append(" "+n),t.show())})}function tvly_update(){$.get("/ajax/status_card.html",function(t){$(".statuscol").html(t),tvly_reg_handlers(),upd_journey_data(),setTimeout(tvly_update,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update,5e3)})}function tvly_update_public(){var t;$(".publicstatuscol").each(function(){t=$(this).data("user")}),$.get("/ajax/status/"+t+".html",function(t){$(".publicstatuscol").html(t),upd_journey_data(),setTimeout(tvly_update_public,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update_public,5e3)})}function tvly_journey_progress(){var t=Date.now()/1e3,a=0;j_duration>0&&(a=1-(j_arrival-t)/j_duration,a<0&&(a=0),a>1&&(a=1),$(".progress .determinate").css("width",100*a+"%"),setTimeout(tvly_journey_progress,5e3))}function tvly_reg_handlers(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")})}),$(".action-checkout").click(function(){var t=$(this),a={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,a,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){var t=$(this);tvly_run(t,{action:"undo",undo_id:t.data("id")})}),$(".action-cancelled-from").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_from",station:t.data("station"),train:t.data("train")})}),$(".action-cancelled-to").click(function(){var t=$(this);tvly_run(t,{action:"cancelled_to",station:t.data("station"),force:!0})}),$(".action-delete").click(function(){var t=$(this),a={action:"delete",id:t.data("id"),checkin:t.data("checkin"),checkout:t.data("checkout")};really_delete=confirm("Diese Zugfahrt wirklich löschen? Der Eintrag wird sofort aus der Datenbank entfernt und kann nicht wiederhergestellt werden."),really_delete&&tvly_run(t,a)})}var j_duration=0,j_arrival=0;$(document).ready(function(){tvly_reg_handlers(),$(".statuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update,4e4),setTimeout(tvly_journey_progress,5e3)),$(".publicstatuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update_public,4e4),setTimeout(tvly_journey_progress,5e3)),$("a[href]").click(function(){$("nav .preloader-wrapper").addClass("active")})});
diff --git a/public/static/manifest.json b/public/static/manifest.json
index c6231238..4c13cbf4 100644
--- a/public/static/manifest.json
+++ b/public/static/manifest.json
@@ -3,27 +3,27 @@
   "short_name": "Travelynx",
   "scope": "/",
   "icons": [{
-    "src": "/static/v16/icons/icon-128x128.png",
+    "src": "/static/v17/icons/icon-128x128.png",
       "sizes": "128x128",
       "type": "image/png"
     }, {
-      "src": "/static/v16/icons/icon-144x144.png",
+      "src": "/static/v17/icons/icon-144x144.png",
       "sizes": "144x144",
       "type": "image/png"
     }, {
-      "src": "/static/v16/icons/icon-152x152.png",
+      "src": "/static/v17/icons/icon-152x152.png",
       "sizes": "152x152",
       "type": "image/png"
     }, {
-      "src": "/static/v16/icons/icon-192x192.png",
+      "src": "/static/v17/icons/icon-192x192.png",
       "sizes": "192x192",
       "type": "image/png"
     }, {
-      "src": "/static/v16/icons/icon-256x256.png",
+      "src": "/static/v17/icons/icon-256x256.png",
       "sizes": "256x256",
       "type": "image/png"
     }, {
-      "src": "/static/v16/icons/icon-512x512.png",
+      "src": "/static/v17/icons/icon-512x512.png",
       "sizes": "512x512",
       "type": "image/png"
     }],
diff --git a/public/static/v15 b/public/static/v17
similarity index 100%
rename from public/static/v15
rename to public/static/v17
diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep
index 43fda027..a7236916 100644
--- a/templates/_checked_in.html.ep
+++ b/templates/_checked_in.html.ep
@@ -3,7 +3,9 @@
 		<i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i>
 		<span class="card-title">Eingecheckt in <%= $journey->{train_type} %> <%= $journey->{train_no} %></span>
 		<p>
-			<div class="center countdown">
+			<div class="center-align countdown"
+				data-duration="<%= $journey->{journey_duration} // 0 %>"
+				data-arrival="<%= $journey->{real_arrival}->epoch %>">
 				% if ($journey->{departure_countdown} > 120) {
 					Abfahrt in <%= sprintf('%.f', $journey->{departure_countdown} / 60) %> Minuten
 				% }
diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep
index 4b3b787d..086c71be 100644
--- a/templates/_public_status_card.html.ep
+++ b/templates/_public_status_card.html.ep
@@ -4,7 +4,9 @@
 			<i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i>
 			<span class="card-title"><%= $name %> ist unterwegs</span>
 			<p>
-				<div class="center-align countdown">
+				<div class="center-align countdown"
+					data-duration="<%= $journey->{journey_duration} // 0 %>"
+					data-arrival="<%= $journey->{real_arrival}->epoch %>">
 					<b><%= $journey->{train_type} %> <%= $journey->{train_no} %></b><br/>
 					% if ($journey->{departure_countdown} > 120) {
 						Abfahrt in <%= sprintf('%.f', $journey->{departure_countdown} / 60) %> Minuten
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index 98289f78..b1905831 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -9,7 +9,7 @@
 	<meta name="apple-mobile-web-app-capable" content="yes">
 	<meta name="apple-mobile-web-app-status-bar-style" content="black">
 	<meta name="apple-mobile-web-app-title" content="Weather PWA">
-	% my $av = 'v16'; # asset version
+	% my $av = 'v17'; # asset version
 	<link rel="apple-touch-icon" href="/static/<%= $av %>/icons/icon-152x152.png">
 	<link rel="manifest" href="/static/<%= $av %>/manifest.json">
 	%= stylesheet "/static/${av}/css/materialize.min.css"
-- 
GitLab