From 1c2b42bbfa6d7f87853937bae50ea054c61c095c Mon Sep 17 00:00:00 2001
From: Anton Sarukhanov <code@ant.sr>
Date: Fri, 15 Apr 2016 23:16:02 -0400
Subject: [PATCH] Embed Predictions works now.

---
 app.py                     |   9 ++-
 static/css/map.css         |  20 ++----
 static/css/predictions.css |  28 ++++++--
 static/js/map.js           |   6 +-
 static/js/predictions.js   | 128 ++++++++++++++++++++++---------------
 templates/modal-embed.html |   7 +-
 templates/predictions.html |   5 +-
 7 files changed, 126 insertions(+), 77 deletions(-)

diff --git a/app.py b/app.py
index e52923e..85dbbbc 100644
--- a/app.py
+++ b/app.py
@@ -38,7 +38,7 @@ def map_embed(mode):
     if not mode or mode == "m":
         return render_template('map.html', agency=agency, config=app.config, embed=True)
     elif mode == "p":
-        return render_template('predictions.html', agency=agency, config=app.config)
+        return render_template('predictions.html', agency=agency, config=app.config, embed=True)
 
 @app.route('/ajax')
 def ajax():
@@ -106,6 +106,7 @@ def ajax():
     def predictions():
         """ Serve arrival predictions only. """
         from models import Prediction
+        stops = request.args.get('stops')
         now = datetime.now()
         p_inner = db.session.query(Prediction.vehicle, Prediction.stop_id,
                                    db.func.max(Prediction.api_call_id).label("api_call_id"))\
@@ -117,7 +118,11 @@ def ajax():
                 p_inner.c.stop_id == Prediction.stop_id
             )).filter(
                 Agency.tag==agency_tag,
-                Prediction.prediction >= now)\
+                Prediction.prediction >= now)
+        if stops:
+            predictions = predictions.filter(
+                Prediction.stop_id.in_(stops.split(',')))
+        predictions = predictions\
             .group_by(Prediction.id, Prediction.vehicle, Prediction.stop_id)\
             .all()
         return {
diff --git a/static/css/map.css b/static/css/map.css
index 1f72df1..53904d4 100644
--- a/static/css/map.css
+++ b/static/css/map.css
@@ -160,11 +160,13 @@ fieldset legend ~ label {
     box-sizing: border-box;
     border: 2px solid #888;
     position: absolute;
-    width: 80%;
+    top: 50%;
     left: 10%;
-    top: 7.5%;
+    width: 80%;
+    transform: translateY(-50%);
     padding: 1em;
-    max-height: 80%;
+    max-height: 100%;
+    margin: 0 auto;
     overflow: auto;
     z-index: 2000;
 }
@@ -221,20 +223,8 @@ body.embed .embed-only {
     .modal {
         width: 100%;
         left: 0;
-        top: 10%;
         border-radius: 0;
         border-left: none;
         border-right: none;
     }
 }
-
-@media all and (max-height: 400px) {
-    .modal {
-        max-height: 100%;
-        height: 100%;
-        top: 0;
-        border-radius: 0;
-        border-top: none;
-        border-bottom: none;
-    }
-}
diff --git a/static/css/predictions.css b/static/css/predictions.css
index 5c9174a..82724f4 100644
--- a/static/css/predictions.css
+++ b/static/css/predictions.css
@@ -1,5 +1,25 @@
-#predictions {
-    width: 100%;
-    height: 1em;
-    background-color: #f00;
+body {
+    margin: 0;
+    padding: .4em;
+    background-color: #444;
+    color: #fff;
+    font-family: sans-serif;
 }
+#predictions header {
+    text-align: center;
+    font-weight: bold;
+    text-align: center;
+}
+#predictions p {
+
+}
+#predictions .prediction.lt1min {
+    color: #f00;
+}
+#predictions .prediction.lt2mins {
+    color: #f80;
+}
+#predictions .prediction.lt5mins {
+    color: #ff0;
+}
+
diff --git a/static/js/map.js b/static/js/map.js
index 607a20d..8fb1205 100644
--- a/static/js/map.js
+++ b/static/js/map.js
@@ -409,10 +409,12 @@ BusMap.Map = function(opts) {
     function zoomShowHide() {
         var zoom = that.leaflet.getZoom();
         if (that.vehicleMarkersGroup) {
-            if (zoom >= that.zoomShowVehicles && !(that.leaflet.hasLayer(that.vehicleMarkersGroup))) {
+            if (zoom >= that.zoomShowVehicles
+              && !(that.leaflet.hasLayer(that.vehicleMarkersGroup))) {
                 that.leaflet.addLayer(that.vehicleMarkersGroup);
                 if (!that.opts.embed) $('#msg-zoomForVehicles').hide();
-            } else if (zoom < that.zoomShowVehicles && that.leaflet.hasLayer(that.vehicleMarkersGroup)) {
+            } else if (zoom < that.zoomShowVehicles
+              && that.leaflet.hasLayer(that.vehicleMarkersGroup)) {
                 that.leaflet.removeLayer(that.vehicleMarkersGroup);
                 if (!that.opts.embed) $('#msg-zoomForVehicles').show();
             }
diff --git a/static/js/predictions.js b/static/js/predictions.js
index 5998757..4830860 100644
--- a/static/js/predictions.js
+++ b/static/js/predictions.js
@@ -14,10 +14,16 @@ BusMap.Predictions = function(opts) {
         if (that.opts.refresh.routes) {
             setInterval(updateRoutes, that.opts.refresh.routes * 1000);
         }
-        if (that.opts.refresh.vehicles) {
-            setInterval(updateVehicles, that.opts.refresh.vehicles * 1000);
+        if (that.opts.refresh.predictions) {
+            setInterval(updatePredictions, that.opts.refresh.predictions * 1000);
         }
 
+        // Listen for hash change
+        window.onhashchange = function() {
+            console.log('foo');
+            setViewFromUrl();
+        };
+
     };
 
     /* Get Routes (and Stops, and Directions) */
@@ -33,14 +39,18 @@ BusMap.Predictions = function(opts) {
                 that.routes = data.routes;
                 updateStopsUI(that.stops);
             });
-        return that;
     };
 
     /* Get Vehicles (and Predictions) */
     function updatePredictions() {
+        if (!that.stops) {
+            updateRoutes();
+            return setTimeout(function() { updatePredictions() }, 500);
+        }
         var url = "ajax";
         var params = {
             query: "predictions",
+            stops: that.stop,
             agency: that.opts.agency,
         };
         $.getJSON(url, params)
@@ -58,68 +68,84 @@ BusMap.Predictions = function(opts) {
                         }
                         that.stops[pr.stop_id].predictions[pr.route].push(pr);
                     }
-                    if (that.vehicles && pr.vehicle in that.vehicles) {
-                        // Store this prediction with the relevant vehicle
-                        that.vehicles[pr.vehicle].predictions.push(pr);
-                    }
                 }
                 updatePredictionsUI(that.stops);
             });
-        return that;
     };
 
+    function applyViewString(view) {
+        if (!view || view == "") return false;
+        if (!that.stops) {
+            updateRoutes();
+            return setTimeout(function() { applyViewString(view) }, 500);
+        }
+        if (view.charAt(0).toLowerCase() == "s") return _avsStop(view);
+        function _avsStop(view) {
+            var stop = that.stops[view.substring(1)];
+            if (stop) {
+                that.stop = stop.id;
+                // Show this stop
+                $(that.opts.predictionsElement).html(
+                    "<header>" + stop.title + "</header>"
+                  + "<p></p>");
+                updatePredictions();
+            } else console.log('Unknown stop in view string: ' + view.substring(1));
+        }
+    }
+
+    function setViewFromUrl() {
+        var hash = window.location.hash.substring(1);
+        applyViewString(hash);
+    }
+
     /* Refresh (and/or create) UI elements for Stops */
     function updateStopsUI(stops) {
-        // TODO: this. usually this would be for a single stop but maybe multiple.
-        // identified via some option. that.opts.stops = ["foo", "bar", "baz"]?
-        // or...get it from the URL hash (like the map does)?
+        var hash = window.location.hash.substring(1);
+        if (hash == "") return false;
+        applyViewString(hash);
     }
 
     /* Refresh (and/or create) UI elements for Predictions */
     function updatePredictionsUI(stops) {
-        // TODO: Generate predictions ui view
-        for (var s in stops) {
-            var text = '<header>' + stops[s].title + '</header>';
-            if (stops[s].predictions) {
-                var predictions = [];
-                /*
-                var now = new Date();
-                var offset_mins = now.getTimezoneOffset();
-                for (r in stops[s].predictions) {
-                    if (!(r in that.routes)) {
-                        console.log("Unknown route " + r + " for stop " + stops[s].title);
-                    }
-                    var p_line = "<strong>" + that.routes[r].title + "</strong>: ";
-                    var times = [];
-                    // Sort by estimated time to arrival
-                    psorted = stops[s].predictions[r].sort(function(a,b){
-                        return new Date(a.prediction).getTime() - new Date(b.prediction).getTime();
-                    });
-                    for (p in psorted) {
-                        pr = psorted[p];
-                        var pdate = new Date(pr.prediction);
-                        var diff_sec = (pdate.getTime() - now.getTime()) / 1000;
-                        var diff_min = Math.ceil(diff_sec / 60) + offset_mins;
-                        // CSS classes for predictions
-                        var pclass = "";
-                        if (diff_min <= 1) { pclass = "lt1min"; }
-                        else if (diff_min <= 2 ) { pclass = "lt2mins"; }
-                        else if (diff_min <= 5 ) { pclass = "lt5mins"; }
-                        times.push("<span class='prediction " + pclass
-                                 + "' title='" + pr.vehicle + "'>"
-                                 + diff_min + "</span>");
-                    }
-                    p_line += times.join(", ");
-                    predictions.push("<span class='route'" + p_line + "</span>");
+        if (!that.stop) return false;
+        var s = that.stop;
+        if (stops[s].predictions) {
+            var predictions = [];
+            var now = new Date();
+            var offset_mins = now.getTimezoneOffset();
+            for (r in stops[s].predictions) {
+                if (!(r in that.routes)) {
+                    console.log("Unknown route " + r + " for stop " + stops[s].title);
                 }
-                */
-                if (predictions.length == 0) {
-                    predictions = ['<span class="none">No arrival predictions.</span>'];
+                var p_line = "<strong>" + that.routes[r].title + "</strong>: ";
+                var times = [];
+                // Sort by estimated time to arrival
+                psorted = stops[s].predictions[r].sort(function(a,b){
+                    return new Date(a.prediction).getTime() - new Date(b.prediction).getTime();
+                });
+                for (p in psorted) {
+                    pr = psorted[p];
+                    var pdate = new Date(pr.prediction);
+                    var diff_sec = (pdate.getTime() - now.getTime()) / 1000;
+                    var diff_min = Math.ceil(diff_sec / 60) + offset_mins;
+                    // CSS classes for predictions
+                    var pclass = "";
+                    if (diff_min <= 1) { pclass = "lt1min"; }
+                    else if (diff_min <= 2 ) { pclass = "lt2mins"; }
+                    else if (diff_min <= 5 ) { pclass = "lt5mins"; }
+                    times.push("<span class='prediction " + pclass
+                             + "' title='" + pr.vehicle + "'>"
+                             + diff_min + "</span>");
                 }
-                text += '<section class="predictions stop-predictions">'
-                      + predictions.sort().join("<br>") + '</section>';
-                // TODO: put output somewhere...
+                p_line += times.join(", ");
+                predictions.push("<span class='route'" + p_line + "</span>");
+            }
+            if (predictions.length == 0) {
+                predictions = ['<span class="none">No arrival predictions.</span>'];
             }
+            text = '<section class="predictions stop-predictions">'
+                  + predictions.sort().join("<br>") + '</section>';
+            $(that.opts.predictionsElement).find('p').html(text);
         }
     }
 
diff --git a/templates/modal-embed.html b/templates/modal-embed.html
index 312a8c6..5165b13 100644
--- a/templates/modal-embed.html
+++ b/templates/modal-embed.html
@@ -1,6 +1,9 @@
-<h2>Embed This Map</h2>
+<h2>Embed 
+    {% if agency.short_title %}{{ agency.short_title -}}
+    {% elif agency.title %}{{ agency.title }}{% endif %} Bus Map
+</h2>
 <p>
-    This map can be used in websites, displays, or digital signage solutions. To embed a list of predictions instead, change the <strong>Mode</strong> selector.
+    Use maps and/or stop predictions in your website or digital signage.
 </p>
 <h3>Embed Code</h3>
 <textarea id="embed-code" cols=100 rows=3  readonly=true></textarea>
diff --git a/templates/predictions.html b/templates/predictions.html
index ad582e3..e8de515 100644
--- a/templates/predictions.html
+++ b/templates/predictions.html
@@ -16,7 +16,10 @@
         var predictions = BusMap.Predictions({
             agency: {{ agency.tag|tojson|safe }},
             predictionsElement: $("#predictions").get(0),
-            refresh: 60,
+            refresh: {
+                routes: 60,
+                predictions: 10,
+            }
         });
     </script>
 {% endblock %}
-- 
GitLab