summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cpanfile5
-rw-r--r--cpanfile.snapshot337
-rw-r--r--lib/DBInfoscreen.pm60
-rw-r--r--lib/DBInfoscreen/Controller/Map.pm30
-rw-r--r--lib/DBInfoscreen/Controller/Stationboard.pm416
-rw-r--r--lib/DBInfoscreen/Controller/Wagenreihung.pm132
-rw-r--r--lib/DBInfoscreen/Helper/HAFAS.pm97
-rw-r--r--public/static/css/dark.min.css2
-rw-r--r--public/static/css/legacy-mobile.css (renamed from public/static/css/mobile.css)0
-rw-r--r--public/static/css/legacy.css (renamed from public/static/css/default.css)0
-rw-r--r--public/static/css/light.min.css2
-rw-r--r--public/static/css/material-icons.css8
-rw-r--r--public/static/js/collapse.js4
-rw-r--r--public/static/js/dbf.min.js2
-rw-r--r--public/static/js/geostop.js9
-rw-r--r--public/static/js/geostop.min.js2
-rw-r--r--public/static/js/map-refresh.js3
-rw-r--r--public/static/js/map-refresh.min.js2
l---------public/static/v98 (renamed from public/static/v88)0
l---------public/static/v99 (renamed from public/static/v89)0
-rw-r--r--sass/app.scss72
-rw-r--r--sass/dark.scss4
-rw-r--r--sass/light.scss4
-rw-r--r--templates/_map_infobox.html.ep36
-rw-r--r--templates/_train_details.html.ep264
-rw-r--r--templates/_wagon.html.ep28
-rw-r--r--templates/about.html.ep53
-rw-r--r--templates/app.html.ep5
-rw-r--r--templates/exception.html.ep2
-rw-r--r--templates/landingpage.html.ep6
-rw-r--r--templates/layouts/app.html.ep60
-rw-r--r--templates/layouts/legacy.html.ep6
-rw-r--r--templates/route_map.html.ep2
-rw-r--r--templates/wagen.html.ep6
-rw-r--r--templates/wagenreihung.html.ep52
35 files changed, 959 insertions, 752 deletions
diff --git a/cpanfile b/cpanfile
index 248b3eb..85c6742 100644
--- a/cpanfile
+++ b/cpanfile
@@ -10,8 +10,7 @@ requires 'List::UtilsBy';
requires 'LWP::UserAgent';
requires 'LWP::Protocol::https';
requires 'Mojolicious';
-requires 'Mojolicious::Plugin::I18N';
-requires 'Travel::Status::DE::DBWagenreihung', '0.06';
-requires 'Travel::Status::DE::HAFAS', '>= 5.03';
+requires 'Travel::Status::DE::DBWagenreihung', '== 0.14';
+requires 'Travel::Status::DE::HAFAS', '>= 5.06';
requires 'Travel::Status::DE::IRIS';
requires 'XML::LibXML';
diff --git a/cpanfile.snapshot b/cpanfile.snapshot
index 60d063b..3afd2e4 100644
--- a/cpanfile.snapshot
+++ b/cpanfile.snapshot
@@ -156,6 +156,37 @@ DISTRIBUTIONS
perl 5.008001
strict 0
warnings 0
+ CPAN-Meta-Requirements-2.143
+ pathname: R/RJ/RJBS/CPAN-Meta-Requirements-2.143.tar.gz
+ provides:
+ CPAN::Meta::Requirements 2.143
+ CPAN::Meta::Requirements::Range 2.143
+ requirements:
+ B 0
+ Carp 0
+ ExtUtils::MakeMaker 6.17
+ perl 5.010000
+ strict 0
+ version 0.88
+ warnings 0
+ CPAN-Requirements-Dynamic-0.001
+ pathname: L/LE/LEONT/CPAN-Requirements-Dynamic-0.001.tar.gz
+ provides:
+ CPAN::Requirements::Dynamic 0.001
+ requirements:
+ CPAN::Meta::Prereqs 0
+ CPAN::Meta::Requirements::Range 0
+ Carp 0
+ ExtUtils::Config 0
+ ExtUtils::HasCompiler 0
+ ExtUtils::MakeMaker 0
+ IPC::Cmd 0
+ Module::Metadata 0
+ Parse::CPAN::Meta 0
+ Perl::OSType 0
+ perl 5.006
+ strict 0
+ warnings 0
Cache-2.11
pathname: S/SH/SHLOMIF/Cache-2.11.tar.gz
provides:
@@ -354,15 +385,15 @@ DISTRIBUTIONS
parent 0
strict 0
warnings 0
- DateTime-Locale-1.40
- pathname: D/DR/DROLSKY/DateTime-Locale-1.40.tar.gz
+ DateTime-Locale-1.42
+ pathname: D/DR/DROLSKY/DateTime-Locale-1.42.tar.gz
provides:
- DateTime::Locale 1.40
- DateTime::Locale::Base 1.40
- DateTime::Locale::Catalog 1.40
- DateTime::Locale::Data 1.40
- DateTime::Locale::FromData 1.40
- DateTime::Locale::Util 1.40
+ DateTime::Locale 1.42
+ DateTime::Locale::Base 1.42
+ DateTime::Locale::Catalog 1.42
+ DateTime::Locale::Data 1.42
+ DateTime::Locale::FromData 1.42
+ DateTime::Locale::Util 1.42
requirements:
Carp 0
Dist::CheckConflicts 0.02
@@ -811,13 +842,16 @@ DISTRIBUTIONS
requirements:
ExtUtils::MakeMaker 6.17
perl 5.006001
- ExtUtils-Config-0.008
- pathname: L/LE/LEONT/ExtUtils-Config-0.008.tar.gz
+ ExtUtils-Config-0.009
+ pathname: L/LE/LEONT/ExtUtils-Config-0.009.tar.gz
provides:
- ExtUtils::Config 0.008
+ ExtUtils::Config 0.009
+ ExtUtils::Config::MakeMaker 0.009
requirements:
Data::Dumper 0
- ExtUtils::MakeMaker 6.30
+ ExtUtils::MakeMaker 0
+ ExtUtils::MakeMaker::Config 0
+ perl 5.006
strict 0
warnings 0
ExtUtils-Depends-0.8001
@@ -830,6 +864,23 @@ DISTRIBUTIONS
File::Spec 0
IO::File 0
perl 5.006
+ ExtUtils-HasCompiler-0.024
+ pathname: L/LE/LEONT/ExtUtils-HasCompiler-0.024.tar.gz
+ provides:
+ ExtUtils::HasCompiler 0.024
+ requirements:
+ Carp 0
+ DynaLoader 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ ExtUtils::Mksymlists 0
+ File::Basename 0
+ File::Spec::Functions 0
+ File::Temp 0
+ base 0
+ perl 5.006
+ strict 0
+ warnings 0
ExtUtils-Helpers-0.026
pathname: L/LE/LEONT/ExtUtils-Helpers-0.026.tar.gz
provides:
@@ -848,10 +899,10 @@ DISTRIBUTIONS
perl 5.006
strict 0
warnings 0
- ExtUtils-InstallPaths-0.012
- pathname: L/LE/LEONT/ExtUtils-InstallPaths-0.012.tar.gz
+ ExtUtils-InstallPaths-0.013
+ pathname: L/LE/LEONT/ExtUtils-InstallPaths-0.013.tar.gz
provides:
- ExtUtils::InstallPaths 0.012
+ ExtUtils::InstallPaths 0.013
requirements:
Carp 0
ExtUtils::Config 0.002
@@ -998,16 +1049,16 @@ DISTRIBUTIONS
parent 0
perl 5.008001
strictures 2.000000
- HTML-Parser-3.81
- pathname: O/OA/OALDERS/HTML-Parser-3.81.tar.gz
+ HTML-Parser-3.82
+ pathname: O/OA/OALDERS/HTML-Parser-3.82.tar.gz
provides:
- HTML::Entities 3.81
- HTML::Filter 3.81
- HTML::HeadParser 3.81
- HTML::LinkExtor 3.81
- HTML::Parser 3.81
- HTML::PullParser 3.81
- HTML::TokeParser 3.81
+ HTML::Entities 3.82
+ HTML::Filter 3.82
+ HTML::HeadParser 3.82
+ HTML::LinkExtor 3.82
+ HTML::Parser 3.82
+ HTML::PullParser 3.82
+ HTML::TokeParser 3.82
requirements:
Carp 0
Exporter 0
@@ -1019,12 +1070,13 @@ DISTRIBUTIONS
URI::URL 0
XSLoader 0
strict 0
- HTML-Tagset-3.20
- pathname: P/PE/PETDANCE/HTML-Tagset-3.20.tar.gz
+ HTML-Tagset-3.24
+ pathname: P/PE/PETDANCE/HTML-Tagset-3.24.tar.gz
provides:
- HTML::Tagset 3.20
+ HTML::Tagset 3.24
requirements:
- ExtUtils::MakeMaker 0
+ ExtUtils::MakeMaker 6.46
+ perl 5.010001
HTTP-Cookies-6.11
pathname: O/OA/OALDERS/HTTP-Cookies-6.11.tar.gz
provides:
@@ -1177,11 +1229,11 @@ DISTRIBUTIONS
Scalar::Util 0
perl 5.006002
strict 0
- LWP-Protocol-https-6.13
- pathname: O/OA/OALDERS/LWP-Protocol-https-6.13.tar.gz
+ LWP-Protocol-https-6.14
+ pathname: O/OA/OALDERS/LWP-Protocol-https-6.14.tar.gz
provides:
- LWP::Protocol::https 6.13
- LWP::Protocol::https::Socket 6.13
+ LWP::Protocol::https 6.14
+ LWP::Protocol::https::Socket 6.14
requirements:
ExtUtils::MakeMaker 0
IO::Socket::SSL 1.970
@@ -1285,12 +1337,13 @@ DISTRIBUTIONS
Text::ParseWords 0
perl 5.006001
version 0.87
- Module-Build-Tiny-0.047
- pathname: L/LE/LEONT/Module-Build-Tiny-0.047.tar.gz
+ Module-Build-Tiny-0.048
+ pathname: L/LE/LEONT/Module-Build-Tiny-0.048.tar.gz
provides:
- Module::Build::Tiny 0.047
+ Module::Build::Tiny 0.048
requirements:
CPAN::Meta 0
+ CPAN::Requirements::Dynamic 0
DynaLoader 0
Exporter 5.57
ExtUtils::CBuilder 0
@@ -1331,8 +1384,8 @@ DISTRIBUTIONS
perl 5.006
strict 0
warnings 0
- Mojolicious-9.35
- pathname: S/SR/SRI/Mojolicious-9.35.tar.gz
+ Mojolicious-9.36
+ pathname: S/SR/SRI/Mojolicious-9.36.tar.gz
provides:
Mojo undef
Mojo::Asset undef
@@ -1400,7 +1453,7 @@ DISTRIBUTIONS
Mojo::UserAgent::Transactor undef
Mojo::Util undef
Mojo::WebSocket undef
- Mojolicious 9.35
+ Mojolicious 9.36
Mojolicious::Command undef
Mojolicious::Command::Author::cpanify undef
Mojolicious::Command::Author::generate undef
@@ -1449,20 +1502,10 @@ DISTRIBUTIONS
IO::Socket::IP 0.37
Sub::Util 1.41
perl 5.016
- Mojolicious-Plugin-I18N-1.6
- pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.6.tar.gz
- provides:
- Mojolicious::Plugin::I18N 1.6
- requirements:
- I18N::LangTags 0.35
- Module::Build 0.42
- Mojolicious 5
- Test::More 0
- perl 5.010001
- Mozilla-CA-20231213
- pathname: L/LW/LWP/Mozilla-CA-20231213.tar.gz
+ Mozilla-CA-20240313
+ pathname: L/LW/LWP/Mozilla-CA-20240313.tar.gz
provides:
- Mozilla::CA 20231213
+ Mozilla::CA 20240313
requirements:
ExtUtils::MakeMaker 0
Net-HTTP-6.23
@@ -1832,12 +1875,13 @@ DISTRIBUTIONS
TimeDate 1.21
requirements:
ExtUtils::MakeMaker 0
- Travel-Status-DE-DBWagenreihung-0.11
- pathname: D/DE/DERF/Travel-Status-DE-DBWagenreihung-0.11.tar.gz
+ Travel-Status-DE-DBWagenreihung-0.14
+ pathname: D/DE/DERF/Travel-Status-DE-DBWagenreihung-0.14.tar.gz
provides:
- Travel::Status::DE::DBWagenreihung 0.11
- Travel::Status::DE::DBWagenreihung::Section 0.11
- Travel::Status::DE::DBWagenreihung::Wagon 0.11
+ Travel::Status::DE::DBWagenreihung 0.14
+ Travel::Status::DE::DBWagenreihung::Group 0.14
+ Travel::Status::DE::DBWagenreihung::Section 0.14
+ Travel::Status::DE::DBWagenreihung::Wagon 0.14
requirements:
Carp 0
Class::Accessor 0
@@ -1851,17 +1895,18 @@ DISTRIBUTIONS
Test::Pod 0
Travel::Status::DE::IRIS 1.2
perl v5.20.0
- Travel-Status-DE-DeutscheBahn-5.05
- pathname: D/DE/DERF/Travel-Status-DE-DeutscheBahn-5.05.tar.gz
- provides:
- Travel::Status::DE::DeutscheBahn 5.05
- Travel::Status::DE::HAFAS 5.05
- Travel::Status::DE::HAFAS::Journey 5.05
- Travel::Status::DE::HAFAS::Location 5.05
- Travel::Status::DE::HAFAS::Message 5.05
- Travel::Status::DE::HAFAS::Polyline 5.05
- Travel::Status::DE::HAFAS::Stop 5.05
- Travel::Status::DE::HAFAS::StopFinder 5.05
+ Travel-Status-DE-DeutscheBahn-6.03
+ pathname: D/DE/DERF/Travel-Status-DE-DeutscheBahn-6.03.tar.gz
+ provides:
+ Travel::Status::DE::DeutscheBahn 6.03
+ Travel::Status::DE::HAFAS 6.03
+ Travel::Status::DE::HAFAS::Journey 6.03
+ Travel::Status::DE::HAFAS::Location 6.03
+ Travel::Status::DE::HAFAS::Message 6.03
+ Travel::Status::DE::HAFAS::Polyline 6.03
+ Travel::Status::DE::HAFAS::Product 6.03
+ Travel::Status::DE::HAFAS::Stop 6.03
+ Travel::Status::DE::HAFAS::StopFinder 6.03
requirements:
Carp 0
Class::Accessor 0.16
@@ -1933,56 +1978,56 @@ DISTRIBUTIONS
requirements:
ExtUtils::MakeMaker 0
common::sense 0
- URI-5.27
- pathname: O/OA/OALDERS/URI-5.27.tar.gz
- provides:
- URI 5.27
- URI::Escape 5.27
- URI::Heuristic 5.27
- URI::IRI 5.27
- URI::QueryParam 5.27
- URI::Split 5.27
- URI::URL 5.27
- URI::WithBase 5.27
- URI::data 5.27
- URI::file 5.27
- URI::file::Base 5.27
- URI::file::FAT 5.27
- URI::file::Mac 5.27
- URI::file::OS2 5.27
- URI::file::QNX 5.27
- URI::file::Unix 5.27
- URI::file::Win32 5.27
- URI::ftp 5.27
- URI::geo 5.27
- URI::gopher 5.27
- URI::http 5.27
- URI::https 5.27
- URI::icap 5.27
- URI::icaps 5.27
- URI::ldap 5.27
- URI::ldapi 5.27
- URI::ldaps 5.27
- URI::mailto 5.27
- URI::mms 5.27
- URI::news 5.27
- URI::nntp 5.27
- URI::nntps 5.27
- URI::pop 5.27
- URI::rlogin 5.27
- URI::rsync 5.27
- URI::rtsp 5.27
- URI::rtspu 5.27
- URI::sftp 5.27
- URI::sip 5.27
- URI::sips 5.27
- URI::snews 5.27
- URI::ssh 5.27
- URI::telnet 5.27
- URI::tn3270 5.27
- URI::urn 5.27
- URI::urn::isbn 5.27
- URI::urn::oid 5.27
+ URI-5.28
+ pathname: O/OA/OALDERS/URI-5.28.tar.gz
+ provides:
+ URI 5.28
+ URI::Escape 5.28
+ URI::Heuristic 5.28
+ URI::IRI 5.28
+ URI::QueryParam 5.28
+ URI::Split 5.28
+ URI::URL 5.28
+ URI::WithBase 5.28
+ URI::data 5.28
+ URI::file 5.28
+ URI::file::Base 5.28
+ URI::file::FAT 5.28
+ URI::file::Mac 5.28
+ URI::file::OS2 5.28
+ URI::file::QNX 5.28
+ URI::file::Unix 5.28
+ URI::file::Win32 5.28
+ URI::ftp 5.28
+ URI::geo 5.28
+ URI::gopher 5.28
+ URI::http 5.28
+ URI::https 5.28
+ URI::icap 5.28
+ URI::icaps 5.28
+ URI::ldap 5.28
+ URI::ldapi 5.28
+ URI::ldaps 5.28
+ URI::mailto 5.28
+ URI::mms 5.28
+ URI::news 5.28
+ URI::nntp 5.28
+ URI::nntps 5.28
+ URI::pop 5.28
+ URI::rlogin 5.28
+ URI::rsync 5.28
+ URI::rtsp 5.28
+ URI::rtspu 5.28
+ URI::sftp 5.28
+ URI::sip 5.28
+ URI::sips 5.28
+ URI::snews 5.28
+ URI::ssh 5.28
+ URI::telnet 5.28
+ URI::tn3270 5.28
+ URI::urn 5.28
+ URI::urn::isbn 5.28
+ URI::urn::oid 5.28
requirements:
Carp 0
Cwd 0
@@ -2001,10 +2046,10 @@ DISTRIBUTIONS
strict 0
utf8 0
warnings 0
- Variable-Magic-0.63
- pathname: V/VP/VPIT/Variable-Magic-0.63.tar.gz
+ Variable-Magic-0.64
+ pathname: V/VP/VPIT/Variable-Magic-0.64.tar.gz
provides:
- Variable::Magic 0.63
+ Variable::Magic 0.64
requirements:
Carp 0
Config 0
@@ -2180,32 +2225,32 @@ DISTRIBUTIONS
XSLoader 0
lib 0
perl 5.008001
- libwww-perl-6.76
- pathname: O/OA/OALDERS/libwww-perl-6.76.tar.gz
- provides:
- LWP 6.76
- LWP::Authen::Basic 6.76
- LWP::Authen::Digest 6.76
- LWP::Authen::Ntlm 6.76
- LWP::ConnCache 6.76
- LWP::Debug 6.76
- LWP::Debug::TraceHTTP 6.76
- LWP::DebugFile 6.76
- LWP::MemberMixin 6.76
- LWP::Protocol 6.76
- LWP::Protocol::cpan 6.76
- LWP::Protocol::data 6.76
- LWP::Protocol::file 6.76
- LWP::Protocol::ftp 6.76
- LWP::Protocol::gopher 6.76
- LWP::Protocol::http 6.76
- LWP::Protocol::loopback 6.76
- LWP::Protocol::mailto 6.76
- LWP::Protocol::nntp 6.76
- LWP::Protocol::nogo 6.76
- LWP::RobotUA 6.76
- LWP::Simple 6.76
- LWP::UserAgent 6.76
+ libwww-perl-6.77
+ pathname: O/OA/OALDERS/libwww-perl-6.77.tar.gz
+ provides:
+ LWP 6.77
+ LWP::Authen::Basic 6.77
+ LWP::Authen::Digest 6.77
+ LWP::Authen::Ntlm 6.77
+ LWP::ConnCache 6.77
+ LWP::Debug 6.77
+ LWP::Debug::TraceHTTP 6.77
+ LWP::DebugFile 6.77
+ LWP::MemberMixin 6.77
+ LWP::Protocol 6.77
+ LWP::Protocol::cpan 6.77
+ LWP::Protocol::data 6.77
+ LWP::Protocol::file 6.77
+ LWP::Protocol::ftp 6.77
+ LWP::Protocol::gopher 6.77
+ LWP::Protocol::http 6.77
+ LWP::Protocol::loopback 6.77
+ LWP::Protocol::mailto 6.77
+ LWP::Protocol::nntp 6.77
+ LWP::Protocol::nogo 6.77
+ LWP::RobotUA 6.77
+ LWP::Simple 6.77
+ LWP::UserAgent 6.77
requirements:
Digest::MD5 0
Encode 2.12
diff --git a/lib/DBInfoscreen.pm b/lib/DBInfoscreen.pm
index 9dcc2d0..c784e96 100644
--- a/lib/DBInfoscreen.pm
+++ b/lib/DBInfoscreen.pm
@@ -38,12 +38,6 @@ sub startup {
chomp $self->config->{version};
$self->defaults( version => $self->config->{version} // 'UNKNOWN' );
- $self->plugin(
- I18N => {
- default => 'de',
- },
- );
-
# Generally, the reverse proxy handles compression.
# Also, Mojolicious compression breaks legacy callback-based JSON endpoints
# for some clients.
@@ -62,18 +56,6 @@ sub startup {
if ( $cookie->name eq 'theme' ) {
$self->session( theme => $cookie->value );
}
- elsif ( $cookie->name eq 'lang' ) {
- my $l = $cookie->value;
- if ( $l eq 'de' or $l eq 'en' ) {
- $self->languages($l);
- }
- }
- }
-
- if ( my $l = $self->param('lang') ) {
- if ( $l eq 'de' or $l eq 'en' ) {
- $self->languages($l);
- }
}
}
);
@@ -103,36 +85,6 @@ sub startup {
);
$self->attr(
- ice_type_map => sub {
- if ( -r 'share/zugbildungsplan.json' ) {
- my $ice_type_map = JSON->new->utf8->decode(
- scalar read_file('share/zugbildungsplan.json') );
- my $ret = {};
- while ( my ( $k, $v ) = each %{ $ice_type_map->{train} } ) {
- if ( $v->{type} ) {
- $ret->{$k} = [
- $v->{type}, $v->{shortType},
- exists $v->{wagons} ? 1 : 0
- ];
- }
- }
- return $ret;
- }
- return {};
- }
- );
-
- $self->attr(
- train_details_db => sub {
- if ( -r 'share/zugbildungsplan.json' ) {
- return JSON->new->utf8->decode(
- scalar read_file('share/zugbildungsplan.json') )->{train};
- }
- return {};
- }
- );
-
- $self->attr(
dbdb_wagon => sub {
return JSON->new->utf8->decode(
scalar read_file('share/dbdb_wagen.json') );
@@ -323,20 +275,22 @@ sub startup {
$r->get('/dyn/:av/autocomplete.js')->to('stationboard#autocomplete');
$r->get('/_wr/:train/:departure')->to('wagenreihung#wagenreihung');
- $r->get('/wr/:train')->to('wagenreihung#zugbildung_db');
$r->get('/w/*wagon')->to('wagenreihung#wagen');
$r->get('/_ajax_mapinfo/:tripid/:lineno')->to('map#ajax_route');
$r->get('/map/:tripid/:lineno')->to('map#route');
- $r->get( '/z/:train/*station' => 'train_at_station' )
- ->to('stationboard#station_train_details');
- $r->get( '/z/:train' => 'train' )->to('stationboard#train_details');
+ $r->get( '/z/:train/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#station_train_details', format => undef )
+ ->name('train_at_station');
+ $r->get( '/z/:train' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#train_details', format => undef )->name('train');
$self->defaults( layout => 'app' );
$r->get('/')->to('stationboard#handle_request');
$r->get('/multi/*station')->to('stationboard#handle_request');
- $r->get('/*station')->to('stationboard#handle_request');
+ $r->get( '/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#handle_request', format => undef );
$self->types->type( json => 'application/json; charset=utf-8' );
diff --git a/lib/DBInfoscreen/Controller/Map.pm b/lib/DBInfoscreen/Controller/Map.pm
index e552a18..bced612 100644
--- a/lib/DBInfoscreen/Controller/Map.pm
+++ b/lib/DBInfoscreen/Controller/Map.pm
@@ -314,13 +314,26 @@ sub route {
my ($self) = @_;
my $trip_id = $self->stash('tripid');
my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
my $from_name = $self->param('from');
my $to_name = $self->param('to');
$self->render_later;
- $self->hafas->get_polyline_p( $trip_id, $line_no )->then(
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
sub {
my ($journey) = @_;
@@ -458,12 +471,25 @@ sub ajax_route {
my ($self) = @_;
my $trip_id = $self->stash('tripid');
my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
delete $self->stash->{layout};
$self->render_later;
- $self->hafas->get_polyline_p( $trip_id, $line_no )->then(
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
sub {
my ($journey) = @_;
diff --git a/lib/DBInfoscreen/Controller/Stationboard.pm b/lib/DBInfoscreen/Controller/Stationboard.pm
index e2e5bb5..b64c661 100644
--- a/lib/DBInfoscreen/Controller/Stationboard.pm
+++ b/lib/DBInfoscreen/Controller/Stationboard.pm
@@ -29,6 +29,20 @@ my %default = (
admode => 'deparr',
);
+sub class_to_product {
+ my ( $self, $hafas ) = @_;
+
+ my $bits = $hafas->get_active_service->{productbits};
+ my $ret;
+
+ for my $i ( 0 .. $#{$bits} ) {
+ $ret->{ 2**$i }
+ = ref( $bits->[$i] ) eq 'ARRAY' ? $bits->[$i][0] : $bits->[$i];
+ }
+
+ return $ret;
+}
+
sub handle_no_results {
my ( $self, $station, $data, $hafas ) = @_;
@@ -455,7 +469,12 @@ sub handle_request {
# (or used by) marudor.de, it was renamed to 'json'. Many clients won't
# notice this for year to come, so we make sure mode=marudor still works as
# intended.
- if ( $template eq 'marudor' ) {
+ if (
+ $template eq 'marudor'
+ or ( $self->req->headers->accept
+ and $self->req->headers->accept eq 'application/json' )
+ )
+ {
$template = 'json';
}
@@ -490,6 +509,7 @@ sub handle_request {
my ($status) = @_;
my $data = {
results => [ $status->results ],
+ hafas => $hafas ? $status : undef,
station_ds100 =>
( $status->station ? $status->station->{ds100} : undef ),
station_eva => (
@@ -503,12 +523,6 @@ sub handle_request {
( $status->station ? $status->station->{name} : $station ),
};
- if ( $status->station and $status->station->{names} ) {
- $data->{station_name}
- = List::Util::reduce { length($a) < length($b) ? $a : $b }
- @{ $status->station->{names} };
- }
-
if ( not @{ $data->{results} } and $template eq 'json' ) {
$self->handle_no_results_json( $station, $data, $api_version );
return;
@@ -735,8 +749,55 @@ sub render_train {
from_json => $wr_json );
$departure->{wr} = $wr;
$departure->{wr_text} = join( q{ • },
- map { $_->{short} }
- grep { $_->{short} } $wr->train_descriptions );
+ map { $_->desc_short }
+ grep { $_->desc_short } $wr->groups );
+ my $first = 0;
+ for my $group ( $wr->groups ) {
+ my $had_entry = 0;
+ for my $wagon ( $group->wagons ) {
+ if (
+ not( $wagon->is_locomotive
+ or $wagon->is_powercar )
+ )
+ {
+ my $class;
+ if ($first) {
+ push(
+ @{ $departure->{wr_preview} },
+ [ '•', 'meta' ]
+ );
+ $first = 0;
+ }
+ my $entry;
+ if ( $wagon->is_closed ) {
+ $entry = 'X';
+ $class = 'closed';
+ }
+ else {
+ $entry = $wagon->number
+ || (
+ $wagon->type =~ m{AB} ? '½'
+ : $wagon->type =~ m{A} ? '1.'
+ : $wagon->type =~ m{B} ? '2.'
+ : $wagon->type
+ );
+ }
+ if (
+ $group->train_no ne $departure->{train_no} )
+ {
+ $class = 'otherno';
+ }
+ push(
+ @{ $departure->{wr_preview} },
+ [ $entry, $class ]
+ );
+ $had_entry = 1;
+ }
+ }
+ if ($had_entry) {
+ $first = 1;
+ }
+ }
};
$departure->{wr_text} ||= 'Wagen';
return;
@@ -812,10 +873,11 @@ sub render_train {
}
if ($direction) {
- $departure->{direction} = $direction;
+ $departure->{wr_direction} = $direction;
+ $departure->{wr_direction_num} = $direction eq 'l' ? 0 : 100;
}
elsif ( $platform_info->{direction} ) {
- $departure->{direction} = 'a' . $platform_info->{direction};
+ $departure->{wr_direction} = 'a' . $platform_info->{direction};
}
return;
@@ -833,17 +895,17 @@ sub render_train {
my %opt = ( train => $result );
- if ( $self->languages =~ m{^en} ) {
- $opt{language} = 'en';
- }
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
$self->hafas->get_route_p(%opt)->then(
sub {
my ( $route, $journey ) = @_;
- $departure->{trip_id} = $journey->id;
- $departure->{operator} = $journey->operator;
- $departure->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
+ $departure->{trip_id} = $journey->id;
+ $departure->{operators} = [ $journey->operators ];
+ $departure->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
# Use HAFAS route as source of truth; ignore IRIS data
$departure->{route_pre_diff} = [];
@@ -859,6 +921,16 @@ sub render_train {
= [ $load->{FIRST}, $load->{SECOND} ];
}
}
+ $departure->{tz_offset} = $route->[$i]{tz_offset};
+ $departure->{local_dt_da} = $route->[$i]{local_dt_da};
+ $departure->{local_sched_arr}
+ = $route->[$i]{local_sched_arr};
+ $departure->{local_sched_dep}
+ = $route->[$i]{local_sched_dep};
+ $departure->{is_annotated} = $route->[$i]{is_annotated};
+ $departure->{prod_name} = $route->[$i]{prod_name};
+ $departure->{direction} = $route->[$i]{direction};
+ $departure->{operator} = $route->[$i]{operator};
last;
}
}
@@ -915,58 +987,39 @@ sub render_train {
}
)->wait;
- $departure->{composition}
- = $self->app->train_details_db->{ $departure->{train_no} };
- if ( not $departure->{arrival}
- and $departure->{composition}{prepTime}
- and $departure->{composition}{prepAt} eq $station_name )
- {
- $departure->{prep_time} = $departure->{composition}{prepTime};
- $departure->{arrival_hidden} = 1;
- }
- if ( $self->param('detailed') ) {
- my @cycle_from;
- my @cycle_to;
- for my $pred ( @{ $departure->{composition}{predecessors} // [] } ) {
- push( @cycle_from, $pred->[1] );
- }
- for my $succ ( @{ $departure->{composition}{successors} // [] } ) {
- push( @cycle_to, $succ->[1] );
- }
- $departure->{cycle_from}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] } @cycle_from ];
- $departure->{cycle_to}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] } @cycle_to ];
- }
-
# Defer rendering until all requests have completed
Mojo::Promise->all(@requests)->then(
sub {
- $self->render(
- $template // '_train_details',
- description => sprintf(
- '%s %s%s%s nach %s',
- $departure->{train_type},
- $departure->{train_line} // $departure->{train_no},
- $departure->{origin} ? ' von ' : q{},
- $departure->{origin} // q{},
- $departure->{destination} // 'unbekannt'
- ),
- departure => $departure,
- linetype => $linetype,
- icetype => $self->app->ice_type_map->{ $departure->{train_no} },
- details => $self->param('detailed')
- ? $departure->{composition} // {}
- : {},
- dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
- station_name => $station_name,
- nav_link =>
- $self->url_for( 'station', station => $station_name )->query(
- {
- detailed => $self->param('detailed'),
- hafas => $self->param('hafas')
- }
- ),
+ $self->respond_to(
+ json => {
+ json => {
+ departure => $departure,
+ station_name => $station_name,
+ },
+ },
+ any => {
+ template => $template // '_train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $departure->{train_type},
+ $departure->{train_line} // $departure->{train_no},
+ $departure->{origin} ? ' von ' : q{},
+ $departure->{origin} // q{},
+ $departure->{destination} // 'unbekannt'
+ ),
+ departure => $departure,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ station_name => $station_name,
+ nav_link =>
+ $self->url_for( 'station', station => $station_name )
+ ->query(
+ {
+ detailed => $self->param('detailed'),
+ hafas => $self->param('hafas')
+ }
+ ),
+ },
);
}
)->wait;
@@ -982,6 +1035,10 @@ sub station_train_details {
delete $self->stash->{layout};
}
+ if ( $station =~ s{ [.] json $ }{}x ) {
+ $self->stash( format => 'json' );
+ }
+
my %opt = (
cache_iris_main => $self->app->cache_iris_main,
cache_iris_rt => $self->app->cache_iris_rt,
@@ -1057,6 +1114,8 @@ sub station_train_details {
arrival_is_cancelled => $result->arrival_is_cancelled,
moreinfo => $moreinfo,
delay => $result->delay,
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
route_pre => [ $result->route_pre ],
route_post => [ $result->route_post ],
replaced_by => [
@@ -1087,11 +1146,20 @@ sub station_train_details {
)->catch(
sub {
my ($errstr) = @_;
- $self->render(
- 'landingpage',
- error =>
- "Keine Abfahrt von $train_no in $station gefunden: $errstr",
- status => 404,
+ $self->respond_to(
+ json => {
+ json => {
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ },
+ status => 404,
+ },
+ any => {
+ template => 'landingpage',
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ status => 404,
+ },
);
return;
}
@@ -1101,7 +1169,8 @@ sub station_train_details {
# /z/:train
sub train_details {
my ($self) = @_;
- my $train = $self->stash('train');
+ my $train = $self->stash('train');
+ my $hafas = $self->param('hafas');
# TODO error handling
@@ -1109,8 +1178,6 @@ sub train_details {
delete $self->stash->{layout};
}
- my $api_version = $Travel::Status::DE::IRIS::VERSION;
-
$self->stash( departures => [] );
$self->stash( title => 'DBF' );
@@ -1139,10 +1206,18 @@ sub train_details {
$opt{train_no} = $train_no;
}
- if ( $self->languages =~ m{^en} ) {
- $opt{language} = 'en';
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $opt{service} = $hafas;
}
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
+
if ( my $date = $self->param('date') ) {
if ( $date
=~ m{ ^ (?<day> \d{1,2} ) [.] (?<month> \d{1,2} ) [.] (?<year> \d{4})? $ }x
@@ -1166,43 +1241,52 @@ sub train_details {
$self->hafas->get_route_p(%opt)->then(
sub {
- my ( $route, $journey ) = @_;
+ my ( $route, $journey, $hafas_obj ) = @_;
$res->{trip_id} = $journey->id;
$res->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
- if ( not $res->{train_type} ) {
- my $train_type = $res->{train_type} = $journey->type // q{};
- my $train_no = $res->{train_no} = $journey->number // q{};
- $res->{train_line} = $journey->line_no // q{};
- $self->stash( title => "${train_type} ${train_no}" );
+ my $product = $journey->product;
+
+ if ( my $req_name = $self->param('highlight') ) {
+ if ( my $p = $journey->product_at($req_name) ) {
+ $product = $p;
+ }
}
- if ( not defined $journey->class ) {
+ my $train_type = $res->{train_type} = $product->type // q{};
+ my $train_no = $res->{train_no} = $product->number // q{};
+ $res->{train_line} = $product->line_no // q{};
+ $self->stash( title => $train_type . ' '
+ . ( $train_no || $res->{train_line} ) );
+
+ if ( not defined $product->class ) {
$linetype = 'ext';
}
- elsif ( $journey->class <= 2 ) {
- $linetype = 'fern';
- }
- elsif ( $journey->class <= 8 ) {
- $linetype = 'bahn';
- }
- elsif ( $journey->class <= 16 ) {
- $linetype = 'sbahn';
- }
- elsif ( $journey->class == 32 ) {
- $linetype = 'bus';
- }
- elsif ( $journey->class == 128 ) {
- $linetype = 'ubahn';
- }
- elsif ( $journey->class == 256 ) {
- $linetype = 'tram';
+ else {
+ my $prod
+ = $self->class_to_product($hafas_obj)->{ $product->class }
+ // q{};
+ if ( $prod eq 'ice' or $prod eq 'ic_ec' ) {
+ $linetype = 'fern';
+ }
+ elsif ( $prod eq 's' ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $prod eq 'bus' ) {
+ $linetype = 'bus';
+ }
+ elsif ( $prod eq 'u' ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $prod eq 'tram' ) {
+ $linetype = 'tram';
+ }
}
$res->{origin} = $journey->route_start;
$res->{destination} = $journey->route_end;
- $res->{operator} = $journey->operator;
+ $res->{operators} = [ $journey->operators ];
$res->{route_post_diff} = $route;
@@ -1246,7 +1330,15 @@ sub train_details {
= $station_info->{dep_cancelled};
$res->{is_cancelled} = $res->{arrival_is_cancelled}
|| $res->{arrival_is_cancelled};
- $res->{platform} = $station_info->{platform};
+ $res->{tz_offset} = $station_info->{tz_offset};
+ $res->{local_dt_da} = $station_info->{local_dt_da};
+ $res->{local_sched_arr} = $station_info->{local_sched_arr};
+ $res->{local_sched_dep} = $station_info->{local_sched_dep};
+ $res->{is_annotated} = $station_info->{is_annotated};
+ $res->{prod_name} = $station_info->{prod_name};
+ $res->{direction} = $station_info->{direction};
+ $res->{operator} = $station_info->{operator};
+ $res->{platform} = $station_info->{platform};
$res->{scheduled_platform}
= $station_info->{sched_platform};
}
@@ -1284,55 +1376,52 @@ sub train_details {
$res->{details} = [@him_details];
}
- if ( $self->param('detailed') ) {
- $res->{composition}
- = $self->app->train_details_db->{ $res->{train_no} };
- my @cycle_from;
- my @cycle_to;
- for my $pred ( @{ $res->{composition}{predecessors} // [] } ) {
- push( @cycle_from, $pred->[1] );
- }
- for my $succ ( @{ $res->{composition}{successors} // [] } ) {
- push( @cycle_to, $succ->[1] );
- }
- $res->{cycle_from}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] }
- @cycle_from ];
- $res->{cycle_to}
- = [ map { [ $_, $self->app->train_details_db->{$_} ] }
- @cycle_to ];
- }
-
- $self->render(
- $self->param('ajax') ? '_train_details' : 'train_details',
- description => sprintf(
- '%s %s%s%s nach %s',
- $res->{train_type},
- $res->{train_line} // $res->{train_no},
- $res->{origin} ? ' von ' : q{},
- $res->{origin} // q{},
- $res->{destination} // 'unbekannt'
- ),
- departure => $res,
- linetype => $linetype,
- icetype => $self->app->ice_type_map->{ $res->{train_no} },
- details => {}, #$departure->{composition} // {},
- dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ $self->respond_to(
+ json => {
+ json => {
+ journey => $journey,
+ },
+ },
+ any => {
+ template => $self->param('ajax')
+ ? '_train_details'
+ : 'train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $res->{train_type},
+ $res->{train_line} // $res->{train_no},
+ $res->{origin} ? ' von ' : q{},
+ $res->{origin} // q{},
+ $res->{destination} // 'unbekannt'
+ ),
+ departure => $res,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ },
);
}
)->catch(
sub {
my ($e) = @_;
if ($e) {
- $self->render(
- 'exception',
- message => $e,
- exception => undef,
- snapshot => {}
+ $self->respond_to(
+ json => {
+ json => {
+ error => $e,
+ },
+ status => 500,
+ },
+ any => {
+ template => 'exception',
+ message => $e,
+ exception => undef,
+ snapshot => {},
+ status => 500,
+ },
);
}
else {
- $self->render('not_found');
+ $self->render( 'not_found', status => 404 );
}
}
)->wait;
@@ -1356,6 +1445,7 @@ sub handle_result {
my $callback = $self->param('callback');
my $via = $self->param('via');
my $hafas = $self->param('hafas');
+ my $hafas_obj = $data->{hafas};
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
@@ -1403,6 +1493,9 @@ sub handle_result {
}
}
+ my $class_to_product
+ = $hafas_obj ? $self->class_to_product($hafas_obj) : {};
+
@results = $self->filter_results(@results);
for my $result (@results) {
@@ -1442,19 +1535,20 @@ sub handle_result {
}
}
elsif ( $result->can('class') ) {
- if ( $result->class <= 2 ) {
+ my $prod = $class_to_product->{ $result->class } // q{};
+ if ( $prod eq 'ice' or $prod eq 'ic_ec' ) {
$linetype = 'fern';
}
- elsif ( $result->class == 16 ) {
+ elsif ( $prod eq 's' ) {
$linetype = 'sbahn';
}
- elsif ( $result->class == 32 ) {
+ elsif ( $prod eq 'bus' ) {
$linetype = 'bus';
}
- elsif ( $result->class == 128 ) {
+ elsif ( $prod eq 'u' ) {
$linetype = 'ubahn';
}
- elsif ( $result->class == 256 ) {
+ elsif ( $prod eq 'tram' ) {
$linetype = 'tram';
}
}
@@ -1662,6 +1756,8 @@ sub handle_result {
station => $result->station,
moreinfo => $moreinfo,
delay => $delay,
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
missing_realtime => (
not $result->has_realtime
and $result->start < $now ? 1 : 0
@@ -1782,6 +1878,11 @@ sub handle_result {
my $params = $self->req->params->clone;
$params->param( hafas => not $params->param('hafas') );
if ( $params->param('hafas') ) {
+ if ( $data->{station_eva} >= 8100000
+ and $data->{station_eva} < 8200000 )
+ {
+ $params->param( hafas => 'ÖBB' );
+ }
$api_link = '/' . $data->{station_eva} . '?' . $params->to_string;
$api_text = 'Auf Nahverkehr wechseln';
$api_icon = 'train';
@@ -1803,7 +1904,6 @@ sub handle_result {
api_text => $api_text,
api_icon => $api_icon,
departures => \@departures,
- ice_type => $self->app->ice_type_map,
station => $station_name,
version => $self->config->{version},
title => $via ? "$station_name → $via" : $station_name,
@@ -1831,14 +1931,23 @@ sub handle_result {
sub stations_by_coordinates {
my $self = shift;
- my $lon = $self->param('lon');
- my $lat = $self->param('lat');
+ my $lon = $self->param('lon');
+ my $lat = $self->param('lat');
+ my $hafas = $self->param('hafas');
if ( not $lon or not $lat ) {
$self->render( json => { error => 'Invalid lon/lat received' } );
return;
}
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
$self->render_later;
my @iris = map {
@@ -1859,6 +1968,7 @@ sub stations_by_coordinates {
Travel::Status::DE::HAFAS->new_p(
promise => 'Mojo::Promise',
user_agent => $self->ua,
+ service => $service,
geoSearch => {
lat => $lat,
lon => $lon
@@ -1871,7 +1981,7 @@ sub stations_by_coordinates {
name => $_->name,
eva => $_->eva,
distance => $_->distance_m / 1000,
- hafas => 1
+ hafas => $service,
}
} $hafas->results;
if ( @hafas > 10 ) {
@@ -1947,6 +2057,10 @@ sub redirect_to_station {
$params = $params->to_string;
$self->redirect_to("/z/${input}?${params}");
}
+ elsif ( $params->param('hafas') and $params->param('hafas') ne '1' ) {
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
+ }
else {
my @candidates
= Travel::Status::DE::IRIS::Stations::get_station($input);
diff --git a/lib/DBInfoscreen/Controller/Wagenreihung.pm b/lib/DBInfoscreen/Controller/Wagenreihung.pm
index 5e5f653..03a607d 100644
--- a/lib/DBInfoscreen/Controller/Wagenreihung.pm
+++ b/lib/DBInfoscreen/Controller/Wagenreihung.pm
@@ -13,126 +13,20 @@ use utf8;
use Travel::Status::DE::DBWagenreihung;
use Travel::Status::DE::DBWagenreihung::Wagon;
-sub get_zugbildung_db {
- my ( $self, $train_no ) = @_;
-
- my $details = $self->app->train_details_db->{$train_no};
-
- if ( not $details ) {
- return;
- }
-
- my @wagons;
-
- for my $wagon ( @{ $details->{wagons} } ) {
- my $wagon_type = $wagon->{type};
- my $wagon_number = $wagon->{number};
- my %wagon = (
- fahrzeugnummer => "",
- fahrzeugtyp => $wagon_type,
- kategorie => $wagon_type =~ m{^[0-9.]+$} ? 'LOK' : '',
- train_no => $train_no,
- wagenordnungsnummer => $wagon_number,
- positionamhalt => {
- startprozent => 0,
- endeprozent => 0,
- startmeter => 0,
- endemeter => 0,
- }
- );
- my $wagon = Travel::Status::DE::DBWagenreihung::Wagon->new(%wagon);
-
- if ( $details->{type} ) {
- $wagon->set_traintype( $details->{type} );
- }
- push( @wagons, $wagon );
- }
-
- my $pos = 0;
- for my $wagon (@wagons) {
- $wagon->{position}{start_percent} = $pos;
- $wagon->{position}{end_percent} = $pos + 5;
- $pos += 5;
- }
-
- my $train_type = $details->{rawType};
- $train_type =~ s{ - .* }{}x;
-
- my $route_start = $details->{route}{start} // $details->{route}{preStart};
- my $route_end = $details->{route}{end} // $details->{route}{postEnd};
- my $route = "${route_start} → ${route_end}";
-
- return {
- route => $route,
- train_type => $train_type,
- wagons => [@wagons]
- };
-}
-
-sub zugbildung_db {
- my ($self) = @_;
-
- my $train_no = $self->param('train');
-
- my $details = $self->get_zugbildung_db($train_no);
-
- if ( not $details ) {
- $self->render( 'not_found',
- message => "Keine Daten zu Zug ${train_no} bekannt" );
- return;
- }
+sub handle_wagenreihung_error {
+ my ( $self, $train_no, $err ) = @_;
$self->render(
- 'zugbildung_db',
- description => sprintf(
- 'Soll-Wagenreihung %s %s',
- $details->{train_type} // 'Zug', $train_no
- ),
- wr_error => undef,
- title => $details->{train_type} . ' ' . $train_no,
- route => $details->{route},
- zb => $details,
+ 'wagenreihung',
+ title => "Zug $train_no",
+ wr_error => $err,
train_no => $train_no,
- wagons => $details->{wagons},
+ wr => undef,
+ wref => undef,
hide_opts => 1,
);
}
-sub handle_wagenreihung_error {
- my ( $self, $train_no, $err ) = @_;
-
- my $details = $self->get_zugbildung_db($train_no);
- if ( $details and @{ $details->{wagons} } ) {
- my $wr_error
- = "${err}. Ersatzweise werden die Solldaten laut Fahrplan angezeigt.";
- $self->render(
- 'zugbildung_db',
- description => sprintf(
- 'Soll-Wagenreihung %s %s',
- $details->{train_type} // 'Zug', $train_no
- ),
- wr_error => $wr_error,
- title => $details->{train_type} . ' ' . $train_no,
- route => $details->{route},
- zb => $details,
- train_no => $train_no,
- wagons => $details->{wagons},
- hide_opts => 1,
- );
- }
- else {
- $self->render(
- 'wagenreihung',
- title => "Zug $train_no",
- wr_error => $err,
- train_no => $train_no,
- wr => undef,
- wref => undef,
- hide_opts => 1,
- );
- }
-}
-
sub wagenreihung {
my ($self) = @_;
my $train = $self->stash('train');
@@ -178,7 +72,7 @@ sub wagenreihung {
e => $exit_side ? substr( $exit_side, 0, 1 ) : '',
tt => $wr->train_type,
tn => $train,
- s => $wr->station_name,
+ s => $wr->station->{name},
p => $wr->platform
};
@@ -274,7 +168,7 @@ sub wagenreihung {
'wagenreihung',
description => sprintf(
'Ist-Wagenreihung %s in %s',
- $title, $wr->station_name
+ $title, $wr->station->{name}
),
wr_error => undef,
title => $title,
@@ -290,7 +184,7 @@ sub wagenreihung {
my ($err) = @_;
$self->handle_wagenreihung_error( $train,
- $err->{error}->{msg} // "Unbekannter Fehler" );
+ $err->{error}->{msg} // $err // "Unbekannter Fehler" );
return;
}
)->wait;
@@ -331,15 +225,15 @@ sub wagen {
);
}
- my $title = $self->l('Wagen ') . $wagon_id;
+ my $title = 'Wagen ' . $wagon_id;
if ( $wref->{tt} and $wref->{tn} ) {
$title = sprintf( '%s %s', $wref->{tt}, $wref->{tn} );
if ($wagon_no) {
- $title .= ' ' . $self->l('Wagen ') . $wagon_no;
+ $title .= ' Wagen ' . $wagon_no;
}
else {
- $title .= ' ' . $self->l('Wagen ') . $wagon_id;
+ $title .= ' Wagen ' . $wagon_id;
}
}
diff --git a/lib/DBInfoscreen/Helper/HAFAS.pm b/lib/DBInfoscreen/Helper/HAFAS.pm
index ad21c58..cdb84f0 100644
--- a/lib/DBInfoscreen/Helper/HAFAS.pm
+++ b/lib/DBInfoscreen/Helper/HAFAS.pm
@@ -39,6 +39,7 @@ sub get_route_p {
if ( $opt{trip_id} ) {
$hafas_promise = Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service},
journey => {
id => $opt{trip_id},
},
@@ -104,7 +105,67 @@ sub get_route_p {
my $journey = $hafas->result;
my @ret;
my $station_is_past = 1;
+
+ my $num_names = 0;
+ my $prev_name = q{};
+ my $num_directions = 0;
+ my $prev_direction = q{};
+ my $num_operators = 0;
+ my $prev_operator = q{};
+
+ for my $stop ( $journey->route ) {
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ if ( $prod and $prod->name and $prod->name ne $prev_name ) {
+ $num_names++;
+ $prev_name = $prod->name;
+ }
+ if ( $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $num_operators++;
+ $prev_operator = $prod->operator;
+ }
+ if ( $stop->direction and $stop->direction ne $prev_direction )
+ {
+ $num_directions++;
+ $prev_direction = $stop->direction;
+ }
+ }
+
+ $prev_name = q{};
+ $prev_direction = q{};
+ $prev_operator = q{};
+
for my $stop ( $journey->route ) {
+
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ my %annotation;
+ if ( $num_names > 1
+ and $prod
+ and $prod->name
+ and $prod->name ne $prev_name )
+ {
+ $prev_name = $annotation{prod_name} = $prod->name;
+ }
+ if ( $num_operators > 1
+ and $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $prev_operator = $annotation{operator} = $prod->operator;
+ }
+ if ( $num_directions > 1
+ and $stop->direction
+ and $stop->direction ne $prev_direction )
+ {
+ $prev_direction = $annotation{direction} = $stop->direction;
+ }
+
+ if (%annotation) {
+ $annotation{is_annotated} = 1;
+ }
+
push(
@ret,
{
@@ -118,6 +179,7 @@ sub get_route_p {
dep_delay => $stop->dep_delay,
arr_cancelled => $stop->arr_cancelled,
dep_cancelled => $stop->dep_cancelled,
+ tz_offset => $stop->tz_offset,
platform => $stop->platform,
sched_platform => $stop->sched_platform,
load => $stop->load,
@@ -127,6 +189,7 @@ sub get_route_p {
and
( $stop->dep_cancelled or not $stop->sched_dep )
),
+ %annotation,
}
);
if (
@@ -141,9 +204,35 @@ sub get_route_p {
$station_is_past = 0;
}
$ret[-1]{isPast} = $station_is_past;
+ if ( $stop->tz_offset ) {
+ if ( $stop->sched_arr ) {
+ $ret[-1]{local_sched_arr}
+ = $stop->sched_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->sched_dep ) {
+ $ret[-1]{local_sched_dep}
+ = $stop->sched_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_arr ) {
+ $ret[-1]{local_rt_arr} = $stop->rt_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_dep ) {
+ $ret[-1]{local_rt_dep} = $stop->rt_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ $ret[-1]{local_dt_ad} = $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr} // $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep};
+ $ret[-1]{local_dt_da} = $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep} // $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr};
+ }
}
- $promise->resolve( \@ret, $journey );
+ $promise->resolve( \@ret, $journey, $hafas );
return;
}
)->catch(
@@ -160,11 +249,15 @@ sub get_route_p {
# Input: (HAFAS TripID, line number)
# Output: Promise returning a Travel::Status::DE::HAFAS::Journey instance on success
sub get_polyline_p {
- my ( $self, $trip_id, $line ) = @_;
+ my ( $self, %opt ) = @_;
+ my $trip_id = $opt{id};
+ my $line = $opt{line};
+ my $service = $opt{service};
my $promise = Mojo::Promise->new;
Travel::Status::DE::HAFAS->new_p(
+ service => $service,
journey => {
id => $trip_id,
name => $line,
diff --git a/public/static/css/dark.min.css b/public/static/css/dark.min.css
index 41e5170..f9b5b35 100644
--- a/public/static/css/dark.min.css
+++ b/public/static/css/dark.min.css
@@ -1 +1 @@
-body{margin:0;color:#fff;background-color:#101010}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#99f;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#222}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#222}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#bbb}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#bbb}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#330}.wagonorder .powercar{background-color:#222}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#bbb}.wagonorder .details a.type{color:#99f}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#bbb;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uic78::before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uicno{color:#bbb}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck::before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#444;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#101010}div.app>ul>li.cancelled{background-color:#512f00}div.app>ul>li.past{opacity:0.8;background-color:#222}div.app>ul>li>a{color:#fff}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#fff;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#ddd}div.app>ul>li .info{color:#f77}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#fff}div.app>ul>li .dest{background-color:transparent;color:#fff}div.app>ul>li .origin{background-color:transparent;color:#bbb}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#fff}div.app>ul>li .changed-platform{color:#f77}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#fff}div.app>ul>li .time.delayed{color:#f77;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:#f77;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#7f7;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#d99}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#9d9}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#afa}div.app .replaced{color:#faa}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#151}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#333}div.app .fern{background-color:#511}div.app .ext{border:2px solid #333}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#411}div.app .bus{background-color:#515}div.app .ubahn{background-color:#071e62}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#101010}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#fff}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:#f77}div.app .moreinfo .timehidden{color:#bbb}div.app .moreinfo .undelay{color:#7f7}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#f77}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#fff}div.app .moreinfo .mroute .generic-stop{color:#bbb}div.app .moreinfo .mroute .additional-stop{color:#7f7}div.app .moreinfo .mroute .cancelled-stop{color:#f77}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#bbb}div.config a{color:#99f;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#bbb}div.about a{color:#99f;text-decoration:none}.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;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.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 .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#bbb}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.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 .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#fff;background-color:#101010;border:1px solid #444;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-light{color:#ddd;background-color:#101010;border-color:#444}.button-light:active,.button-light:focus,.button-light:hover{color:#ddd;background-color:#111;border-color:#333}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{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)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
+body{margin:0;color:#fff;background-color:#101010}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#99f;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#222}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#222}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#bbb}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#bbb}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#330}.wagonorder .powercar{background-color:#222}.wagonorder .closed{background-color:#222}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#fff}.wagonorder .details a.type{color:#99f}.wagonorder .details .groupno{color:#fff}.wagonorder .details .grouptype{color:#bbb}.wagonorder .details .grouptype:before{content:"("}.wagonorder .details .grouptype:after{content:")"}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#bbb;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uic78:before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uicno{color:#bbb}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck:before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#444;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#101010}div.app>ul>li.cancelled{background-color:#512f00}div.app>ul>li.past{opacity:0.8;background-color:#222}div.app>ul>li>a{color:#fff}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#fff;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#ddd}div.app>ul>li .info{color:#f77}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#fff}div.app>ul>li .dest{background-color:transparent;color:#fff}div.app>ul>li .origin{background-color:transparent;color:#bbb}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#fff}div.app>ul>li .changed-platform{color:#f77}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#fff}div.app>ul>li .time.delayed{color:#f77;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:#f77;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#7f7;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#d99}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#9d9}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#afa}div.app .replaced{color:#faa}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#151}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#333}div.app .fern{background-color:#511}div.app .ext{border:2px solid #333}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#411}div.app .bus{background-color:#515}div.app .ubahn{background-color:#071e62}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#101010}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#fff}div.app .moreinfo .wagonorder-preview .otherno{color:#bbb}div.app .moreinfo .wagonorder-preview .meta{color:#ddd}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:#f77}div.app .moreinfo .timehidden{color:#bbb}div.app .moreinfo .undelay{color:#7f7}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#f77}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#fff}div.app .moreinfo .mroute .generic-stop{color:#bbb}div.app .moreinfo .mroute .additional-stop{color:#7f7}div.app .moreinfo .mroute .cancelled-stop{color:#f77}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute .time-early{color:#cfc}div.app .moreinfo .mroute .time-delayed{color:#f99}div.app .moreinfo .mroute .time-sched-only{color:#f99}div.app .moreinfo .mroute .time-sched-ontime{color:#cfc}div.app .moreinfo .mroute .annotation{color:#bbb;list-style-type:none;padding-left:3em}div.app .moreinfo .mroute .-sched:before{content:" "}div.app .moreinfo .mroute .time-sched:after{content:" "}div.app .moreinfo .mroute .time-sched-only:before{content:"("}div.app .moreinfo .mroute .time-sched-only:after{content:")"}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#bbb}div.config a{color:#99f;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#bbb}div.about a{color:#99f;text-decoration:none}.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;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.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 .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#bbb}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.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 .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#fff;background-color:#101010;border:1px solid #444;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-light{color:#ddd;background-color:#101010;border-color:#444}.button-light:active,.button-light:focus,.button-light:hover{color:#ddd;background-color:#111;border-color:#333}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{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)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
diff --git a/public/static/css/mobile.css b/public/static/css/legacy-mobile.css
index 0bf84d4..0bf84d4 100644
--- a/public/static/css/mobile.css
+++ b/public/static/css/legacy-mobile.css
diff --git a/public/static/css/default.css b/public/static/css/legacy.css
index ac2eb79..ac2eb79 100644
--- a/public/static/css/default.css
+++ b/public/static/css/legacy.css
diff --git a/public/static/css/light.min.css b/public/static/css/light.min.css
index f2de094..26205dd 100644
--- a/public/static/css/light.min.css
+++ b/public/static/css/light.min.css
@@ -1 +1 @@
-body{margin:0;color:#000;background-color:#fff}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#009;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#ddd}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#ddd}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#666}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#666}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#ff9}.wagonorder .powercar{background-color:#ccc}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#666}.wagonorder .details a.type{color:#009}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#666;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uic78::before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uicno{color:#666}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck::before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#ccc;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#fff}div.app>ul>li.cancelled{background-color:#ffe7d0}div.app>ul>li.past{opacity:0.8;background-color:#ddd}div.app>ul>li>a{color:#000}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#000;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#444}div.app>ul>li .info{color:red}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#000}div.app>ul>li .dest{background-color:transparent;color:#000}div.app>ul>li .origin{background-color:transparent;color:#666}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#000}div.app>ul>li .changed-platform{color:red}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#000}div.app>ul>li .time.delayed{color:red;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:red;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#060;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#b33}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#383}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#060}div.app .replaced{color:#600}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#95d79f}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#eee}div.app .fern{background-color:#fdd}div.app .ext{border:2px solid #eee}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#fcc}div.app .bus{background-color:#eae}div.app .ubahn{background-color:#aac0ff}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#fff}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#000}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:red}div.app .moreinfo .timehidden{color:#666}div.app .moreinfo .undelay{color:#060}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#c00}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#000}div.app .moreinfo .mroute .generic-stop{color:#666}div.app .moreinfo .mroute .additional-stop{color:#090}div.app .moreinfo .mroute .cancelled-stop{color:#c00}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#666}div.config a{color:#009;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#666}div.about a{color:#009;text-decoration:none}.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;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.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 .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#666}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.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 .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#000;background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-light{color:#333;background-color:#fff;border-color:#ccc}.button-light:active,.button-light:focus,.button-light:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{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)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
+body{margin:0;color:#000;background-color:#fff}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#009;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#ddd}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#ddd}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#666}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#666}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#ff9}.wagonorder .powercar{background-color:#ccc}.wagonorder .closed{background-color:#ddd}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#000}.wagonorder .details a.type{color:#009}.wagonorder .details .groupno{color:#000}.wagonorder .details .grouptype{color:#666}.wagonorder .details .grouptype:before{content:"("}.wagonorder .details .grouptype:after{content:")"}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#666;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uic78:before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uicno{color:#666}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck:before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#ccc;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#fff}div.app>ul>li.cancelled{background-color:#ffe7d0}div.app>ul>li.past{opacity:0.8;background-color:#ddd}div.app>ul>li>a{color:#000}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#000;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#444}div.app>ul>li .info{color:red}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#000}div.app>ul>li .dest{background-color:transparent;color:#000}div.app>ul>li .origin{background-color:transparent;color:#666}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#000}div.app>ul>li .changed-platform{color:red}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#000}div.app>ul>li .time.delayed{color:red;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:red;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#060;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#b33}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#383}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#060}div.app .replaced{color:#600}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#95d79f}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#eee}div.app .fern{background-color:#fdd}div.app .ext{border:2px solid #eee}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#fcc}div.app .bus{background-color:#eae}div.app .ubahn{background-color:#aac0ff}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#fff}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#000}div.app .moreinfo .wagonorder-preview .otherno{color:#666}div.app .moreinfo .wagonorder-preview .meta{color:#333}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:red}div.app .moreinfo .timehidden{color:#666}div.app .moreinfo .undelay{color:#060}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#c00}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#000}div.app .moreinfo .mroute .generic-stop{color:#666}div.app .moreinfo .mroute .additional-stop{color:#090}div.app .moreinfo .mroute .cancelled-stop{color:#c00}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute .time-early{color:#070}div.app .moreinfo .mroute .time-delayed{color:#900}div.app .moreinfo .mroute .time-sched-only{color:#900}div.app .moreinfo .mroute .time-sched-ontime{color:#070}div.app .moreinfo .mroute .annotation{color:#666;list-style-type:none;padding-left:3em}div.app .moreinfo .mroute .-sched:before{content:" "}div.app .moreinfo .mroute .time-sched:after{content:" "}div.app .moreinfo .mroute .time-sched-only:before{content:"("}div.app .moreinfo .mroute .time-sched-only:after{content:")"}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#666}div.config a{color:#009;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#666}div.about a{color:#009;text-decoration:none}.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;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.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 .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#666}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.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 .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#000;background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-light{color:#333;background-color:#fff;border-color:#ccc}.button-light:active,.button-light:focus,.button-light:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{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)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
diff --git a/public/static/css/material-icons.css b/public/static/css/material-icons.css
index 64358b5..aa3afa7 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/v89/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
+ src: url(/static/v99/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
- url(/static/v89/fonts/MaterialIcons-Regular.woff2) format('woff2'),
- url(/static/v89/fonts/MaterialIcons-Regular.woff) format('woff'),
- url(/static/v89/fonts/MaterialIcons-Regular.ttf) format('truetype');
+ url(/static/v99/fonts/MaterialIcons-Regular.woff2) format('woff2'),
+ url(/static/v99/fonts/MaterialIcons-Regular.woff) format('woff'),
+ url(/static/v99/fonts/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
diff --git a/public/static/js/collapse.js b/public/static/js/collapse.js
index 2db1089..d7d1e3b 100644
--- a/public/static/js/collapse.js
+++ b/public/static/js/collapse.js
@@ -113,7 +113,7 @@ function dbf_reg_handlers() {
if (param.get('detailed')) {
suffix += '&detailed=1';
}
- if (param.get('hafas')) {
+ if (param.get('hafas') && param.get('hafas') != '0') {
suffix += '&hafas=' + param.get('hafas') + '&highlight=' + trainElem.data('station');
}
if (param.get('past')) {
@@ -122,7 +122,7 @@ function dbf_reg_handlers() {
if (param.get('rt') || param.get('show_realtime')) {
suffix += '&rt=1';
}
- if (param.get('hafas')) {
+ if (param.get('hafas') && param.get('hafas') != '0') {
history.pushState({'page':'traindetail','jid':trainElem.data('jid')}, 'test', '/z/' + trainElem.data('jid') + suffix);
} else {
history.pushState({'page':'traindetail','station':station,'train':trainElem.data('no')}, 'test', '/z/' + trainElem.data('train') + '/' + trainElem.data('station') + suffix);
diff --git a/public/static/js/dbf.min.js b/public/static/js/dbf.min.js
index 73981f7..da4ac90 100644
--- a/public/static/js/dbf.min.js
+++ b/public/static/js/dbf.min.js
@@ -1 +1 @@
-function setLang(e){document.cookie="lang="+e+";SameSite=None;Secure",location.reload()}function setTheme(e){localStorage.setItem("theme",e),otherTheme.hasOwnProperty(e)||(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),addStyleSheet(e,"theme")}function reload_app(){0==$(".expanded-moreinfo").length?$.get(window.location.href,{ajax:1},function(e){$("div.app > ul").html(e),dbf_reg_handlers(),setTimeout(reload_app,6e4)}).fail(function(){setTimeout(reload_app,1e4)}):setTimeout(reload_app,3e4)}function dbf_show_moreinfo(d,n){const s=d.data("routeprev").split("|"),r=d.data("routenext").split("|"),l=d.data("moreinfo").split("|");$(".moreinfo").each(function(){var e=$(this);if(!n){$(".moreinfo .train-line").removeClass("sbahn fern ext ubahn bus tram").addClass(d.data("linetype")),$(".moreinfo .train-line").text(d.data("line")),$(".moreinfo .train-no").text(d.data("no")),$(".moreinfo .train-origin").text(d.data("from")),$(".moreinfo .train-dest").text(d.data("to")),$(".moreinfo .minfo").text(""),$(".moreinfo .mfooter").html(""),$(".moreinfo .verbose").html(""),$(".moreinfo .mroute").html(""),$(".moreinfo ul").html("");var a="";if(""!=d.data("arrival")?a+='<div><div class="arrival">An: '+d.data("arrival")+"</div></div>":a+='<div><div class="arrival"></div></div>',""!=d.data("platform")?a+='<div><div class="platform">Gleis '+d.data("platform")+"</div></div>":a+='<div><div class="platform"></div></div>',""!=d.data("departure")?a+='<div><div class="departure">Ab: '+d.data("departure")+"</div></div>":a+='<div><div class="departure"></div></div>',$(".moreinfo .mfooter").append('<div class="dataline">'+a+"</div>"),0==$(".moreinfo .loading").length&&$(".moreinfo .mfooter").append('<div class="loading">Lade Daten, bitte warten...</div>'),""!=d.data("moreinfo")){var t="";for(i in l)t+="<li>"+l[i]+"</li>";$(".moreinfo .mfooter").append("Meldungen: <ul>"+t+"</ul>")}var o="";if(""!=d.data("routeprev"))for(var i in s)o+="<li>"+s[i]+"</li>";if(o+="<li><strong>"+document.title+"</strong></li>",""!=d.data("routenext"))for(var i in r)o+="<li>"+r[i]+"</li>";$(".moreinfo .mfooter").append('Fahrtverlauf: <ul class="mroute">'+o+"</ul>")}$.get(window.location.href,{train:d.data("train"),jid:d.data("jid"),ajax:1},function(e){$(".moreinfo").html(e)}).fail(function(){$(".moreinfo .mfooter").append("Der Zug ist abgefahren (Zug nicht gefunden)")}),e.removeClass("collapsed-moreinfo"),e.addClass("expanded-moreinfo")})}function dbf_reg_handlers(){$("div.app > ul > li").click(function(e){var a=$(this),t=$("div.app").data("station"),e=(e.preventDefault(),"?");window.location.href.includes("detailed=1")&&(e+="&detailed=1"),window.location.href.includes("hafas=1")&&(e+="&hafas=1&highlight="+a.data("station")),window.location.href.includes("past=1")&&(e+="&past=1"),(window.location.href.includes("rt=1")||window.location.href.includes("show_realtime=1"))&&(e+="&rt=1"),window.location.href.includes("hafas=1")?history.pushState({page:"traindetail",jid:a.data("jid")},"test","/z/"+a.data("jid")+e):history.pushState({page:"traindetail",station:t,train:a.data("no")},"test","/z/"+a.data("train")+"/"+a.data("station")+e),dbf_show_moreinfo(a,!1)});const a=$(location).attr("hash").substr(1);var t;a&&(t=!1,$("div.app > ul > li").each(function(e){t||$(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)})}),t)&&(t=!1,$("div.app > ul > li").each(function(e){t||($(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)}),t?$(this).addClass("selected"):$(this).addClass("past"))}))}$(function(){$(".moresettings-header").each(function(){$(this).click(function(){var e=$(".moresettings");$(this).hasClass("moresettings-header-collapsed")?($(this).removeClass("moresettings-header-collapsed"),$(this).addClass("moresettings-header-expanded"),e.removeClass("moresettings-collapsed"),e.addClass("moresettings-expanded")):($(this).removeClass("moresettings-header-expanded"),$(this).addClass("moresettings-header-collapsed"),e.removeClass("moresettings-expanded"),e.addClass("moresettings-collapsed"))})}),$(".developers-header").each(function(){$(this).click(function(){var e=$(".developers");$(this).hasClass("developers-header-collapsed")?($(this).removeClass("developers-header-collapsed"),$(this).addClass("developers-header-expanded"),e.removeClass("developers-collapsed"),e.addClass("developers-expanded")):($(this).removeClass("developers-header-expanded"),$(this).addClass("developers-header-collapsed"),e.removeClass("developers-expanded"),e.addClass("developers-collapsed"))})}),dbf_reg_handlers(),$(".content .app").length&&(setTimeout(reload_app,3e4),history.replaceState({page:"station"},document.title,"")),window.onpopstate=function(a){var t;null!=a.state?"station"==a.state.page?($(".moreinfo").each(function(){$(this).removeClass("expanded-moreinfo"),$(this).addClass("collapsed-moreinfo")}),$("div.app > ul").length||($("div.app").append("<ul></ul>"),reload_app())):"traindetail"==a.state.page&&(t=!1,$("div.app > ul > li").each(function(){var e=$(this);e.data("no")==a.state.train&&(dbf_show_moreinfo(e,!0),t=!0)}),t||($(".moreinfo").each(function(){$(this).removeClass("collapsed-moreinfo"),$(this).addClass("expanded-moreinfo")}),$(".moreinfo .mfooter").append("Der Zug ist abgefahren (Zug nicht gefunden)"))):console.log("unhandled popstate! "+document.location)}});
+function setLang(e){document.cookie="lang="+e+";SameSite=None;Secure",location.reload()}function setTheme(e){localStorage.setItem("theme",e),otherTheme.hasOwnProperty(e)||(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),addStyleSheet(e,"theme")}function reload_app(){0==$(".expanded-moreinfo").length?$.get(window.location.href,{ajax:1},function(e){$("div.app > ul").html(e),dbf_reg_handlers(),setTimeout(reload_app,6e4)}).fail(function(){setTimeout(reload_app,1e4)}):setTimeout(reload_app,3e4)}function dbf_show_moreinfo(d,s){const n=d.data("routeprev").split("|"),r=d.data("routenext").split("|"),l=d.data("moreinfo").split("|");$(".moreinfo").each(function(){var e=$(this);if(!s){$(".moreinfo .train-line").removeClass("sbahn fern ext ubahn bus tram").addClass(d.data("linetype")),$(".moreinfo .train-line").text(d.data("line")),$(".moreinfo .train-no").text(d.data("no")),$(".moreinfo .train-origin").text(d.data("from")),$(".moreinfo .train-dest").text(d.data("to")),$(".moreinfo .minfo").text(""),$(".moreinfo .mfooter").html(""),$(".moreinfo .verbose").html(""),$(".moreinfo .mroute").html(""),$(".moreinfo ul").html("");var a="";if(""!=d.data("arrival")?a+='<div><div class="arrival">An: '+d.data("arrival")+"</div></div>":a+='<div><div class="arrival"></div></div>',""!=d.data("platform")?a+='<div><div class="platform">Gleis '+d.data("platform")+"</div></div>":a+='<div><div class="platform"></div></div>',""!=d.data("departure")?a+='<div><div class="departure">Ab: '+d.data("departure")+"</div></div>":a+='<div><div class="departure"></div></div>',$(".moreinfo .mfooter").append('<div class="dataline">'+a+"</div>"),0==$(".moreinfo .loading").length&&$(".moreinfo .mfooter").append('<div class="loading">Lade Daten, bitte warten...</div>'),""!=d.data("moreinfo")){var t="";for(i in l)t+="<li>"+l[i]+"</li>";$(".moreinfo .mfooter").append("Meldungen: <ul>"+t+"</ul>")}var o="";if(""!=d.data("routeprev"))for(var i in n)o+="<li>"+n[i]+"</li>";if(o+="<li><strong>"+document.title+"</strong></li>",""!=d.data("routenext"))for(var i in r)o+="<li>"+r[i]+"</li>";$(".moreinfo .mfooter").append('Fahrtverlauf: <ul class="mroute">'+o+"</ul>")}$.get(window.location.href,{train:d.data("train"),jid:d.data("jid"),ajax:1},function(e){$(".moreinfo").html(e)}).fail(function(){$(".moreinfo .mfooter").append("Der Zug ist abgefahren (Zug nicht gefunden)")}),e.removeClass("collapsed-moreinfo"),e.addClass("expanded-moreinfo")})}function dbf_reg_handlers(){$("div.app > ul > li").click(function(e){var a=$(this),t=$("div.app").data("station"),o=new URLSearchParams(window.location.search),e=(e.preventDefault(),"?");o.get("detailed")&&(e+="&detailed=1"),o.get("hafas")&&"0"!=o.get("hafas")&&(e+="&hafas="+o.get("hafas")+"&highlight="+a.data("station")),o.get("past")&&(e+="&past=1"),(o.get("rt")||o.get("show_realtime"))&&(e+="&rt=1"),o.get("hafas")&&"0"!=o.get("hafas")?history.pushState({page:"traindetail",jid:a.data("jid")},"test","/z/"+a.data("jid")+e):history.pushState({page:"traindetail",station:t,train:a.data("no")},"test","/z/"+a.data("train")+"/"+a.data("station")+e),dbf_show_moreinfo(a,!1)});const a=$(location).attr("hash").substr(1);var t;a&&(t=!1,$("div.app > ul > li").each(function(e){t||$(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)})}),t)&&(t=!1,$("div.app > ul > li").each(function(e){t||($(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)}),t?$(this).addClass("selected"):$(this).addClass("past"))}))}$(function(){$(".moresettings-header").each(function(){$(this).click(function(){var e=$(".moresettings");$(this).hasClass("moresettings-header-collapsed")?($(this).removeClass("moresettings-header-collapsed"),$(this).addClass("moresettings-header-expanded"),e.removeClass("moresettings-collapsed"),e.addClass("moresettings-expanded")):($(this).removeClass("moresettings-header-expanded"),$(this).addClass("moresettings-header-collapsed"),e.removeClass("moresettings-expanded"),e.addClass("moresettings-collapsed"))})}),$(".developers-header").each(function(){$(this).click(function(){var e=$(".developers");$(this).hasClass("developers-header-collapsed")?($(this).removeClass("developers-header-collapsed"),$(this).addClass("developers-header-expanded"),e.removeClass("developers-collapsed"),e.addClass("developers-expanded")):($(this).removeClass("developers-header-expanded"),$(this).addClass("developers-header-collapsed"),e.removeClass("developers-expanded"),e.addClass("developers-collapsed"))})}),dbf_reg_handlers(),$(".content .app").length&&(setTimeout(reload_app,3e4),history.replaceState({page:"station"},document.title,"")),window.onpopstate=function(a){var t;null!=a.state?"station"==a.state.page?($(".moreinfo").each(function(){$(this).removeClass("expanded-moreinfo"),$(this).addClass("collapsed-moreinfo")}),$("div.app > ul").length||($("div.app").append("<ul></ul>"),reload_app())):"traindetail"==a.state.page&&(t=!1,$("div.app > ul > li").each(function(){var e=$(this);e.data("no")==a.state.train&&(dbf_show_moreinfo(e,!0),t=!0)}),t||($(".moreinfo").each(function(){$(this).removeClass("collapsed-moreinfo"),$(this).addClass("expanded-moreinfo")}),$(".moreinfo .mfooter").append("Der Zug ist abgefahren (Zug nicht gefunden)"))):console.log("unhandled popstate! "+document.location)}});
diff --git a/public/static/js/geostop.js b/public/static/js/geostop.js
index 80e8311..fa2d6f1 100644
--- a/public/static/js/geostop.js
+++ b/public/static/js/geostop.js
@@ -42,7 +42,11 @@ $(function() {
hafas = candidate.hafas;
const stationlink = $(document.createElement('a'));
- stationlink.attr('href', eva + '?hafas=' + hafas);
+ if (hafas) {
+ stationlink.attr('href', eva + '?hafas=' + hafas);
+ } else {
+ stationlink.attr('href', eva);
+ }
stationlink.text(name + ' ');
const distancenode = $(document.createElement('div'));
@@ -61,7 +65,8 @@ $(function() {
};
const processLocation = function(loc) {
- $.post('/_geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude}, processResult).fail(function(jqXHR, textStatus, errorThrown) {
+ const param = new URLSearchParams(window.location.search);
+ $.post('/_geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude, hafas: param.get('hafas')}, processResult).fail(function(jqXHR, textStatus, errorThrown) {
removeStatus();
showError("Netzwerkfehler: ", textStatus, errorThrown);
});
diff --git a/public/static/js/geostop.min.js b/public/static/js/geostop.min.js
index 8a5db00..41e18e0 100644
--- a/public/static/js/geostop.min.js
+++ b/public/static/js/geostop.min.js
@@ -1 +1 @@
-$(function(){function t(e){o(),e.error?r("Backend-Fehler:",e.error,null):0==e.candidates.length?r("Keine Stationen in 70km Umkreis gefunden","",null):$.each(e.candidates,function(e,t){var n=t.eva,o=t.name,r=t.distance.toFixed(1),t=t.hafas,a=$(document.createElement("a")),n=(a.attr("href",n+"?hafas="+t),a.text(o+" "),$(document.createElement("div"))),o=(n.attr("class","distance"),n.text(r),$(document.createElement("i")));o.attr("class","material-icons"),o.text(t?"directions":"train"),a.append(o),a.append(n),$("div.candidatelist").append(a)})}const o=function(){$("div.candidatestatus").remove()},r=function(e,t,n){var o=$(document.createElement("div")),t=(o.attr("class","error"),o.text(t),$(document.createElement("strong")));t.text(e),o.prepend(t),n&&((e=$(document.createElement("div"))).attr("class","errcode"),e.text(n),o.append(e)),$("div.candidatelist").append(o)};navigator.geolocation?(navigator.geolocation.getCurrentPosition(function(e){$.post("/_geolocation",{lon:e.coords.longitude,lat:e.coords.latitude},t).fail(function(e,t,n){o(),r("Netzwerkfehler: ",t,n)}),$("div.candidatestatus").text("Suche Stationen…")},function(e){o(),e.code==e.PERMISSION_DENIED?r("Standortanfrage nicht möglich.","Vermutlich fehlen die Rechte im Browser oder der Android Location Service ist deaktiviert.","geolocation.error.PERMISSION_DENIED"):e.code==e.POSITION_UNAVAILABLE?r("Standort konnte nicht ermittelt werden","(Service nicht verfügbar)","geolocation.error.POSITION_UNAVAILABLE"):e.code==e.TIMEOUT?r("Standort konnte nicht ermittelt werden","(Timeout)","geolocation.error.TIMEOUT"):r("Standort konnte nicht ermittelt werden","(unbekannter Fehler)","unknown geolocation.error code")}),$("div.candidatestatus").text("Position wird bestimmt…")):(o(),r("Standortanfragen werden von diesem Browser nicht unterstützt","",null))});
+$(function(){function n(e){a(),e.error?r("Backend-Fehler:",e.error,null):0==e.candidates.length?r("Keine Stationen in 70km Umkreis gefunden","",null):$.each(e.candidates,function(e,t){var n=t.eva,a=t.name,r=t.distance.toFixed(1),t=t.hafas,o=$(document.createElement("a")),n=(t?o.attr("href",n+"?hafas="+t):o.attr("href",n),o.text(a+" "),$(document.createElement("div"))),a=(n.attr("class","distance"),n.text(r),$(document.createElement("i")));a.attr("class","material-icons"),a.text(t?"directions":"train"),o.append(a),o.append(n),$("div.candidatelist").append(o)})}const a=function(){$("div.candidatestatus").remove()},r=function(e,t,n){var a=$(document.createElement("div")),t=(a.attr("class","error"),a.text(t),$(document.createElement("strong")));t.text(e),a.prepend(t),n&&((e=$(document.createElement("div"))).attr("class","errcode"),e.text(n),a.append(e)),$("div.candidatelist").append(a)};navigator.geolocation?(navigator.geolocation.getCurrentPosition(function(e){var t=new URLSearchParams(window.location.search);$.post("/_geolocation",{lon:e.coords.longitude,lat:e.coords.latitude,hafas:t.get("hafas")},n).fail(function(e,t,n){a(),r("Netzwerkfehler: ",t,n)}),$("div.candidatestatus").text("Suche Stationen…")},function(e){a(),e.code==e.PERMISSION_DENIED?r("Standortanfrage nicht möglich.","Vermutlich fehlen die Rechte im Browser oder der Android Location Service ist deaktiviert.","geolocation.error.PERMISSION_DENIED"):e.code==e.POSITION_UNAVAILABLE?r("Standort konnte nicht ermittelt werden","(Service nicht verfügbar)","geolocation.error.POSITION_UNAVAILABLE"):e.code==e.TIMEOUT?r("Standort konnte nicht ermittelt werden","(Timeout)","geolocation.error.TIMEOUT"):r("Standort konnte nicht ermittelt werden","(unbekannter Fehler)","unknown geolocation.error code")}),$("div.candidatestatus").text("Position wird bestimmt…")):(a(),r("Standortanfragen werden von diesem Browser nicht unterstützt","",null))});
diff --git a/public/static/js/map-refresh.js b/public/static/js/map-refresh.js
index aa5629b..0389323 100644
--- a/public/static/js/map-refresh.js
+++ b/public/static/js/map-refresh.js
@@ -68,7 +68,8 @@ function dbf_anim_fine() {
}
function dbf_map_reload() {
- $.get('/_ajax_mapinfo/' + j_reqid, function(data) {
+ const param = new URLSearchParams(window.location.search);
+ $.get('/_ajax_mapinfo/' + j_reqid + '?hafas=' + param.get('hafas'), function(data) {
$('#infobox').html(data);
dbf_map_parse();
setTimeout(dbf_map_reload, 61000);
diff --git a/public/static/js/map-refresh.min.js b/public/static/js/map-refresh.min.js
index 215074b..b988098 100644
--- a/public/static/js/map-refresh.min.js
+++ b/public/static/js/map-refresh.min.js
@@ -1 +1 @@
-var j_reqid,j_positions=[],j_frame=[],j_frame_i=[];function dbf_map_parse(){$("#jdata").each(function(){j_reqid=$(this).data("req");var a=$(this).data("poly");if(a)for(var e in a=a.split("|"),j_positions=[],a){e=a[e].split(";");e[0]=parseFloat(e[0]),e[1]=parseFloat(e[1]),j_positions.push(e)}})}function dbf_anim_coarse(){if(j_positions.length){var a=marker.getLatLng(),e=a.lat,i=a.lng,a=j_positions.shift(),_=a[0],t=a[1];j_frame_i=200,j_frame=[];for(var f=1;f<=60;f++){var r=f/60;j_frame.push([e+(_-e)*r,i+(t-i)*r])}j_frame_i=0}}function dbf_anim_fine(){j_frame[j_frame_i]&&marker.setLatLng(j_frame[j_frame_i++])}function dbf_map_reload(){$.get("/_ajax_mapinfo/"+j_reqid,function(a){$("#infobox").html(a),dbf_map_parse(),setTimeout(dbf_map_reload,61e3)}).fail(function(){setTimeout(dbf_map_reload,5e3)})}$(document).ready(function(){$("#infobox").length&&(dbf_map_parse(),setInterval(dbf_anim_coarse,2e3),setInterval(dbf_anim_fine,33),setTimeout(dbf_map_reload,61e3))});
+var j_reqid,j_positions=[],j_frame=[],j_frame_i=[];function dbf_map_parse(){$("#jdata").each(function(){j_reqid=$(this).data("req");var a=$(this).data("poly");if(a)for(var e in a=a.split("|"),j_positions=[],a){e=a[e].split(";");e[0]=parseFloat(e[0]),e[1]=parseFloat(e[1]),j_positions.push(e)}})}function dbf_anim_coarse(){if(j_positions.length){var a=marker.getLatLng(),e=a.lat,i=a.lng,a=j_positions.shift(),_=a[0],t=a[1];j_frame_i=200,j_frame=[];for(var r=1;r<=60;r++){var f=r/60;j_frame.push([e+(_-e)*f,i+(t-i)*f])}j_frame_i=0}}function dbf_anim_fine(){j_frame[j_frame_i]&&marker.setLatLng(j_frame[j_frame_i++])}function dbf_map_reload(){var a=new URLSearchParams(window.location.search);$.get("/_ajax_mapinfo/"+j_reqid+"?hafas="+a.get("hafas"),function(a){$("#infobox").html(a),dbf_map_parse(),setTimeout(dbf_map_reload,61e3)}).fail(function(){setTimeout(dbf_map_reload,5e3)})}$(document).ready(function(){$("#infobox").length&&(dbf_map_parse(),setInterval(dbf_anim_coarse,2e3),setInterval(dbf_anim_fine,33),setTimeout(dbf_map_reload,61e3))});
diff --git a/public/static/v88 b/public/static/v98
index 945c9b4..945c9b4 120000
--- a/public/static/v88
+++ b/public/static/v98
diff --git a/public/static/v89 b/public/static/v99
index 945c9b4..945c9b4 120000
--- a/public/static/v89
+++ b/public/static/v99
diff --git a/sass/app.scss b/sass/app.scss
index f72203e..fb81921 100644
--- a/sass/app.scss
+++ b/sass/app.scss
@@ -143,6 +143,9 @@ div.content {
background-color: $powercar-wagon-color;
}
+ .closed {
+ background-color: $closed-wagon-color;
+ }
.nondestwagon {
border-style: dashed;
@@ -155,13 +158,29 @@ div.content {
.type {
display: inline-block;
width: 5em;
- color: $fg2;
+ color: $fg;
}
a.type {
color: $link-color;
}
+ .groupno {
+ color: $fg;
+ }
+
+ .grouptype {
+ color: $fg2;
+ }
+
+ .grouptype:before {
+ content: "(";
+ }
+
+ .grouptype:after {
+ content: ")";
+ }
+
.uicunknown {
color: $fg3;
}
@@ -192,7 +211,7 @@ div.content {
font-weight: bold;
}
- .uic78::before {
+ .uic78:before {
content: "-";
}
@@ -210,7 +229,7 @@ div.content {
color: $fg3;
}
- .uiccheck::before {
+ .uiccheck:before {
content: "-";
}
}
@@ -539,6 +558,14 @@ div.app {
a {
color: $fg;
}
+
+ .otherno {
+ color: $fg2;
+ }
+
+ .meta {
+ color: $fg1;
+ }
}
.departure {
@@ -615,6 +642,45 @@ div.app {
list-style-type: circle;
}
+ .time-early {
+ color: $early-stop-color;
+ }
+
+ .time-delayed {
+ color: $delayed-stop-color;
+ }
+
+ .time-sched-only {
+ color: $delayed-stop-color;
+ }
+
+ .time-sched-ontime {
+ color: $early-stop-color;
+ }
+
+
+ .annotation {
+ color: $fg2;
+ list-style-type: none;
+ padding-left: 3em;
+ }
+
+ .-sched:before {
+ content: " ";
+ }
+
+ .time-sched:after {
+ content: " ";
+ }
+
+ .time-sched-only:before {
+ content: "(";
+ }
+
+ .time-sched-only:after {
+ content: ")";
+ }
+
i.material-icons {
font-size: 14px;
}
diff --git a/sass/dark.scss b/sass/dark.scss
index 72a6927..c0e8d2b 100644
--- a/sass/dark.scss
+++ b/sass/dark.scss
@@ -39,11 +39,15 @@ $undelaynorm-color: #99dd99;
$additional-stop-color: #77ff77;
$cancelled-stop-color: #ff7777;
+$early-stop-color: #ccffcc;
+$delayed-stop-color: #ff9999;
+
$cancelled-bg-color: #512f00;
$past-bg-color: $bg05;
$firstclass-wagon-color: #333300;
$powercar-wagon-color: #222222;
+$closed-wagon-color: #222222;
$button-hover: #111111;
$button-hover-border: #333333;
diff --git a/sass/light.scss b/sass/light.scss
index 809c8ce..1dee6a9 100644
--- a/sass/light.scss
+++ b/sass/light.scss
@@ -39,11 +39,15 @@ $undelaynorm-color: #338833;
$additional-stop-color: #009900;
$cancelled-stop-color: #cc0000;
+$early-stop-color: #007700;
+$delayed-stop-color: #990000;
+
$cancelled-bg-color: #ffe7d0;
$past-bg-color: $bg05;
$firstclass-wagon-color: #ffff99;
$powercar-wagon-color: #cccccc;
+$closed-wagon-color: #dddddd;
$button-hover: #e6e6e6;
$button-hover-border: #adadad;
diff --git a/templates/_map_infobox.html.ep b/templates/_map_infobox.html.ep
index c8936ae..7372802 100644
--- a/templates/_map_infobox.html.ep
+++ b/templates/_map_infobox.html.ep
@@ -1,43 +1,43 @@
<div class="container" id="infobox" style="margin-top: 1ex; margin-bottom: 1ex;">
<div class="journey" id="jdata"
-data-req="<%= stash('ajax_req') %>"
+data-req="<%= stash('ajax_req') =~ s{#}{%23}gr %>"
data-route="<%= stash('ajax_route') %>"
data-poly="<%= stash('ajax_polyline') %>"
>
- %= l 'Fahrt'
+ Fahrt
% if (stash('train_no')) {
<strong><%= stash('train_no') %></strong>
% }
- <%= l 'von' %> <strong><%= stash('origin')->{name} %></strong>
- <%= l 'nach' %> <strong><%= stash('destination')->{name} %></strong>
+ von <strong><%= stash('origin')->{name} %></strong>
+ nach <strong><%= stash('destination')->{name} %></strong>
</div>
% if (my $next = stash('next_stop')) {
<div class="nextstop">
% if ($next->{type} eq 'present' and $next->{station}{dep} and $next->{station}{arr}) {
- <%= l 'Aufenthalt in' %> <strong><%= $next->{station}->loc->name %></strong>
+ Aufenthalt in <strong><%= $next->{station}->loc->name %></strong>
% if ($next->{station}{platform}) {
- <%= l 'an Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ an Gleis <strong><%= $next->{station}{platform} %></strong>
% }
- <%= l 'bis' %> <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ bis <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
% if ($next->{station}{dep_delay}) {
%= sprintf('(%+d)', $next->{station}{dep_delay})
% }
% }
% elsif ($next->{type} eq 'present' and $next->{station}{dep}) {
- <%= l 'Abfahrt in' %> <strong><%= $next->{station}->loc->name %></strong>
+ Abfahrt in <strong><%= $next->{station}->loc->name %></strong>
% if ($next->{station}{platform}) {
- <%= l 'von Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ von Gleis <strong><%= $next->{station}{platform} %></strong>
% }
- <%= l 'um' %> <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ um <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
% if ($next->{station}{dep_delay}) {
%= sprintf('(%+d)', $next->{station}{dep_delay})
% }
% }
% elsif ($next->{type} eq 'present' and $next->{station}{arr}) {
- %= l 'Endstation erreicht um'
+ Endstation erreicht um
<strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
% if ($next->{station}{platform}) {
- <%= l 'auf Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
% }
% if ($next->{station}{arr_delay}) {
%= sprintf('(%+d)', $next->{station}{arr_delay})
@@ -47,25 +47,25 @@ data-poly="<%= stash('ajax_polyline') %>"
Zug steht in
<strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
% if ($next->{station}{platform}) {
- <%= l 'auf Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
% }
% }
% elsif ($next->{type} eq 'next' and $next->{station}{arr}) {
- %= l 'Nächster Halt:'
+ Nächster Halt:
<strong><%= $next->{station}->loc->name %></strong>
- <%= l 'um' %> <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ um <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
% if ($next->{station}{arr_delay}) {
%= sprintf('(%+d)', $next->{station}{arr_delay})
% }
% if ($next->{station}{platform}) {
- <%= l 'auf Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
% }
% }
% elsif ($next->{type} eq 'next') {
- %= l 'Nächster Halt:'
+ Nächster Halt:
<strong><%= $next->{station}->loc->name %></strong>
% if ($next->{station}{platform}) {
- <%= l 'auf Gleis' %> <strong><%= $next->{station}{platform} %></strong>
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
% }
% }
</div>
diff --git a/templates/_train_details.html.ep b/templates/_train_details.html.ep
index 932c355..f560950 100644
--- a/templates/_train_details.html.ep
+++ b/templates/_train_details.html.ep
@@ -2,9 +2,6 @@
<div>
% if ($departure->{train_no} or $departure->{train_line}) {
<span class="train-line <%= $linetype %>"><%= $departure->{train_type} %>
-% if ($linetype eq 'fern' and $icetype and $icetype->[1]) {
- <span class="trainsubtype" aria-hidden="true"><%= $icetype->[1] %></span>
-% }
<%= $departure->{train_line} // $departure->{train_no} %></span>
<span class="train-no"><%= $departure->{train_line} ? $departure->{train_no} : q{} %></span>
@@ -23,31 +20,34 @@
<div>
<div class="arrival <%= $departure->{arrival_hidden} ? 'timehidden' : q{} %>">
% if ($departure->{is_cancelled} and $departure->{sched_arrival}) {
- <span class="minfo"><%= l 'An:' %> ––:––</span><br/><%= l 'Plan:' %> <%= $departure->{sched_arrival} %>
+ <span class="minfo">An: ––:––</span><br/>Plan: <%= $departure->{sched_arrival} %>
% }
% elsif ($departure->{arrival_is_cancelled}) {
- <span class="minfo">Beginnt hier</span><br/><%= l 'Plan:' %> <%= $departure->{sched_arrival} %>
+ <span class="minfo">Beginnt hier</span><br/>Plan: <%= $departure->{sched_arrival} %>
% }
% elsif ($departure->{arrival}) {
% if ($departure->{arrival} ne $departure->{sched_arrival}) {
- % if (($departure->{delay} // 0) < 0) {
- <%= l 'An:' %> <span class="undelay"><%= $departure->{arrival} %></span>
+ % if (($departure->{arrival_delay} // 0) < 0) {
+ An: <span class="undelay"><%= $departure->{arrival} %></span>
% }
% else {
- <%= l 'An:' %> <span class="minfo"><%= $departure->{arrival} %></span>
+ An: <span class="minfo"><%= $departure->{arrival} %></span>
% }
- <br/><%= l 'Plan:' %> <%= $departure->{sched_arrival} %>
+ <br/>Plan: <%= $departure->{sched_arrival} %>
% }
% else {
- <%= l 'An:' %> <%= $departure->{arrival} %>
+ An: <%= $departure->{arrival} %>
% }
% }
% elsif ($departure->{sched_arrival}) {
- <%= l 'An:' %> <%= $departure->{sched_arrival} %>
+ An: <%= $departure->{sched_arrival} %>
% }
% elsif ($departure->{prep_time}) {
Ein: <%= $departure->{prep_time} %>
% }
+% if ($departure->{tz_offset} and $departure->{local_sched_arr}) {
+ <br/>Lokal: <%= $departure->{local_sched_arr}->strftime('%H:%M') %>
+% }
</div>
</div>
<div>
@@ -68,18 +68,18 @@
% else {
% my $left = '';
% my $right = '';
-% if ($departure->{direction} and $departure->{direction} eq 'l') {
+% if ($departure->{wr_direction} and $departure->{wr_direction} eq 'l') {
% $left = '◀ ';
% }
-% elsif ($departure->{direction} and $departure->{direction} eq 'r') {
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} eq 'r') {
% $right = ' ▶';
% }
% if ($departure->{scheduled_platform} and $departure->{platform}
% and $departure->{scheduled_platform} ne $departure->{platform}) {
- <span class="minfo"><%= $left %><%= l 'Gleis' %> <%= $departure->{platform} %><%= $right %></span>
+ <span class="minfo"><%= $left %>Gleis <%= $departure->{platform} %><%= $right %></span>
% }
% elsif ($departure->{scheduled_platform} or $departure->{platform}) {
- <%= $left %><%= l 'Gleis' %> <%= $departure->{platform} // $departure->{scheduled_platform} %><%= $right %>
+ <%= $left %>Gleis <%= $departure->{platform} // $departure->{scheduled_platform} %><%= $right %>
% }
% }
% if ($departure->{arrival_hidden} and not $departure->{prep_time}) {
@@ -93,71 +93,68 @@
<div>
<div class="departure <%= $departure->{departure_hidden} ? 'timehidden' : q{} %>">
% if ($departure->{is_cancelled} and $departure->{sched_departure}) {
- <span class="minfo"><%= l 'Ab:' %> ––:––</span><br/><%= l 'Plan:' %> <%= $departure->{sched_departure} %>
+ <span class="minfo">Ab: ––:––</span><br/>Plan: <%= $departure->{sched_departure} %>
% }
% elsif ($departure->{departure_is_cancelled}) {
- <span class="minfo">Endet hier</span><br/><%= l 'Plan:' %> <%= $departure->{sched_departure} %>
+ <span class="minfo">Endet hier</span><br/>Plan: <%= $departure->{sched_departure} %>
% }
% elsif ($departure->{departure}) {
% if ($departure->{departure} ne $departure->{sched_departure}) {
- <%= l 'Ab:' %> <span class="minfo"><%= $departure->{departure} %></span>
- <br/><%= l 'Plan:' %> <%= $departure->{sched_departure} %>
+ Ab: <span class="minfo"><%= $departure->{departure} %></span>
+ <br/>Plan: <%= $departure->{sched_departure} %>
% }
% else {
- <%= l 'Ab:' %> <%= $departure->{departure} %>
+ Ab: <%= $departure->{departure} %>
% }
% }
% elsif ($departure->{sched_departure}) {
- <%= l 'Ab:' %> <%= $departure->{sched_departure} %>
+ Ab: <%= $departure->{sched_departure} %>
+% }
+% if ($departure->{tz_offset} and $departure->{local_sched_dep}) {
+ <br/>Lokal: <%= $departure->{local_sched_dep}->strftime('%H:%M') %>
% }
</div>
</div>
</div> <!-- dataline -->
% if (my $wr = $departure->{wr}) {
<div class="wagonorder-preview">
-% my @wagons = $wr->wagons;
-% my $direction = $wr->direction ? $wr->direction == 100 ? '→' : '←' : q{};
-% if ($departure->{direction}) {
-% $direction = $departure->{direction} eq 'l' ? '◀' : '▶';
-% if (($departure->{direction} eq 'l' ? 0 : 100) != $wr->direction) {
-% @wagons = reverse @wagons;
-% }
-% }
- <a href="/_wr/<%= $departure->{train_no} %>/<%= $departure->{wr_link} %>?e=<%= $departure->{direction} // '' %>">
- %= $direction
-% my $gi;
-% for my $wagon (@wagons) {
-% if (not ($wagon->is_locomotive or $wagon->is_powercar)) {
-% if (defined $gi and $gi != $wagon->group_index) {
- •
-% }
-%= $wagon->number || ($wagon->type =~ m{AB} ? '½' : $wagon->type =~ m{A} ? '1.' : $wagon->type =~ m{B} ? '2.' : '?' )
-% }
-% $gi = $wagon->group_index;
-% }
- %= $direction
+% my $left = defined $wr->direction ? $wr->direction == 100 ? q{} : '←' : q{};
+% my $right = defined $wr->direction ? $wr->direction == 100 ? '→' : q{} : q{};
+% if ($departure->{wr_direction} and $departure->{wr_direction} eq 'l') {
+% $left = '◀';
+% $right = q{};
+% }
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} eq 'r') {
+% $left = q{};
+% $right = '▶';
+% }
+ <a href="/_wr/<%= $departure->{train_no} %>/<%= $departure->{wr_link} %>?e=<%= $departure->{wr_direction} // '' %>">
+ %= $left
+ % for my $entry ((defined $departure->{wr_direction_num} and $departure->{wr_direction_num} != $wr->direction) ? reverse @{$departure->{wr_preview} // []} : @{$departure->{wr_preview} // []}) {
+ % if ($entry->[1]) {
+ <span class="<%= $entry->[1] %>"><%= $entry->[0] %></span>
+ % }
+ % else {
+ %= $entry->[0]
+ % }
+ % }
+ %= $right
</a>
</div>
% }
<div class="verbose">
% if ($departure->{trip_id}) {
% if (stash('station_name')) {
- <a class="smallbutton" href="/map/<%= $departure->{trip_id} %>/<%= $departure->{train_line} // 0 %>?from=<%= stash('station_name') %>"><i class="material-icons" aria-hidden="true">map</i> <%= l 'Karte' %></a>
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?from=<%= stash('station_name') %>&amp;hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
% }
% else {
- <a class="smallbutton" href="/map/<%= $departure->{trip_id} %>/<%= $departure->{train_line} // 0 %>"><i class="material-icons" aria-hidden="true">map</i> <%= l 'Karte' %></a>
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
% }
% }
% if ($departure->{wr_link}) {
- <a class="smallbutton" href="/_wr/<%= $departure->{train_no} %>/<%= $departure->{wr_link} %>?e=<%= $departure->{direction} // '' %>"><i class="material-icons" aria-hidden="true">train</i> <%= $departure->{wr_text} || 'Wagen' %>
+ <a class="smallbutton" href="/_wr/<%= $departure->{train_no} %>/<%= $departure->{wr_link} %>?e=<%= $departure->{wr_direction} // '' %>"><i class="material-icons" aria-hidden="true">train</i> <%= $departure->{wr_text} || 'Wagen' %>
</a>
% }
-% elsif ($icetype and $icetype->[2] and ($linetype eq 'fern' or $departure->{train_type} =~ m{NJ})) {
- <a class="smallbutton" href="/wr/<%= $departure->{train_no} %>"><i class="material-icons" aria-hidden="true">train</i> Plan: <%= $icetype->[0] %></a>
-% }
-% elsif ($icetype and $icetype->[1] and $linetype eq 'fern') {
- <span class="disabledbutton"><i class="material-icons" aria-hidden="true">train</i> Plan: <%= $icetype->[0] %></span>
-% }
% if ($departure->{train_type} and $departure->{train_no}) {
<a class="smallbutton" href="https://bahn.expert/details/<%= $departure->{train_type} %>%20<%= $departure->{train_no} %>/<%= ($departure->{date} // DateTime->now(time_zone => 'Europe/Berlin'))->iso8601 %>?evaNumberAlongRoute=<%= $departure->{eva} %>"><img src="/static/icons/bahn-expert.svg">Details</a>
% }
@@ -170,13 +167,13 @@
% if (my $u = $departure->{utilization}) {
<div class="verbose">
% my ($text, $icon1, $icon2) = utilization_icon($u);
- <%= l $text %><span style="padding-right: 0.5em;">.</span> 1. <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon1 %></i> 2. <i class="material-icons" aria-hidden="true" style="vertical-align: bottom;"><%= $icon2 %></i>
+ <%= $text %><span style="padding-right: 0.5em;">.</span> 1. <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon1 %></i> 2. <i class="material-icons" aria-hidden="true" style="vertical-align: bottom;"><%= $icon2 %></i>
</div>
% }
% elsif (my $o = $departure->{occupancy}) {
<div class="verbose">
% my ($text, $icon) = occupancy_icon($o);
- <%= l $text %><span style="padding-right: 0.5em;">.</span> <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon %></i></i>
+ <%= $text %><span style="padding-right: 0.5em;">.</span> <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon %></i></i>
</div>
% }
% }
@@ -193,7 +190,7 @@
% }
% if ($departure->{moreinfo} and @{$departure->{moreinfo}}) {
-%= l 'Meldungen'
+ Meldungen
<ul class="messages">
% for my $pair (@{$departure->{moreinfo}}) {
<li>
@@ -230,7 +227,7 @@
% }
% if ($departure->{route_pre_diff} and $departure->{route_post_diff}) {
% if ($departure->{date}) {
-%= l 'Fahrtverlauf am'
+ Fahrtverlauf am
% if (stash('train') !~ m{[|]}) {
<a href="<%= url_for('train', train => stash('train'))->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas'), date => $departure->{date}->clone->subtract(days => 1)->strftime('%d.%m.%Y'), highlight => param('highlight') // stash('station')}) %>">◀</a>
% }
@@ -241,6 +238,19 @@
% }
<ul class="mroute">
% for my $stop (@{$departure->{route_pre_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
+ </li>
+% }
<li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
<a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas')}) %>#<%= ($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x}) %>" class="
% if ($stop->{isAdditional}) {
@@ -256,13 +266,16 @@
generic-stop
% }
% if (($stop->{rt_dep} and $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and $stop->{arr_delay})) {
- "><%= ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') %> (heute <%= ($stop->{rt_dep} // $stop->{rt_arr})->strftime('%H:%M') %>)
+ "><span class="time-sched-only"><%= ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_dep} // $stop->{rt_arr})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_dep} and defined $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and defined $stop->{arr_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
% }
% else {
- "><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %>
-% if ($stop->{rt_bogus}) {
- <i class="material-icons" aria-label="Echtzeitdaten fehlen">gps_off</i>
-% }
+ "><span class="time-sched"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_da}) {
+ (lokal <%= $stop->{local_dt_da}->strftime('%H:%M') %>)
% }
<%= $stop->{name} %></a>
% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
@@ -273,15 +286,39 @@
</li>
% }
% if (stash('station_name')) {
- <li class="<%= $departure->{is_cancelled} ? 'cancelled-stop' : q{} %> <%= $departure->{isPast} ? 'past-stop' : 'future-stop' %>"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %>
+% if ($departure->{is_annotated} and $departure->{prod_name}) {
+ <li class="annotation">
+% if ($departure->{prod_name}) {
+%= $departure->{prod_name}
+% }
+% if ($departure->{direction}) {
+ → <%= $departure->{direction} %>
+% }
+% if ($departure->{operator}) {
+ (<%= $departure->{operator} %>)
+% }
+ </li>
+% }
+ <li class="<%= $departure->{is_cancelled} ? 'cancelled-stop' : q{} %> <%= $departure->{isPast} ? 'past-stop' : 'future-stop' %>">
% if ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} ne $departure->{sched_departure}) {
- (heute <%= $departure->{departure} %>)
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
+% }
+% elsif ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} eq $departure->{sched_departure} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
% }
% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} ne $departure->{sched_arrival}) {
- (heute <%= $departure->{arrival} %>)
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
% }
-% if ($departure->{missing_realtime} or $departure->{no_realtime_yet}) {
- <i class="material-icons" aria-label="Echtzeitdaten fehlen">gps_off</i>
+% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} eq $departure->{sched_arrival} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
+% }
+% else {
+ <span class="time-sched">
+% }
+%= $departure->{departure} // $departure->{arrival} // $departure->{sched_departure} // $departure->{sched_arrival} // q{}
+ </span>
+% if ($departure->{tz_offset} and $departure->{local_dt_da}) {
+ (lokal <%= $departure->{local_dt_da}->strftime('%H:%M') %>)
% }
<strong><%= stash('station_name') %></strong>
% if (my $u = $departure->{utilization}) {
@@ -292,6 +329,19 @@
</li>
% }
% for my $stop (@{$departure->{route_post_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
+ </li>
+% }
<li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
<a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas')}) %>#<%= ($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x}) %>" class="
% if ($stop->{isAdditional}) {
@@ -307,13 +357,16 @@
generic-stop
% }
% if (($stop->{rt_arr} and $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and $stop->{dep_delay})) {
- "><%= ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') %> (heute <%= ($stop->{rt_arr} // $stop->{rt_dep})->strftime('%H:%M') %>)
+ "><span class="time-sched-only"><%= ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_arr} // $stop->{rt_dep})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_arr} and defined $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and defined $stop->{dep_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
% }
% else {
- "><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %>
-% if ($stop->{rt_bogus}) {
- <i class="material-icons" aria-label="Echtzeitdaten fehlen">gps_off</i>
-% }
+ "><span class="time-sched"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_ad}) {
+ (lokal <%= $stop->{local_dt_ad}->strftime('%H:%M') %>)
% }
<%= $stop->{name} %></a>
% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
@@ -325,8 +378,8 @@
% }
</ul> <!-- mroute -->
% }
-% if ($departure->{operator}) {
- <div class="details"><%= l 'Betrieb' %>: <%= $departure->{operator} %></div>
+% if ($departure->{operators} and @{$departure->{operators} // []}) {
+ <div class="details">Betrieb: <%= join(q{, }, @{ $departure->{operators} // [] } ) %></div>
% }
% if ($departure->{details} and @{$departure->{details}}) {
<div class="details">Details:
@@ -351,71 +404,4 @@
</ul>
</div>
% }
-% if ($details->{attributes}) {
-% if (@{$details->{attributes}} > 1) {
- <div class="db-attr">
- Attribute:
- <ul>
-% for my $attr (@{$details->{attributes}}) {
- <li><%= include '_train_attr', attr => $attr, with_station => 1 %></li>
-% }
- </ul>
- </div>
-% }
-% else {
- <div class="db-attr">
-%= include '_train_attr', attr => $details->{attributes}[0], with_station => 0
- </div>
-% }
-% }
-% if ($details and not $departure->{arrival}) {
-% if (my $s = $details->{route}{preStart}) {
- Zug wird voraussichtlich aus <%= $s %> eingesetzt.<br/><br/>
-% }
-% if (@{$departure->{cycle_from} // []}) {
- Bildung möglicherweise aus
- <ul>
-% for my $t (@{$departure->{cycle_from}}) {
-% my ($train_no, $train) = @{$t};
-% my $tt = $train->{type} // $train->{rawType} // 'Zug';
-% $tt =~ s{ .*|[0-9]}{};
-% if ($tt ne 'Zug') {
- <li><a href="<%= url_for('train', train => "$tt $train_no")->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas')}) %>"><%= $tt %> <%= $train_no %></a>
-% }
-% else {
- <li><%= $tt %> <%= $train_no %>
-% }
-% if ($train->{route}{start} and $train->{route}{end}) {
- <%= $train->{route}{start} %> → <%= $train->{route}{end} %>
-% }
- </li>
-% }
- </ul>
-% }
-% }
-% if ($details and not $departure->{departure}) {
-% if (my $e = $details->{route}{postEnd}) {
- Zug wird voraussichtlich in <%= $e %> abgestellt.<br/><br/>
-% }
-% if (@{$departure->{cycle_to} // []}) {
- Weiterfahrt möglicherweise als
- <ul>
-% for my $t (@{$departure->{cycle_to}}) {
-% my ($train_no, $train) = @{$t};
-% my $tt = $train->{type} // $train->{rawType} // 'Zug';
-% $tt =~ s{ .*|[0-9]}{};
-% if ($tt ne 'Zug') {
- <li><a href="<%= url_for('train', train => "$tt $train_no")->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas')}) %>"><%= $tt %> <%= $train_no %></a>
-% }
-% else {
- <li><%= $tt %> <%= $train_no %>
-% }
-% if ($train->{route}{start} and $train->{route}{end}) {
- <%= $train->{route}{start} %> → <%= $train->{route}{end} %>
-% }
- </li>
-% }
- </ul>
-% }
-% }
</div> <!-- mfooter -->
diff --git a/templates/_wagon.html.ep b/templates/_wagon.html.ep
index 94ef56a..59a2ca1 100644
--- a/templates/_wagon.html.ep
+++ b/templates/_wagon.html.ep
@@ -6,6 +6,9 @@
% if ($wagon->is_locomotive or $wagon->is_powercar) {
% $extra_class .= ' powercar';
% }
+% if ($wagon->is_closed) {
+% $extra_class .= ' closed';
+% }
% if ($wagon->train_no ne $train_no) {
% $extra_class .= ' nondestwagon';
% }
@@ -13,6 +16,9 @@
top: <%= $wagon->{position}{start_percent} %>%; bottom: <%= 100 - $wagon->{position}{end_percent} %>%; <%= $bg %>">
% if ($wagon->is_locomotive or $wagon->is_powercar) {
% }
+% elsif ($wagon->is_closed) {
+ X
+% }
% else {
%= $wagon->number // '?'
% if ($wagon->has_accessibility) {
@@ -38,9 +44,9 @@
% }
% }
<div class="direction">
-% if (not defined $direction) {
+% if (not defined $wr->direction) {
% }
-% elsif ($direction == 100) {
+% elsif ($wr->direction == 100) {
<i class="material-icons">arrow_downward</i>
% }
% else {
@@ -51,7 +57,7 @@
<div class="details" style="
top: <%= $wagon->{position}{start_percent} %>%; bottom: <%= 100 - $wagon->{position}{end_percent} %>%;">
% if ($exit_dir ne 'right') {
-% if (my $img = wagon_image($wagon->train_subtype // $type // '?', $wagon->type, $wagon->uic_id)) {
+% if (my $img = wagon_image($wagon->train_subtype // $wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
<a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
% }
% else {
@@ -71,7 +77,7 @@
<span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic56"><%= substr($uic_id, 4, 2) %></span><span class="uic78"><%= substr($uic_id, 6, 2) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
% }
% if ($exit_dir eq 'right') {
-% if (my $img = wagon_image($wagon->train_subtype // $type // '?', $wagon->type, $wagon->uic_id)) {
+% if (my $img = wagon_image($wagon->train_subtype // $wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
<a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
% }
% else {
@@ -80,4 +86,18 @@
</span>
% }
% }
+% if ($multi and $first) {
+ <br/>
+ <span class="groupno">
+% if (scalar $wr->train_nos > 1) {
+ <%= $wr->train_type %> <%= ($wr->groups)[$wagon->group_index]->train_no %>
+% }
+% if (scalar $wr->destinations > 1) {
+ → <%= $wr->{data}{istformation}{allFahrzeuggruppe}[$wagon->group_index]{zielbetriebsstellename} %>
+% }
+ </span>
+ % if ($multi and ($wr->groups)[$wagon->group_index]->desc_short) {
+ <span class="grouptype"><%= ($wr->groups)[$wagon->group_index]->desc_short %></span>
+% }
+% }
</div>
diff --git a/templates/about.html.ep b/templates/about.html.ep
index f299389..b5af92b 100644
--- a/templates/about.html.ep
+++ b/templates/about.html.ep
@@ -4,7 +4,7 @@
Der Fokus liegt auf Zügen im Netz der Deutschen Bahn; eingeschränkte Unterstützung für Nahverkehr und Züge in anderen Netzen lässt sich optional zuschalten.
</p>
<p>
- Der <a href="<%= app->config->{'source_url'} %>">Quelltext</a> steht unter der <a href="https://git.finalrewind.org/db-fakedisplay/tree/COPYING">GNU AGPL v3</a> als Open Source zur Verfügung. © 2011 – 2023 <a href="https://finalrewind.org">derf</a>.
+ Der <a href="<%= app->config->{'source_url'} %>">Quelltext</a> steht unter der <a href="https://git.finalrewind.org/db-fakedisplay/tree/COPYING">GNU AGPL v3</a> als Open Source zur Verfügung. © 2011 – 2024 <a href="https://finalrewind.org">derf</a>.
% if (my $issue_url = app->config->{'issue_url'}) {
Fehlermeldungen bitte via
<a href="<%= $issue_url %>">Issue Tracker</a>.
@@ -17,32 +17,37 @@
und die Bezeichnung DBF wurde zum Eigennamen ohne weitere Bedeutung.
</p>
<p>
- Diese Installation verwendet die DBF-Version
- <b><%= stash('version') // '???' %></b> und greift auf die folgenden Backends
- zu:<br/>
- • Regional- und Fernverkehr: DB IRIS via <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
- v<%= $Travel::Status::DE::IRIS::VERSION %><br/>
- • Nahverkehr und Zugdetails: DB HAFAS via <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
- % if ($Travel::Status::DE::HAFAS::VERSION) {
- v<%= $Travel::Status::DE::HAFAS::VERSION %>
- % }
- <br/>
- • Wagenreihung: <a href="https://finalrewind.org/projects/Travel-Status-DE-DBWagenreihung/">Travel::Status::DE::DBWagenreihung</a>
- % if ($Travel::Status::DE::DBWagenreihung::VERSION) {
- v<%= $Travel::Status::DE::DBWagenreihung::VERSION %>
+ Diese Installation nutzt
+ <strong>DBF v<%= stash('version') // '???' %></strong> mit folgenden Backends:
+ <ul>
+ <li> Innerdeutscher Regional- und Fernverkehr: DB IRIS via <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
+ <strong>v<%= $Travel::Status::DE::IRIS::VERSION %></strong></li>
+ <li> Außerdeutsche Fahrten, Nahverkehr, Details, Karten: HAFAS via <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
+ <strong>v<%= $Travel::Status::DE::HAFAS::VERSION %></strong></li>
+ <li>Wagenreihung: <a href="https://finalrewind.org/projects/Travel-Status-DE-DBWagenreihung/">Travel::Status::DE::DBWagenreihung</a>
+ <strong>v<%= $Travel::Status::DE::DBWagenreihung::VERSION %></strong></li>
+ <li>Zugauslastung Regionalverkehr: VRR EFA via <a href="https://github.com/derf/eva-to-efa-gw">eva-to-efa-gw</a></li>
+ </ul>
+ </p>
+ <p>
+ Unterstützte HAFAS-Instanzen („hafas=…“):
+ <ul>
+ % for my $service (Travel::Status::DE::HAFAS::get_services()) {
+ <li><%= $service->{shortname} %> (<%= $service->{name} %>)</li>
% }
- <br/>
- • Zugauslastung Regionalverkehr: VRR EFA via <a href="https://github.com/derf/eva-to-efa-gw">eva-to-efa-gw</a><br/>
- <br/>
- Sie nutzt zusätzlich die folgenden Open Data-Ressourcen:<br/>
- • <a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">Zugbildungsplan</a> © DB Fernverkehr AG, lizensiert unter CC-BY 4.0
- <br/>
- • <a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellenliste</a>
+ </ul>
+ </p>
+ <p>
+ Verwendete Open Data-Ressourcen:
+ <ul>
+ <li><a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">Zugbildungsplan</a> © DB Fernverkehr AG, lizensiert unter CC-BY 4.0</li>
+ <li><a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellenliste</a>
© DB Station&amp;Service AG,
Europaplatz 1,
- 10557 Berlin, lizensiert unter CC-BY 4.0<br/>
- • <a href="https://data.deutschebahn.com/dataset/fahrzeuglexikon">Fahrzeuglexikon</a>
- © DB Fernverkehr AG, lizensiert unter CC-BY 4.0; Abbildungen © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG, lizensiert unter CC-BY-SA 4.0<br/>
+ 10557 Berlin, lizensiert unter CC-BY 4.0</li>
+ <li><a href="https://data.deutschebahn.com/dataset/fahrzeuglexikon">Fahrzeuglexikon</a>
+ © DB Fernverkehr AG, lizensiert unter CC-BY 4.0; Abbildungen © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG, lizensiert unter CC-BY-SA 4.0</li>
+ </ul>
</p>
</div>
diff --git a/templates/app.html.ep b/templates/app.html.ep
index 360a2f0..50f8a5a 100644
--- a/templates/app.html.ep
+++ b/templates/app.html.ep
@@ -31,7 +31,7 @@
% }
<li
% if (param('hafas')) {
- data-jid="<%= $departure->{journey_id} %>"
+ data-jid="<%= $departure->{journey_id} =~ s{#}{%23}gr %>"
% }
data-train="<%= ($departure->{train_type} // q{}) %> <%= ($departure->{train_no} // $departure->{train} // q{}) %>"
data-line="<%= $departure->{train_type} %> <%= $departure->{train_line} // $departure->{train_no} %>"
@@ -67,9 +67,6 @@
%= $departure->{train_line}
% }
% elsif ($departure->{train_no}) {
-% if (param('detailed') and $departure->{linetype} eq 'fern' and exists $ice_type->{$departure->{train_no}} and $ice_type->{$departure->{train_no}}[1]) {
- <span class="trainsubtype" aria-hidden="true"><%= $ice_type->{$departure->{train_no}}[1] %></span>
-% }
<span class="trainno"><%= $departure->{train_no} %></span>
% }
% else {
diff --git a/templates/exception.html.ep b/templates/exception.html.ep
index 65ec7ff..7654c0b 100644
--- a/templates/exception.html.ep
+++ b/templates/exception.html.ep
@@ -5,7 +5,7 @@ Beim Bearbeiten der Anfrage ist ein Fehler aufgetreten.<br/>
<pre>
----------[Debug start]----------
% if ($exception) {
-%= $exception->message
+%= ref($exception) ? $exception->message : $exception
Stash:
%= dumper $snapshot
% }
diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep
index 0a977a8..17bb2bb 100644
--- a/templates/landingpage.html.ep
+++ b/templates/landingpage.html.ep
@@ -1,6 +1,6 @@
% if (stash 'show_intro') {
<div class="container">
-% if (languages() =~ m{^en}) {
+% if (0) {
<p>
DBF is an unofficial departure monitor for regional and long-distance trains within Germany, aiming to combine multiple data sources in a useful manner.
It also has limited support for local transit and traffic outside of Germany.
@@ -21,10 +21,10 @@
</p>
% }
<p class="geolink">
-<a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https') %>"><%= l 'Stationen in der Umgebung suchen' %></a>
+<a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
</p>
<p>
-%= l 'Oder hier angeben:'
+Oder hier angeben:
</p>
</div>
% }
diff --git a/templates/layouts/app.html.ep b/templates/layouts/app.html.ep
index ab4dffa..9c39019 100644
--- a/templates/layouts/app.html.ep
+++ b/templates/layouts/app.html.ep
@@ -18,7 +18,7 @@
<meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
% }
- % my $av = 'v89'; # asset version
+ % my $av = 'v99'; # asset version
% if (session('theme') and session('theme') eq 'dark' or param('dark')) {
%= stylesheet "/static/${av}/css/dark.min.css", id => 'theme'
% }
@@ -102,8 +102,8 @@
</div>
% }
% elsif (stash('stationlist')) {
-<div class="error"><strong><%= l 'Mehrdeutige Eingabe' %>.</strong>
-<%= l 'Bitte eine Station aus der Liste auswählen' %></div>
+<div class="error"><strong>Mehrdeutige Eingabe.</strong>
+Bitte eine Station aus der Liste auswählen</div>
% }
</div>
@@ -117,37 +117,38 @@
%= form_for _redirect => begin
+%= hidden_field hafas => param('hafas')
<div>
<div class="field">
- <div class="desc"><%= l 'Zug / Station' %></div>
+ <div class="desc">Zug / Station</div>
<div>
% if (stash('stationlist')) {
%= select_field input => stash('stationlist')
% }
% elsif (stash('input')) {
- %= text_field 'input', class => 'station', placeholder => l 'Zug, Stationsname oder Ril100-Kürzel', id => 'stationinput'
+ %= text_field 'input', class => 'station', placeholder => 'Zug, Stationsname oder Ril100-Kürzel', id => 'stationinput'
% }
% else {
- %= text_field 'input', class => 'station', placeholder => l 'Zug, Stationsname oder Ril100-Kürzel', id => 'stationinput', autofocus => 'autofocus'
+ %= text_field 'input', class => 'station', placeholder => 'Zug, Stationsname oder Ril100-Kürzel', id => 'stationinput', autofocus => 'autofocus'
% }
</div>
</div>
<div class="field">
- %= submit_button l('Abfahrtstafel')
+ %= submit_button 'Abfahrtstafel'
</div>
% if (stash('input')) {
<div class="geolink">
- <a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https') %>"><%= l 'Stationen in der Umgebung suchen' %></a>
+ <a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
</div>
% }
<div class="break"></div>
- <div class="moresettings-header moresettings-header-collapsed button button-light"><%= l 'Weitere Einstellungen' %></div>
+ <div class="moresettings-header moresettings-header-collapsed button button-light">Weitere Einstellungen</div>
<div class="moresettings moresettings-collapsed">
<div class="field">
<div class="desc">
%= check_box 'rt' => 1, id => 'id_show_realtime'
<label for="id_show_realtime">
- %= l 'Zeiten inkl. Verspätung angeben'
+ Zeiten inkl. Verspätung angeben
</label>
</div>
</div>
@@ -155,7 +156,7 @@
<div class="desc">
%= check_box 'hidelowdelay' => 1, id => 'id_hidelowdelay'
<label for="id_hidelowdelay">
- %= l 'Verspätungen erst ab 5 Minuten anzeigen'
+ Verspätungen erst ab 5 Minuten anzeigen
</label>
</div>
</div>
@@ -163,7 +164,7 @@
<div class="desc">
%= check_box 'detailed' => 1, id => 'id_detailed'
<label for="id_detailed">
- %= l 'Mehr Details'
+ Mehr Details
</label>
</div>
</div>
@@ -171,7 +172,7 @@
<div class="desc">
%= check_box 'no_related' => 1, id => 'id_no_related'
<label for="id_no_related">
- %= l 'Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")'
+ Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")
</label>
</div>
</div>
@@ -179,7 +180,7 @@
<div class="desc">
%= check_box 'past' => 1, id => 'past'
<label for="past">
- %= l 'Bereits abgefahrene Züge anzeigen'
+ Bereits abgefahrene Züge anzeigen
</label>
</div>
</div>
@@ -187,21 +188,21 @@
<div class="desc">
%= check_box 'hide_opts' => 1, id => 'id_hide_opts'
<label for="id_hide_opts">
- %= l 'Formular verstecken'
+ Formular verstecken
</label>
</div>
</div>
<div class="field">
<div class="desc">
- %= l 'Nur Züge über'
+ Nur Züge über
</div>
<div>
- %= text_field 'via', placeholder => l('Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)'), class => 'station'
+ %= text_field 'via', placeholder => 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)', class => 'station'
</div>
</div>
<div class="field">
<div class="desc">
- %= l 'Gleise'
+ Gleise
</div>
<div>
%= text_field 'platforms', placeholder => '1, 2, 5, ...'
@@ -209,10 +210,10 @@
</div>
<div class="field">
<div class="desc">
- %= l 'Ankunfts- oder Abfahrtszeit anzeigen?'
+ Ankunfts- oder Abfahrtszeit anzeigen?
</div>
<div>
- %= select_field admode => [[l('Abfahrt bevorzugen') => 'deparr'], [l('Nur Abfahrt') => 'dep'], [l('Nur Ankunft') => 'arr']]
+ %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
</div>
</div>
<div class="field">
@@ -224,7 +225,7 @@
</div>
</div>
<div class="field">
- %= submit_button l('Anzeigen')
+ %= submit_button 'Anzeigen'
</div>
</div> <!-- moresettings -->
</div>
@@ -236,7 +237,7 @@
<div class="developers-header developers-header-collapsed button button-light">API</div>
<div class="developers developers-collapsed">
<ul>
- % if (languages() =~ m{^en}) {
+ % if (0) {
<li>You're welcome to embed DBF departure boards as iframes or use them
in full-screen browser setups. The App frontend works best for
small screens, whereas the legacy Infoscreen mode is better suited
@@ -300,26 +301,27 @@
<div class="container">
<div class="config">
-Theme:
-<a onClick="javascript:setTheme('dark')">dark</a>
+Farbschema:
<a onClick="javascript:setTheme('light')">light</a>
·
-<a onClick="javascript:setTheme('default')">device theme</a>
+<a onClick="javascript:setTheme('dark')">dunkel</a>
+<a onClick="javascript:setTheme('default')">automatisch</a>
+<!--Language:
<br/>
-Language:
<a onClick="javascript:setLang('de')">DE</a>
·
<a onClick="javascript:setLang('en')">EN</a>
·
<a onClick="javascript:setLang('default')">system language</a>
+-->
</div>
<div class="about">
<a href="_about">DBF</a> v<%= stash('version') // '???' %>
·
-<a href="_datenschutz" rel="nofollow"><%= l 'Datenschutz' %></a>
+<a href="_datenschutz" rel="nofollow">Datenschutz</a>
·
-<a href="_impressum" rel="nofollow"><%= l 'Impressum' %></a>
+<a href="_impressum" rel="nofollow">Impressum</a>
</div> <!-- about -->
</div> <!-- container -->
% }
diff --git a/templates/layouts/legacy.html.ep b/templates/layouts/legacy.html.ep
index e0265d0..5389c4b 100644
--- a/templates/layouts/legacy.html.ep
+++ b/templates/layouts/legacy.html.ep
@@ -17,13 +17,13 @@
<meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
% }
- % my $av = 'v89'; # asset version
- %= stylesheet "/static/${av}/css/default.css"
+ % my $av = 'v99'; # asset version
+ %= stylesheet "/static/${av}/css/legacy.css"
%= stylesheet "/static/${av}/css/material-icons.css"
%= stylesheet "/static/${av}/css/jquery-ui.min.css"
% my $force_mobile = param('force_mobile') // stash('force_mobile');
% if ($force_mobile) {
- %= stylesheet "/static/${av}/css/mobile.css"
+ %= stylesheet "/static/${av}/css/legacy-mobile.css"
% }
%if (stash('load_marquee')) {
%= javascript '/static/js/jquery-3.4.1.min.js'
diff --git a/templates/route_map.html.ep b/templates/route_map.html.ep
index 502b7fd..447960a 100644
--- a/templates/route_map.html.ep
+++ b/templates/route_map.html.ep
@@ -91,6 +91,6 @@ nicht implementiert.
% if (my $op = stash('operator')) {
<div class="container" style="margin-top: 1ex; margin-bottom: 1ex; color: #555;">
-<%= l 'Betrieb' %>: <%= $op %>
+Betrieb: <%= $op %>
</div>
% }
diff --git a/templates/wagen.html.ep b/templates/wagen.html.ep
index 0bb88c4..efc2e32 100644
--- a/templates/wagen.html.ep
+++ b/templates/wagen.html.ep
@@ -1,13 +1,13 @@
<div class="container singlewagon">
% if (not $wref->{e} and $wref->{s} and $wref->{p} and $wref->{ws}) {
<p>
- <%= $wref->{s} %> <%= l 'Gleis' %> <%= $wref->{p} %> <%= $wref->{ws} %>
+ <%= $wref->{s} %> Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
</p>
% }
% if ($wref->{e} eq 'u') {
% if ($wref->{s} and $wref->{p} and $wref->{ws}) {
<div class="platform">
- <%= $wref->{s} %><br/><%= l 'Gleis' %> <%= $wref->{p} %> <%= $wref->{ws} %>
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
<div class="sign-left"><i class="material-icons">arrow_upward</i></div>
<div class="sign-right"><i class="material-icons">arrow_upward</i></div>
</div>
@@ -47,7 +47,7 @@
<div class="sign-right"><i class="material-icons">arrow_downward</i></div>
% if ($wref->{s} and $wref->{p} and $wref->{ws}) {
<div class="platform">
- <%= $wref->{s} %><br/><%= l 'Gleis' %> <%= $wref->{p} %> <%= $wref->{ws} %>
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
</div>
% }
% }
diff --git a/templates/wagenreihung.html.ep b/templates/wagenreihung.html.ep
index 7d2e278..6781dca 100644
--- a/templates/wagenreihung.html.ep
+++ b/templates/wagenreihung.html.ep
@@ -7,37 +7,10 @@
</div>
% }
% else {
- % my $has_multi_dest = 0;
- % my $has_multi_desc = 0;
- % if (scalar $wr->destinations > 1) {
- % $has_multi_dest = 1;
- % }
- % if (scalar $wr->train_descriptions > 1) {
- % $has_multi_desc = 1;
- % }
<div class="container">
<div style="text-align: center;">
-%= join( ' / ', $wr->origins )
- →
-%= join( ' / ', map { $_->{name} } $wr->destinations )
+ <%= $wr->station->{name} %> Gleis <%= $wr->platform %><br/>
</div>
- % if ($has_multi_dest) {
- <div style="text-align: center;">
- % for my $destination ($wr->destinations) {
- <%= l 'Nach' %> <%= $destination->{name} %> <%= l 'in Abschnitt' %> <%= join(q{}, sort @{$destination->{sections} // []}) %><br/>
- % }
- </div>
- % }
- <%= $wr->station_name %> <%= l 'Gleis' %> <%= $wr->platform %><br/>
- % for my $desc ($wr->train_descriptions) {
- % if ($desc->{text}) {
- %= $desc->{text}
- % if ($has_multi_desc and length(join(q{}, sort @{$desc->{sections}}))) {
- <%= l 'in Abschnitt' %> <%= join(q{}, sort @{$desc->{sections}}) %>
- % }
- <br/>
- % }
- % }
</div>
<div class="container">
<div class="wagonorder exit-<%= stash('exit_dir') // 'unknown'%>">
@@ -49,10 +22,29 @@
</div>
% }
% }
-% for my $wagon ($wr->wagons) {
-%= include '_wagon', direction => $wr->direction, wagon => $wagon, type => $wr->train_type, wref => $wref, exit_dir => stash('exit_dir');
+% for my $group ($wr->groups) {
+% my $first = 1;
+% for my $wagon ($group->wagons) {
+%= include '_wagon', wr => $wr, wagon => $wagon, first => $first, multi => (scalar $wr->destinations) - 1 + (scalar $wr->train_nos) - 1, wref => $wref, exit_dir => stash('exit_dir');
+% $first = 0;
+% }
% }
</div>
+ <div style="text-align: center;">
+%= join( ' / ', map { $_->{name} } $wr->origins )
+ →
+%= join( ' / ', map { $_->{name} } $wr->destinations )
+ </div>
+ % for my $group ($wr->groups) {
+ % if ($group->description) {
+ <div style="text-align: center;">
+ %= $group->description
+ % if (scalar $wr->groups > 1 and $group->has_sections) {
+ in Abschnitt <%= join(q{}, sort $group->sections) %>
+ % }
+ </div>
+ % }
+ % }
<!-- <div>
Legende: ♿ Behindertengerechte Ausstattung / 🍴 Bistro/Restaurant / 🚪 Abteile vorhanden
</div>