summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2021-08-23 20:22:51 +0200
committerDaniel Friesel <derf@finalrewind.org>2021-08-23 20:22:51 +0200
commit4eeb0036ecd4497d3900f46a7c5371d0e6a0bad6 (patch)
tree5cb03ed711f8c342eef6216b9cb1c17b39766437
parent9f698bdfe42a668dd18343f18fb02d158fc66158 (diff)
Add trip detail page0.5.0
-rwxr-xr-xbin/nvm154
-rw-r--r--sass/app.scss91
-rw-r--r--sass/dark.scss4
-rw-r--r--sass/light.scss6
-rw-r--r--static/css/dark.min.css2
-rw-r--r--static/css/light.min.css2
l---------static/v6 (renamed from static/v4)0
-rw-r--r--templates/departure_list.html2
-rw-r--r--templates/departures_page.html2
-rw-r--r--templates/header.html4
-rw-r--r--templates/landing_page.html2
-rw-r--r--templates/tripinfo.html72
-rw-r--r--templates/tripinfo_page.html32
13 files changed, 361 insertions, 12 deletions
diff --git a/bin/nvm b/bin/nvm
index 3df9013..41ac8c0 100755
--- a/bin/nvm
+++ b/bin/nvm
@@ -152,6 +152,114 @@ class TransportAPIs:
return None
+class Trip:
+ def __init__(self, obj):
+
+ for key in "departure plannedDeparture arrival plannedArrival".split():
+ try:
+ obj[key] = dateutil.parser.parse(obj[key])
+ except TypeError:
+ obj[key] = None
+
+ self.cancelled = None
+ self.__dict__.update(obj)
+
+ if "," in self.direction:
+ self.direction, self.suffix = self.direction.split(",", maxsplit=1)
+ else:
+ self.suffix = None
+
+ self.stopovers = list(map(Stopover, self.stopovers))
+ self.where = None
+ self.quoted_where = None
+
+ self.platform = None
+ self.plannedPlatform = None
+ self.platform_changed = None
+
+ def set_ref(self, stop_name):
+ self.where = stop_name
+ self.quoted_where = aiohttp.helpers.quote(self.where)
+ ref_pos = -1
+ for stopover in self.stopovers:
+ if stopover.stop["name"] == stop_name:
+ ref_pos = 0
+ self.arrival = stopover.arrival
+ self.arrivalDelay = stopover.arrivalDelay
+ self.plannedArrival = stopover.plannedArrival
+ self.departure = stopover.departure
+ self.departureDelay = stopover.departureDelay
+ self.plannedDeparture = stopover.plannedDeparture
+ self.platform = stopover.platform
+ self.plannedPlatform = stopover.plannedPlatform
+ self.platform_changed = stopover.platform_changed
+ elif ref_pos == 0:
+ ref_pos = 1
+ stopover.set_ref(ref_pos)
+
+ def set_relative(self, now_ts):
+ for stopover in self.stopovers:
+ stopover.set_relative(now_ts)
+
+
+class Stopover:
+ def __init__(self, obj):
+
+ for key in "departure plannedDeparture arrival plannedArrival".split():
+ try:
+ obj[key] = dateutil.parser.parse(obj[key])
+ except TypeError:
+ obj[key] = None
+
+ self.cancelled = None
+ self.__dict__.update(obj)
+
+ if "," in self.stop["name"]:
+ self.name, self.suffix = self.stop["name"].split(",", maxsplit=1)
+ else:
+ self.name = self.stop["name"]
+ self.suffix = None
+
+ if self.arrivalPlatform:
+ self.platform = self.arrivalPlatform
+ elif self.departurePlatform:
+ self.platform = departurePlatform
+ else:
+ self.platform = None
+
+ if self.plannedArrivalPlatform:
+ self.plannedPlatform = self.plannedArrivalPlatform
+ elif self.plannedDeparturePlatform:
+ self.plannedPlatform = self.plannedDeparturePlatform
+ else:
+ self.plannedPlatform = None
+
+ self.platform_changed = self.platform != self.plannedPlatform
+
+ self.is_requested_stop = False
+ self.ref_pos = None
+ self.is_future = None
+
+ def set_ref(self, ref_pos):
+ self.ref_pos = ref_pos
+ if ref_pos <= 0:
+ self.when = self.departure
+ self.plannedWhen = self.plannedDeparture
+ self.delay = self.departureDelay
+ else:
+ self.when = self.arrival
+ self.plannedWhen = self.plannedArrival
+ self.delay = self.arrivalDelay
+ if ref_pos == 0:
+ self.is_requested_stop = True
+
+ def set_relative(self, now_ts):
+ if self.arrival is not None:
+ self.is_future = self.arrival.timestamp() > now_ts
+ elif self.departure is not None:
+ self.is_future = self.departure.timestamp() > now_ts
+
+
class Departure:
def __init__(self, obj):
self.__dict__.update(obj)
@@ -166,8 +274,6 @@ class Departure:
self.stop_name = obj.get("stop", dict()).get("name", None)
self.station_name = obj.get("station", dict()).get("name", self.stop_name)
- self.quoted_stop_name = aiohttp.helpers.quote(self.stop_name)
-
try:
self.location = (
obj["stop"]["location"]["longitude"],
@@ -208,6 +314,14 @@ class Departure:
self.delay = self.delay // 60
self.delay = f"{self.delay:+.0f}"
+ self.quoted_line_name = aiohttp.helpers.quote(self.line.name)
+ self.quoted_stop_name = aiohttp.helpers.quote(self.stop_name)
+
+ def quoted_platform(self):
+ if self.platform:
+ return aiohttp.helpers.quote(self.platform)
+ return ""
+
def __repr__(self):
return f"Departure<line {self.line} to {self.direction}, scheduled departure at {self.iso8601}>"
@@ -256,6 +370,8 @@ class Line:
elif self.product == "bus":
if self.name.startswith("Bus "):
self.name = self.name[4:]
+ elif self.name.startswith("Bus"):
+ self.name = self.name[3:]
self.css_class = "bus"
def __repr__(self):
@@ -286,6 +402,39 @@ def meta_privacy(request):
)
+async def show_trip_info(request, trip_id=None):
+ if trip_id is None:
+ trip_id = request.match_info.get("tripid")
+ request_url = f"{db_rest_api}/trips/{trip_id}?lineName=0"
+ logging.debug(f"Requesting trip info from {request_url}")
+ async with aiohttp.ClientSession() as session:
+ async with session.get(request_url) as response:
+ tripinfo = await response.json()
+
+ tripinfo = Trip(tripinfo)
+
+ if request.query.get("highlight", None):
+ tripinfo.set_ref(request.query.get("highlight"))
+
+ if not tripinfo.platform and request.query.get("platform", None):
+ tripinfo.platform = request.query.get("platform")
+
+ now = datetime.now()
+ now_ts = now.timestamp()
+ tripinfo.set_relative(now_ts)
+
+ tripinfo_page = env.get_template("tripinfo_page.html")
+
+ return web.Response(
+ body=tripinfo_page.render(
+ title=tripinfo.line["name"] + " ➔ " + tripinfo.direction,
+ tripinfo=tripinfo,
+ version=nvm_version,
+ ),
+ headers=headers,
+ )
+
+
async def show_departure_board(request, eva=None):
if eva is None:
@@ -473,5 +622,6 @@ if __name__ == "__main__":
app.router.add_get(f"{args.prefix}meta/about", meta_about)
app.router.add_get(f"{args.prefix}meta/imprint", meta_imprint)
app.router.add_get(f"{args.prefix}meta/privacy", meta_privacy)
+ app.router.add_get(f"{args.prefix}trip/{{tripid}}", show_trip_info)
app.router.add_static(f"{args.prefix}static", "static")
web.run_app(app, host="localhost", port=args.port)
diff --git a/sass/app.scss b/sass/app.scss
index c5391a2..be78af3 100644
--- a/sass/app.scss
+++ b/sass/app.scss
@@ -67,6 +67,32 @@ input[type="text"] {
box-sizing: border-box;
}
+.smallbutton {
+ display: inline-block;
+ vertical-align: baseline;
+ border-radius: 4px;
+ border: 1px solid #2e6da4;
+ transition: background-color .3s;
+ color: #fff;
+ background-color: #337ab7;
+ cursor: pointer;
+ box-shadow: none;
+ padding: 0.9ex;
+ margin-right: 1em;
+
+ &:active, &:focus, &:hover {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74;
+ }
+
+ .material-icons {
+ display: block;
+ float: left;
+ margin-right: 0.5ex;
+ }
+}
+
.globalnote {
margin-top: 1em;
font-style: italic;
@@ -328,7 +354,7 @@ ul.departures {
.delay {
padding-right: 1ex;
- color: #ff0000;
+ color: $delay-color;
}
}
@@ -344,6 +370,67 @@ ul.departures {
}
}
+.tripinfo {
+ padding-left: 1em;
+ padding-right: 1em;
+ .direction {
+ text-align: center;
+ font-size: 120%;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-bottom: 0.1em dashed #cccccc;
+ }
+ .dataline {
+ margin-top: 0.6em;
+ font-size: 120%;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 1em;
+
+ > div {
+ width: 33%;
+ }
+
+ .arrival {
+ display: inline-block;
+ text-align: right;
+ }
+ .platform {
+ text-align: center;
+ }
+ .departure {
+ text-align: right;
+ }
+
+ .delay {
+ color: $delay-color;
+ }
+ }
+ .linklist {
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+ .material-icons {
+ font-size: 20px;
+ }
+ }
+ .route {
+ margin-top: 1em;
+ .past-stop {
+ list-style-type: disc;
+ }
+ .future-stop {
+ list-style-type: circle;
+ }
+ .this-stop {
+ font-weight: bold;
+ }
+ .cancelled-stop {
+ color: $cancelled-stop-color;
+ }
+ }
+}
+
.navbar-fixed {
position: relative;
z-index: 997;
@@ -398,7 +485,7 @@ ul.departures {
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
- url(/static/v5/font/MaterialIcons-Regular.ttf) format('truetype');
+ url(/static/v6/font/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
diff --git a/sass/dark.scss b/sass/dark.scss
index 1b90571..34b82f4 100644
--- a/sass/dark.scss
+++ b/sass/dark.scss
@@ -23,6 +23,10 @@ $suburban-bg: #2f6639;
$subway-bg: #2045b0;
$bus-bg: #7f3d7f;
+$cancelled-stop-color: #ff7777;
$cancelled-bg-color: #512f00;
+$delay-color: #ff7777;
+$undelay-color: #77ff77;
+
@import 'app.scss';
diff --git a/sass/light.scss b/sass/light.scss
index f080ddc..8061206 100644
--- a/sass/light.scss
+++ b/sass/light.scss
@@ -19,10 +19,14 @@ $longdistance-bg: #ffcccc;
$regional-bg: #eeeeee;
$tram-bg: #ffcccc;
$taxi-bg: #eeaaee;
-$suburban-bg: #aaffba;
+$suburban-bg: #9fd79f;
$subway-bg: #aac0ff;
$bus-bg: #eeaaee;
+$cancelled-stop-color: #cc0000;
$cancelled-bg-color: #ffe7d0;
+$delay-color: #ff0000;
+$undelay-color: #006600;
+
@import 'app.scss';
diff --git a/static/css/dark.min.css b/static/css/dark.min.css
index 7364729..61b17b3 100644
--- a/static/css/dark.min.css
+++ b/static/css/dark.min.css
@@ -1 +1 @@
-body{margin:0;color:#fff;background-color:#101010}html{font-family:Sans-Serif}.container{max-width:60em;margin-left:auto;margin-right:auto}.textcontent{margin-left:1ex;margin-right:1ex}a{color:#99f;text-decoration:none}input,select,button{display:block;width:100%;max-width:100%;min-height:1.8em;border-radius:4px;font-size:90%;color:#fff;background-color:#101010;border:1px solid #444;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);margin-left:auto;margin-right:auto;text-align:center;vertical-align:middle;padding-top:1ex;padding-bottom:1ex;margin-top:1ex;margin-bottom:1ex}button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none}button:active,button:focus,button:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="text"]{padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}.globalnote{margin-top:1em;font-style:italic;text-align:center}.geolocation{margin-bottom:1em}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.warning .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error strong{margin-right:.2em}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}ul.stops{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.stops>li{border-bottom:1px solid grey;background-color:#101010}ul.stops>li a{color:#fff;text-decoration:none;display:block;height:4em;width:100%;position:relative}ul.stops>li a .name{position:absolute;bottom:0;left:5px;width:70%;overflow:hidden;background-color:transparent;font-size:150%}ul.stops>li a .distance{position:absolute;top:1px;right:5px;width:30%;text-align:right;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .note{position:absolute;top:1px;left:0;width:70%;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .lines{position:absolute;bottom:0.1em;right:5px;background-color:transparent;font-weight:bold;font-size:120%}ul.stops>li a .lines span{padding-left:0.1em;padding-right:0.1em;margin-left:0.2em}ul.stops>li a .lines .longdistance{border:0.1em solid #7c1a1a}ul.stops>li a .lines .regional{background-color:#444}ul.stops>li a .lines .tram{background-color:#852121}ul.stops>li a .lines .taxi{background-color:#7f3d7f;font-weight:normal}ul.stops>li a .lines .suburban{background-color:#2f6639;border-radius:30px}ul.stops>li a .lines .subway{background-color:#2045b0}ul.stops>li a .lines .bus{background-color:#7f3d7f;border-radius:10px}ul.departures{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.departures>li{border-bottom:1px solid grey;background-color:#101010}ul.departures>li.cancelled{background-color:#512f00}ul.departures>li a{color:#fff;text-decoration:none;display:block;height:3em;width:100%;position:relative}ul.departures .line{position:absolute;bottom:2px;left:2px;max-width:6em;max-height:3ex;max-width:5em;overflow:hidden;font-size:120%;background-color:#444;font-weight:bold;padding-left:0.1em;padding-right:0.1em}ul.departures .longdistance{border:0.1em solid #7c1a1a}ul.departures .tram{background-color:#852121}ul.departures .suburban{background-color:#2f6639;border-radius:30px}ul.departures .subway{background-color:#2045b0}ul.departures .bus{background-color:#7f3d7f;border-radius:10px}ul.departures .dest{position:absolute;bottom:0;left:4em;width:70%;overflow:hidden;background-color:transparent;font-size:150%;color:#fff}ul.departures .destsuffix{position:absolute;top:1px;left:6.1em;width:70%;height:1.2em;overflow:hidden}ul.departures .time{position:absolute;right:5px;top:1px;background-color:transparent;padding-left:0.2em;color:#fff}ul.departures .time .delay{padding-right:1ex;color:#ff0000}ul.departures .platform{position:absolute;bottom:0;right:5px;overflow:hidden;background-color:transparent;font-size:140%;font-weight:bold;color:#fff}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed;width:100%;box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);overflow:hidden}.navbar-fixed nav a{color:#ffffff}.navbar-fixed nav .container{position:relative;height:100%}.navbar-fixed nav .main{position:absolute;display:inline-block;padding-left:0.5rem}.navbar-fixed nav ul{float:right;margin:0;padding-left:0;list-style-type:none}.navbar-fixed nav ul li{float:left;padding:0;list-style-type:none;padding-left:.8em;padding-right:.8em}.about{color:#bbb;margin-top:2em}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url(/static/v5/font/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}@media only screen and (max-width: 600px){ul.departures>li,ul.stops>li{font-size:85%}.navbar-fixed{height:50px}nav{height:50px;line-height:50px}nav .main{font-size:120%}nav .material-icons{height:50px;line-height:50px}}@media only screen and (min-width: 600px){.navbar-fixed{height:60px}nav{height:60px;line-height:60px}nav .main{font-size:140%}nav .material-icons{height:60px;line-height:60px}}
+body{margin:0;color:#fff;background-color:#101010}html{font-family:Sans-Serif}.container{max-width:60em;margin-left:auto;margin-right:auto}.textcontent{margin-left:1ex;margin-right:1ex}a{color:#99f;text-decoration:none}input,select,button{display:block;width:100%;max-width:100%;min-height:1.8em;border-radius:4px;font-size:90%;color:#fff;background-color:#101010;border:1px solid #444;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);margin-left:auto;margin-right:auto;text-align:center;vertical-align:middle;padding-top:1ex;padding-bottom:1ex;margin-top:1ex;margin-bottom:1ex}button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none}button:active,button:focus,button:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="text"]{padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}.smallbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #2e6da4;transition:background-color .3s;color:#fff;background-color:#337ab7;cursor:pointer;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}.smallbutton .material-icons{display:block;float:left;margin-right:0.5ex}.globalnote{margin-top:1em;font-style:italic;text-align:center}.geolocation{margin-bottom:1em}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.warning .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error strong{margin-right:.2em}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}ul.stops{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.stops>li{border-bottom:1px solid grey;background-color:#101010}ul.stops>li a{color:#fff;text-decoration:none;display:block;height:4em;width:100%;position:relative}ul.stops>li a .name{position:absolute;bottom:0;left:5px;width:70%;overflow:hidden;background-color:transparent;font-size:150%}ul.stops>li a .distance{position:absolute;top:1px;right:5px;width:30%;text-align:right;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .note{position:absolute;top:1px;left:0;width:70%;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .lines{position:absolute;bottom:0.1em;right:5px;background-color:transparent;font-weight:bold;font-size:120%}ul.stops>li a .lines span{padding-left:0.1em;padding-right:0.1em;margin-left:0.2em}ul.stops>li a .lines .longdistance{border:0.1em solid #7c1a1a}ul.stops>li a .lines .regional{background-color:#444}ul.stops>li a .lines .tram{background-color:#852121}ul.stops>li a .lines .taxi{background-color:#7f3d7f;font-weight:normal}ul.stops>li a .lines .suburban{background-color:#2f6639;border-radius:30px}ul.stops>li a .lines .subway{background-color:#2045b0}ul.stops>li a .lines .bus{background-color:#7f3d7f;border-radius:10px}ul.departures{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.departures>li{border-bottom:1px solid grey;background-color:#101010}ul.departures>li.cancelled{background-color:#512f00}ul.departures>li a{color:#fff;text-decoration:none;display:block;height:3em;width:100%;position:relative}ul.departures .line{position:absolute;bottom:2px;left:2px;max-width:6em;max-height:3ex;max-width:5em;overflow:hidden;font-size:120%;background-color:#444;font-weight:bold;padding-left:0.1em;padding-right:0.1em}ul.departures .longdistance{border:0.1em solid #7c1a1a}ul.departures .tram{background-color:#852121}ul.departures .suburban{background-color:#2f6639;border-radius:30px}ul.departures .subway{background-color:#2045b0}ul.departures .bus{background-color:#7f3d7f;border-radius:10px}ul.departures .dest{position:absolute;bottom:0;left:4em;width:70%;overflow:hidden;background-color:transparent;font-size:150%;color:#fff}ul.departures .destsuffix{position:absolute;top:1px;left:6.1em;width:70%;height:1.2em;overflow:hidden}ul.departures .time{position:absolute;right:5px;top:1px;background-color:transparent;padding-left:0.2em;color:#fff}ul.departures .time .delay{padding-right:1ex;color:#f77}ul.departures .platform{position:absolute;bottom:0;right:5px;overflow:hidden;background-color:transparent;font-size:140%;font-weight:bold;color:#fff}.tripinfo{padding-left:1em;padding-right:1em}.tripinfo .direction{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;border-bottom:0.1em dashed #cccccc}.tripinfo .dataline{margin-top:0.6em;font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:1em}.tripinfo .dataline>div{width:33%}.tripinfo .dataline .arrival{display:inline-block;text-align:right}.tripinfo .dataline .platform{text-align:center}.tripinfo .dataline .departure{text-align:right}.tripinfo .dataline .delay{color:#f77}.tripinfo .linklist{margin-top:0.5em;margin-bottom:1em}.tripinfo .linklist .material-icons{font-size:20px}.tripinfo .route{margin-top:1em}.tripinfo .route .past-stop{list-style-type:disc}.tripinfo .route .future-stop{list-style-type:circle}.tripinfo .route .this-stop{font-weight:bold}.tripinfo .route .cancelled-stop{color:#f77}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed;width:100%;box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);overflow:hidden}.navbar-fixed nav a{color:#ffffff}.navbar-fixed nav .container{position:relative;height:100%}.navbar-fixed nav .main{position:absolute;display:inline-block;padding-left:0.5rem}.navbar-fixed nav ul{float:right;margin:0;padding-left:0;list-style-type:none}.navbar-fixed nav ul li{float:left;padding:0;list-style-type:none;padding-left:.8em;padding-right:.8em}.about{color:#bbb;margin-top:2em}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url(/static/v6/font/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}@media only screen and (max-width: 600px){ul.departures>li,ul.stops>li{font-size:85%}.navbar-fixed{height:50px}nav{height:50px;line-height:50px}nav .main{font-size:120%}nav .material-icons{height:50px;line-height:50px}}@media only screen and (min-width: 600px){.navbar-fixed{height:60px}nav{height:60px;line-height:60px}nav .main{font-size:140%}nav .material-icons{height:60px;line-height:60px}}
diff --git a/static/css/light.min.css b/static/css/light.min.css
index d9a855e..827dfc3 100644
--- a/static/css/light.min.css
+++ b/static/css/light.min.css
@@ -1 +1 @@
-body{margin:0;color:#000;background-color:#fff}html{font-family:Sans-Serif}.container{max-width:60em;margin-left:auto;margin-right:auto}.textcontent{margin-left:1ex;margin-right:1ex}a{color:#009;text-decoration:none}input,select,button{display:block;width:100%;max-width:100%;min-height:1.8em;border-radius:4px;font-size:90%;color:#000;background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);margin-left:auto;margin-right:auto;text-align:center;vertical-align:middle;padding-top:1ex;padding-bottom:1ex;margin-top:1ex;margin-bottom:1ex}button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none}button:active,button:focus,button:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="text"]{padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}.globalnote{margin-top:1em;font-style:italic;text-align:center}.geolocation{margin-bottom:1em}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.warning .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error strong{margin-right:.2em}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}ul.stops{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.stops>li{border-bottom:1px solid grey;background-color:#fff}ul.stops>li a{color:#000;text-decoration:none;display:block;height:4em;width:100%;position:relative}ul.stops>li a .name{position:absolute;bottom:0;left:5px;width:70%;overflow:hidden;background-color:transparent;font-size:150%}ul.stops>li a .distance{position:absolute;top:1px;right:5px;width:30%;text-align:right;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .note{position:absolute;top:1px;left:0;width:70%;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .lines{position:absolute;bottom:0.1em;right:5px;background-color:transparent;font-weight:bold;font-size:120%}ul.stops>li a .lines span{padding-left:0.1em;padding-right:0.1em;margin-left:0.2em}ul.stops>li a .lines .longdistance{border:0.1em solid #fcc}ul.stops>li a .lines .regional{background-color:#eee}ul.stops>li a .lines .tram{background-color:#fcc}ul.stops>li a .lines .taxi{background-color:#eae;font-weight:normal}ul.stops>li a .lines .suburban{background-color:#aaffba;border-radius:30px}ul.stops>li a .lines .subway{background-color:#aac0ff}ul.stops>li a .lines .bus{background-color:#eae;border-radius:10px}ul.departures{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.departures>li{border-bottom:1px solid grey;background-color:#fff}ul.departures>li.cancelled{background-color:#ffe7d0}ul.departures>li a{color:#000;text-decoration:none;display:block;height:3em;width:100%;position:relative}ul.departures .line{position:absolute;bottom:2px;left:2px;max-width:6em;max-height:3ex;max-width:5em;overflow:hidden;font-size:120%;background-color:#eee;font-weight:bold;padding-left:0.1em;padding-right:0.1em}ul.departures .longdistance{border:0.1em solid #fcc}ul.departures .tram{background-color:#fcc}ul.departures .suburban{background-color:#aaffba;border-radius:30px}ul.departures .subway{background-color:#aac0ff}ul.departures .bus{background-color:#eae;border-radius:10px}ul.departures .dest{position:absolute;bottom:0;left:4em;width:70%;overflow:hidden;background-color:transparent;font-size:150%;color:#000}ul.departures .destsuffix{position:absolute;top:1px;left:6.1em;width:70%;height:1.2em;overflow:hidden}ul.departures .time{position:absolute;right:5px;top:1px;background-color:transparent;padding-left:0.2em;color:#000}ul.departures .time .delay{padding-right:1ex;color:#ff0000}ul.departures .platform{position:absolute;bottom:0;right:5px;overflow:hidden;background-color:transparent;font-size:140%;font-weight:bold;color:#000}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed;width:100%;box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);overflow:hidden}.navbar-fixed nav a{color:#ffffff}.navbar-fixed nav .container{position:relative;height:100%}.navbar-fixed nav .main{position:absolute;display:inline-block;padding-left:0.5rem}.navbar-fixed nav ul{float:right;margin:0;padding-left:0;list-style-type:none}.navbar-fixed nav ul li{float:left;padding:0;list-style-type:none;padding-left:.8em;padding-right:.8em}.about{color:#666;margin-top:2em}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url(/static/v5/font/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}@media only screen and (max-width: 600px){ul.departures>li,ul.stops>li{font-size:85%}.navbar-fixed{height:50px}nav{height:50px;line-height:50px}nav .main{font-size:120%}nav .material-icons{height:50px;line-height:50px}}@media only screen and (min-width: 600px){.navbar-fixed{height:60px}nav{height:60px;line-height:60px}nav .main{font-size:140%}nav .material-icons{height:60px;line-height:60px}}
+body{margin:0;color:#000;background-color:#fff}html{font-family:Sans-Serif}.container{max-width:60em;margin-left:auto;margin-right:auto}.textcontent{margin-left:1ex;margin-right:1ex}a{color:#009;text-decoration:none}input,select,button{display:block;width:100%;max-width:100%;min-height:1.8em;border-radius:4px;font-size:90%;color:#000;background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);margin-left:auto;margin-right:auto;text-align:center;vertical-align:middle;padding-top:1ex;padding-bottom:1ex;margin-top:1ex;margin-bottom:1ex}button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none}button:active,button:focus,button:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="text"]{padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}.smallbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #2e6da4;transition:background-color .3s;color:#fff;background-color:#337ab7;cursor:pointer;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}.smallbutton .material-icons{display:block;float:left;margin-right:0.5ex}.globalnote{margin-top:1em;font-style:italic;text-align:center}.geolocation{margin-bottom:1em}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.warning .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error strong{margin-right:.2em}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}ul.stops{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.stops>li{border-bottom:1px solid grey;background-color:#fff}ul.stops>li a{color:#000;text-decoration:none;display:block;height:4em;width:100%;position:relative}ul.stops>li a .name{position:absolute;bottom:0;left:5px;width:70%;overflow:hidden;background-color:transparent;font-size:150%}ul.stops>li a .distance{position:absolute;top:1px;right:5px;width:30%;text-align:right;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .note{position:absolute;top:1px;left:0;width:70%;height:1.2em;overflow:hidden;opacity:50%}ul.stops>li a .lines{position:absolute;bottom:0.1em;right:5px;background-color:transparent;font-weight:bold;font-size:120%}ul.stops>li a .lines span{padding-left:0.1em;padding-right:0.1em;margin-left:0.2em}ul.stops>li a .lines .longdistance{border:0.1em solid #fcc}ul.stops>li a .lines .regional{background-color:#eee}ul.stops>li a .lines .tram{background-color:#fcc}ul.stops>li a .lines .taxi{background-color:#eae;font-weight:normal}ul.stops>li a .lines .suburban{background-color:#9fd79f;border-radius:30px}ul.stops>li a .lines .subway{background-color:#aac0ff}ul.stops>li a .lines .bus{background-color:#eae;border-radius:10px}ul.departures{position:relative;width:100%;border-width:1px 2px;margin-bottom:5em;list-style-type:none;margin:0;padding:0}ul.departures>li{border-bottom:1px solid grey;background-color:#fff}ul.departures>li.cancelled{background-color:#ffe7d0}ul.departures>li a{color:#000;text-decoration:none;display:block;height:3em;width:100%;position:relative}ul.departures .line{position:absolute;bottom:2px;left:2px;max-width:6em;max-height:3ex;max-width:5em;overflow:hidden;font-size:120%;background-color:#eee;font-weight:bold;padding-left:0.1em;padding-right:0.1em}ul.departures .longdistance{border:0.1em solid #fcc}ul.departures .tram{background-color:#fcc}ul.departures .suburban{background-color:#9fd79f;border-radius:30px}ul.departures .subway{background-color:#aac0ff}ul.departures .bus{background-color:#eae;border-radius:10px}ul.departures .dest{position:absolute;bottom:0;left:4em;width:70%;overflow:hidden;background-color:transparent;font-size:150%;color:#000}ul.departures .destsuffix{position:absolute;top:1px;left:6.1em;width:70%;height:1.2em;overflow:hidden}ul.departures .time{position:absolute;right:5px;top:1px;background-color:transparent;padding-left:0.2em;color:#000}ul.departures .time .delay{padding-right:1ex;color:red}ul.departures .platform{position:absolute;bottom:0;right:5px;overflow:hidden;background-color:transparent;font-size:140%;font-weight:bold;color:#000}.tripinfo{padding-left:1em;padding-right:1em}.tripinfo .direction{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;border-bottom:0.1em dashed #cccccc}.tripinfo .dataline{margin-top:0.6em;font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:1em}.tripinfo .dataline>div{width:33%}.tripinfo .dataline .arrival{display:inline-block;text-align:right}.tripinfo .dataline .platform{text-align:center}.tripinfo .dataline .departure{text-align:right}.tripinfo .dataline .delay{color:red}.tripinfo .linklist{margin-top:0.5em;margin-bottom:1em}.tripinfo .linklist .material-icons{font-size:20px}.tripinfo .route{margin-top:1em}.tripinfo .route .past-stop{list-style-type:disc}.tripinfo .route .future-stop{list-style-type:circle}.tripinfo .route .this-stop{font-weight:bold}.tripinfo .route .cancelled-stop{color:#c00}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed;width:100%;box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);overflow:hidden}.navbar-fixed nav a{color:#ffffff}.navbar-fixed nav .container{position:relative;height:100%}.navbar-fixed nav .main{position:absolute;display:inline-block;padding-left:0.5rem}.navbar-fixed nav ul{float:right;margin:0;padding-left:0;list-style-type:none}.navbar-fixed nav ul li{float:left;padding:0;list-style-type:none;padding-left:.8em;padding-right:.8em}.about{color:#666;margin-top:2em}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url(/static/v6/font/MaterialIcons-Regular.ttf) format("truetype")}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr}@media only screen and (max-width: 600px){ul.departures>li,ul.stops>li{font-size:85%}.navbar-fixed{height:50px}nav{height:50px;line-height:50px}nav .main{font-size:120%}nav .material-icons{height:50px;line-height:50px}}@media only screen and (min-width: 600px){.navbar-fixed{height:60px}nav{height:60px;line-height:60px}nav .main{font-size:140%}nav .material-icons{height:60px;line-height:60px}}
diff --git a/static/v4 b/static/v6
index 945c9b4..945c9b4 120000
--- a/static/v4
+++ b/static/v6
diff --git a/templates/departure_list.html b/templates/departure_list.html
index 801e6d2..3df1e65 100644
--- a/templates/departure_list.html
+++ b/templates/departure_list.html
@@ -1,6 +1,6 @@
{% for departure in departures %}
<li class="{{ departure.classes }}" data-timestamp="{{ departure.sort_by }}">
- <a href="https://dbf.finalrewind.org/map/{{ departure.tripId }}/0?from={{ departure.quoted_stop_name }}">
+ <a href="/trip/{{ departure.tripId }}?line={{ departure.quoted_line_name }}&amp;highlight={{ departure.quoted_stop_name }}&amp;platform={{ departure.quoted_platform() }}">
<span class="line {{ departure.line.css_class }}">{{ departure.line.name }}</span>
{% if departure.suffix %}
<span class="destsuffix">{{ departure.suffix }}</span>
diff --git a/templates/departures_page.html b/templates/departures_page.html
index 605acff..eeceb76 100644
--- a/templates/departures_page.html
+++ b/templates/departures_page.html
@@ -3,7 +3,7 @@
<head>
<title>{{ title }}</title>
{% include 'header.html' %}
- <script src="/static/v5/js/reload.js"></script>
+ <script src="/static/v6/js/reload.js"></script>
</head>
<body>
diff --git a/templates/header.html b/templates/header.html
index 9f57312..14953fe 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -12,11 +12,11 @@
<link rel="apple-touch-icon" sizes="152x152" href="/static/icons/icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="/static/icons/icon-167x167.png">
-->
-<link href="/static/v5/css/light.min.css" id="theme" rel="stylesheet">
+<link href="/static/v6/css/light.min.css" id="theme" rel="stylesheet">
<script>
function addStyleSheet(name, id) {
- const path = "/static/v5/css/" + name + ".min.css";
+ const path = "/static/v6/css/" + name + ".min.css";
const old = document.getElementById(id);
if (old && (old.href != path)) {
old.href = path;
diff --git a/templates/landing_page.html b/templates/landing_page.html
index 045007a..063b49c 100644
--- a/templates/landing_page.html
+++ b/templates/landing_page.html
@@ -3,7 +3,7 @@
<head>
<title>{{ title }}</title>
{% include 'header.html' %}
- <script src="/static/v5/js/geolocation.js"></script>
+ <script src="/static/v6/js/geolocation.js"></script>
</head>
<body>
{% include 'navbar.html' %}
diff --git a/templates/tripinfo.html b/templates/tripinfo.html
new file mode 100644
index 0000000..bfe46d0
--- /dev/null
+++ b/templates/tripinfo.html
@@ -0,0 +1,72 @@
+<div class="dataline">
+
+<div><div class="arrival">
+{% if tripinfo.arrival %}
+An:
+{% if tripinfo.arrivalDelay %}
+<span class="delay">{{ tripinfo.arrival.strftime('%H:%M') }}</span>
+<br/>Plan: {{ tripinfo.plannedArrival.strftime('%H:%M') }}
+{% else %}
+{{ tripinfo.arrival.strftime('%H:%M') }}
+{% endif %}
+{% endif %}
+</div></div>
+
+<div><div class="platform">
+{% if tripinfo.platform %}
+Steig {{ tripinfo.platform }}
+{% else %}
+&nbsp;
+{% endif %}
+</div></div>
+
+<div><div class="departure">
+{% if tripinfo.arrival %}
+Ab:
+{% if tripinfo.departureDelay %}
+<span class="delay">{{ tripinfo.departure.strftime('%H:%M') }}</span>
+<br/>Plan: {{ tripinfo.plannedDeparture.strftime('%H:%M') }}
+{% else %}
+{{ tripinfo.departure.strftime('%H:%M') }}
+{% endif %}
+{% endif %}
+</div></div>
+
+</div>
+
+<div class="linklist">
+{% if tripinfo.quoted_where %}
+<a class="smallbutton" href="https://dbf.finalrewind.org/map/{{ tripinfo.id }}/0?from={{ tripinfo.quoted_where }}"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
+{% else %}
+<a class="smallbutton" href="https://dbf.finalrewind.org/map/{{ tripinfo.id }}/0"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
+{% endif %}
+<div>
+
+<div class="route">
+Fahrtverlauf:
+<ul>
+{% for stopover in tripinfo.stopovers %}
+ <li class="
+ {% if stopover.is_future %}
+ future-stop
+ {% else %}
+ past-stop
+ {% endif %}
+ {% if stopover.cancelled %}
+ cancelled-stop
+ {% endif %}
+ ">
+ {% if stopover.when %}
+ {{ stopover.when.strftime('%H:%M') }}
+ {% elif stopover.plannedWhen %}
+ {{ stopover.plannedWhen.strftime('%H:%M') }}
+ {% endif %}
+ {% if stopover.is_requested_stop %}
+ <span class="this-stop">{{ stopover.name }}</span>
+ {% else %}
+ {{ stopover.name }}
+ {% endif %}
+ </li>
+{% endfor %}
+</ul>
+</div>
diff --git a/templates/tripinfo_page.html b/templates/tripinfo_page.html
new file mode 100644
index 0000000..cd04367
--- /dev/null
+++ b/templates/tripinfo_page.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title>{{ title }}</title>
+ {% include 'header.html' %}
+ <!--<script src="/static/v6/js/reload.js"></script>-->
+</head>
+<body>
+
+{% include 'navbar.html' %}
+
+<div class="container">
+ <div class="content">
+ {% if warning %}
+ <div class="content">
+ <div class="warning">
+ <strong>{{ warning["lead"] }}</strong>
+ {{ warning["body"] }}
+ <div class="errcode">{{ warning["code"] }}</div>
+ </div>
+ </div>
+ {% endif %}
+ <div class="tripinfo" id="tripinfo">
+ {% include 'tripinfo.html' %}
+ </div>
+ </div>
+</div>
+
+{% include 'footer.html' %}
+
+</body>
+</html>