diff options
Diffstat (limited to 'templates')
69 files changed, 4720 insertions, 827 deletions
diff --git a/templates/_backend_line.html.ep b/templates/_backend_line.html.ep new file mode 100644 index 0000000..00496d3 --- /dev/null +++ b/templates/_backend_line.html.ep @@ -0,0 +1,25 @@ +<div class="row"> + <div class="col s8 m6 l6 right-align"> + %= $backend->{longname} + % if ($backend->{id} == $user->{backend_id}) { + (aktuell ausgewählt) + % } + % if ($backend->{has_area}) { + <br/> + <a href="https://dbf.finalrewind.org/coverage/<%= $backend->{type} %>/<%= $backend->{name} %>"><%= join(q{, }, @{$backend->{regions} // []}) || '[Karte]' %></a> + % } + % elsif ($backend->{regions}) { + <br/> + %= join(q{, }, @{$backend->{regions} // []}) + % } + % if ($backend->{homepage}) { + <br/> + <a href="<%= $backend->{homepage} %>"><%= $backend->{homepage} =~ s{ ^ http s? :// (?: www[.] )? (.*?) (?: / )? $ }{$1}xr %></a> + % } + </div> + <div class="col s4 m6 l6 left-align"> + <button class="btn waves-effect waves-light <%= $backend->{id} == $user->{backend_id} ? 'disabled' : q{} %>" style="min-width: 6em;" type="submit" name="backend" value="<%= $backend->{id} %>"> + <%= $backend->{name} %> + </button> + </div> +</div> diff --git a/templates/_cancelled.html.ep b/templates/_cancelled.html.ep deleted file mode 100644 index be3e318..0000000 --- a/templates/_cancelled.html.ep +++ /dev/null @@ -1,27 +0,0 @@ -<div class="card info-color"> - <div class="card-content"> - <span class="card-title">Zugausfall dokumentieren</span> - <p>Prinzipiell wärest du nun eingecheckt in - <%= $journey->{train_type} %> <%= $journey->{train_no} %> - ab <%= $journey->{dep_name} %>, doch dieser Zug fällt aus. - </p> - <p>Falls du den Zugausfall z.B. für ein Fahrgastrechteformular - dokumentieren möchtest, wähle bitte jetzt deine geplante - Zielstation aus. Achtung: Momentan wird dabei keine - Soll-Ankunftszeit gespeichert, das zu beheben steht auf - der Todoliste.</p> - <table> - <tbody> - % my $is_after = 0; - % for my $station (@{$journey->{route_after}}) { - <tr><td><a class="action-cancelled-to" data-station="<%= $station->[0] %>"><%= $station->[0] %></a></td></tr> - % } - </tbody> - </table> - </div> - <div class="card-action"> - <a class="action-undo" data-id="in_transit"> - <i class="material-icons">undo</i> Checkinversuch Rückgängig? - </a> - </div> -</div> diff --git a/templates/_cancelled_departure.html.ep b/templates/_cancelled_departure.html.ep new file mode 100644 index 0000000..79492a5 --- /dev/null +++ b/templates/_cancelled_departure.html.ep @@ -0,0 +1,13 @@ +<div class="card"> + <div class="card-content"> + <span class="card-title">Zugausfall</span> + <p>Die Abfahrt von <%= $journey->{train_type} %> <%= $journey->{train_no} %> + in <a href="/s/<%= $journey->{dep_eva} %>"><%= $journey->{dep_name} %></a> + entfällt. Der Zugausfall auf der Fahrt nach <%= $journey->{arr_name} %> wurde bereits dokumentiert. + </p> + % if (my @connections = @{stash('connections_iris') // []}) { + <p>Alternative Reisemöglichkeiten:</p> + %= include '_connections', connections => \@connections, checkin_from => $journey->{dep_eva}; + % } + </div> +</div> diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 0d71c93..91f1ce7 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -1,8 +1,14 @@ +% my $user = current_user(); <div class="autorefresh"> <div class="card"> <div class="card-content"> - <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title">Eingecheckt in <%= $journey->{train_type} %> <%= $journey->{train_no} %></span> + <i class="material-icons right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + % if (not $journey->{arr_name}) { + <span class="card-title center-align">Ziel wählen</span> + % } + <span class="card-title center-align"> + %= include '_format_train', journey => $journey + </span> % if ($journey->{comment}) { <p><%= $journey->{comment} %></p> % } @@ -12,28 +18,21 @@ data-route="<%= journey_to_ajax_route($journey) %>" data-dest="<%= $journey->{arr_name} %>" > - % if ($journey->{boarding_countdown} > 120) { - Einfahrt in <%= sprintf('%.f', $journey->{boarding_countdown} / 60) %> Minuten<br/> - % } - % elsif ($journey->{boarding_countdown} > 60) { - Einfahrt in einer Minute<br/> + % if ($journey->{boarding_countdown} > 60) { + Einfahrt in <%= journeys->min_to_human(int($journey->{boarding_countdown} / 60)) %><br/> % } % elsif ($journey->{boarding_countdown} > 0) { - Zug fährt ein<br/> - % } - % if ($journey->{departure_countdown} > 120) { - Abfahrt in <%= sprintf('%.f', $journey->{departure_countdown} / 60) %> Minuten + Fährt ein<br/> % } - % elsif ($journey->{departure_countdown} > 60) { - Abfahrt in einer Minute + % if ($journey->{departure_countdown} > 60) { + Abfahrt in <%= journeys->min_to_human(int($journey->{departure_countdown} / 60)) %> % } % elsif ($journey->{departure_countdown} > 0) { Abfahrt in weniger als einer Minute % } % elsif (defined $journey->{arrival_countdown}) { % if ($journey->{arrival_countdown} > 60) { - Ankunft in <%= sprintf('%.f', $journey->{arrival_countdown} / 60) %> - Minute<%= sprintf('%.f', $journey->{arrival_countdown} / 60) == 1 ? '' : 'n' %> + Ankunft in <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %> % } % elsif ($journey->{arrival_countdown} > 0) { Ankunft in weniger als einer Minute @@ -43,13 +42,13 @@ % } % if ($journey->{arrival_countdown} < (60 * 15) and $journey->{arr_platform}) { % if ($journey->{arr_direction} and $journey->{arr_direction} eq 'r') { - <br/>Gleis <%= $journey->{arr_platform} %> ▶ + <br/><%= $journey->{platform_type} %> <%= $journey->{arr_platform} %> ▶ % } % elsif ($journey->{arr_direction} and $journey->{arr_direction} eq 'l') { - <br/>◀ Gleis <%= $journey->{arr_platform} %> + <br/>◀ <%= $journey->{platform_type} %> <%= $journey->{arr_platform} %> % } % else { - <br/>auf Gleis <%= $journey->{arr_platform} %> + <br/>auf <%= $journey->{platform_type} %> <%= $journey->{arr_platform} %> % } % } % } @@ -58,33 +57,60 @@ % } % if ($journey->{departure_countdown} > 0 and $journey->{dep_platform}) { % if ($journey->{dep_direction} and $journey->{dep_direction} eq 'r') { - <br/>Gleis <%= $journey->{dep_platform} %> ▶ + <br/><%= $journey->{platform_type} %> <%= $journey->{dep_platform} %> ▶ % } % elsif ($journey->{dep_direction} and $journey->{dep_direction} eq 'l') { - <br/>◀ Gleis <%= $journey->{dep_platform} %> + <br/>◀ <%= $journey->{platform_type} %> <%= $journey->{dep_platform} %> % } % else { - <br/>von Gleis <%= $journey->{dep_platform} %> + <br/>von <%= $journey->{platform_type} %> <%= $journey->{dep_platform} %> % } % } % if (my $wr = $journey->{wagonorder}) { <br/> - % my @wagons = $wr->wagons; - % my $direction = $wr->direction == 100 ? '→' : '←'; - % if ($journey->{dep_direction}) { - % $direction = $journey->{dep_direction} eq 'l' ? '◀' : '▶'; - % if (($journey->{dep_direction} eq 'l' ? 0 : 100) != $wr->direction) { - % @wagons = reverse @wagons; + <a href="https://dbf.finalrewind.org/carriage-formation?<%= join('&', map { $_ . '=' . $journey->{extra_data}{wagonorder_param}{$_} } sort keys %{$journey->{extra_data}{wagonorder_param}}) %>&e=<%= $journey->{dep_direction} // q{} %>"> + % my $direction = $wr->direction == 100 ? '→' : '←'; + % my $rev = 0; + % if ($journey->{dep_direction}) { + % $direction = $journey->{dep_direction} eq 'l' ? '◀' : '▶'; + % $rev = (($journey->{dep_direction} eq 'l' ? 0 : 100) == $wr->direction) ? 0 : 1; % } - % } - <a href="https://marudor.de/details/<%= $journey->{train_type} %>%20<%= $journey->{train_no} %>/<%= DateTime->now(time_zone => 'Europe/Berlin')->epoch %>000"> - %= $direction - % for my $wagon (@wagons) { - % if (not ($wagon->is_locomotive or $wagon->is_powercar)) { - %= $wagon->number || $wagon->type + %= $direction + % my $had_entry = 0; + % for my $group ($rev ? reverse $wr->groups : $wr->groups) { + % if ($had_entry) { + % $had_entry = 0; + • + % } + % for my $wagon ($rev ? reverse $group->carriages : $group->carriages) { + % if (not ($wagon->is_locomotive or $wagon->is_powercar)) { + % $had_entry = 1; + % if ($wagon->is_closed) { + X + % } + % elsif ( $wagon->number) { + %= $wagon->number + % } + % else { + % if ( $wagon->has_first_class ) { + % if ( $wagon->has_second_class ) { + ½ + % } + % else { + 1. + % } + % } + % elsif ( $wagon->has_second_class ) { + 2. + % } + % else { + %= $wagon->type; + % } + % } + % } + % } % } - % } - %= $direction + %= $direction </a> % } </div> @@ -102,7 +128,7 @@ % } </div> <div style="float: right; text-align: right;"> - <b><%= $journey->{arr_name} %></b><br/> + <b><a href="<%= resolve_sb_template($user->{sb_template}, name => $journey->{arr_name}, eva => $journey->{arr_eva}, tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id} =~ s{[ #|]}{x}gr, dbris => $journey->{is_dbris} ? $journey->{backend_name} : q{}, efa => $journey->{is_efa} ? $journey->{backend_name} : q{}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}, is_iris => $journey->{is_iris}, motis => $journey->{is_motis} ? $journey->{backend_name} : q{}) %>" class="unmarked"><%= $journey->{arr_name} %></a></b><br/> % if ($journey->{real_arrival}->epoch) { <b><%= $journey->{real_arrival}->strftime('%H:%M') %></b> % if ($journey->{real_arrival}->epoch != $journey->{sched_arrival}->epoch) { @@ -118,19 +144,21 @@ % if ($station->[0] eq $journey->{arr_name}) { % last; % } - % if (($station->[1]{rt_arr_countdown} // 0) > 0) { - <%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %> - % if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60); + % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { + <%= $station->[0] %><br/><%= $station->[2]{arr}->strftime('%H:%M') %> + % if ($station->[2]{arr_delay}) { + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60); % } % last; % } - % if (($station->[1]{rt_dep_countdown} // 0) > 0) { + % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{dep}) { <%= $station->[0] %><br/> - <%= $station->[1]{rt_arr}->strftime('%H:%M') %> → - <%= $station->[1]{rt_dep}->strftime('%H:%M') %> - % if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60); + % if ($station->[2]{arr}) { + <%= $station->[2]{arr}->strftime('%H:%M') %> → + % } + %= $station->[2]{dep}->strftime('%H:%M') + % if ($station->[2]{dep_delay}) { + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60); % } % last; % } @@ -143,19 +171,19 @@ % if ($station->[0] eq $journey->{arr_name}) { % last; % } - % if (($station->[1]{rt_arr_countdown} // 0) > 0) { - <%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %> - % if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60); + % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { + <%= $station->[0] %><br/><%= $station->[2]{arr}->strftime('%H:%M') %> + % if ($station->[2]{arr_delay}) { + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60); % } % last; % } - % if (($station->[1]{rt_dep_countdown} // 0) > 0) { + % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{arr} and $station->[2]{dep}) { <%= $station->[0] %><br/> - <%= $station->[1]{rt_arr}->strftime('%H:%M') %> → - <%= $station->[1]{rt_dep}->strftime('%H:%M') %> - % if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60); + <%= $station->[2]{arr}->strftime('%H:%M') %> → + <%= $station->[2]{dep}->strftime('%H:%M') %> + % if ($station->[2]{dep_delay}) { + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60); % } % last; % } @@ -163,9 +191,23 @@ </div> </p> % } - % if (@{$journey->{messages} // []} or @{$journey->{extra_data}{him_msg} // []} or @{$journey->{extra_data}{qos_msg} // []}) { + % if ($journey->{extra_data}{cancelled_destination}) { + <p style="margin-bottom: 2ex;"> + Der Halt an der Zielstation <b><%= + $journey->{extra_data}{cancelled_destination} %></b> entfällt. + Die zugehörige Fahrt wurde bereits als ausgefallen eingetragen. + Bitte wähle ein neues Reiseziel. + </p> + % } + % if (@{$journey->{messages} // []} or @{$journey->{extra_data}{qos_msg} // []} or not $journey->{extra_data}{rt}) { <p style="margin-bottom: 2ex;"> <ul> + % if ($journey->{extra_data}{manual}) { + <li><i class="material-icons tiny">gps_off</i> Manueller Checkin ohne Echtzeitdaten + % } + % elsif (not $journey->{extra_data}{rt}) { + <li><i class="material-icons tiny">gps_off</i> Keine Echtzeitdaten vorhanden + % } % for my $message (reverse @{$journey->{messages} // []}) { % if ($journey->{sched_departure}->epoch - $message->[0]->epoch < 1800) { <li> <i class="material-icons tiny">warning</i> <%= $message->[0]->strftime('%H:%M') %>: <%= $message->[1] %></li> @@ -176,57 +218,64 @@ <li> <i class="material-icons tiny">info</i> <%= $message->[0]->strftime('%H:%M') %>: <%= $message->[1] %></li> % } % } - % for my $message (@{$journey->{extra_data}{him_msg} // []}) { - <li> <i class="material-icons tiny">info</i> <%= $message->{header} %> <%= $message->{lead} %></li> - % } </ul> </p> % } - % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} < (20*60)) { - % if (my @connections = get_connecting_trains()) { - <span class="card-title" style="margin-top: 2ex;">Verbindungen</span> - % if ($journey->{arrival_countdown} < 0) { - <p>Zug auswählen zum Einchecken mit Zielwahl.</p> - % } - %= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef; + % if (@{stash('connections_iris') // [] } or @{stash('connections_hafas') // []}) { + <span class="card-title" style="margin-top: 2ex;">Verbindungen</span> + % if ($journey->{arrival_countdown} < 0) { + <p>Fahrt auswählen zum Einchecken mit Zielwahl.</p> + % } + % if (@{stash('connections_iris') // [] }) { + %= include '_connections', connections => stash('connections_iris'), checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_eva} : undef; + % } + % if (@{stash('connections_hafas') // [] }) { + %= include '_connections_hafas', connections => stash('connections_hafas'), checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_eva} : undef; % } % } % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) { <p style="margin-top: 2ex;"> - Der automatische Checkout erfolgt wegen gelegentlich veralteter - IRIS-Daten erst etwa zehn Minuten nach der Ankunft. + Der automatische Checkout erfolgt wegen teilweise langsamer + Echtzeitdatenupdates erst etwa zehn Minuten nach der Ankunft. </p> % } % elsif (not $journey->{arr_name}) { - <p>Ziel wählen:</p> - <table> - <tbody> - % for my $station (@{$journey->{route_after}}) { - <tr><td><a class="action-checkout" data-station="<%= $station->[0] %>"><%= $station->[0] %> - % if ($station->[2] and $station->[2] eq 'cancelled') { - <span style="float: right;">entfällt</span> - % } - % elsif ($station->[1]{rt_arr}) { - <span style="float: right;"><%= $station->[1]{rt_arr}->strftime('%H:%M') %></span> - % } - % elsif ($station->[2] and $station->[2] eq 'additional') { - <span style="float: right;">Zusatzhalt</span> - % } - </a></td></tr> - % } - </tbody> - </table> + <p> + % for my $station (@{$journey->{route_after}}) { + <a class="tablerow action-checkout" data-station="<%= $station->[1] // $station->[0] %>"> + <span><%= $station->[0] %></span> + <span> + %= include '_show_load_icons', station => $station + % if ($station->[2]{isCancelled}) { + entfällt + % } + % elsif ($station->[2]{rt_arr} or $station->[2]{sched_arr}) { + %= ($station->[2]{rt_arr} || $station->[2]{sched_arr})->strftime('%H:%M') + % } + % elsif ($station->[2]{rt_dep} or $station->[2]{sched_dep}) { + (<%= ($station->[2]{rt_dep} || $station->[2]{sched_dep})->strftime('%H:%M') %>) + % } + % elsif ($station->[2]{isAdditional}) { + Zusatzhalt + % } + </span> + </a> + % } + </p> % } </div> <div class="card-action"> % if ($journey->{arr_name}) { - <a style="margin-right: 0;" href="/journey/comment"> - <i class="material-icons left">comment</i> Kommentar + <a href="/journey/comment"> + <i class="material-icons">comment</i> + </a> + <a style="margin-right: 0;" href="/journey/visibility"> + <i class="material-icons"><%= visibility_icon($journey_visibility) %></i> </a> % } % else { <a class="action-undo blue-text" data-id="in_transit" data-checkints="<%= $journey->{timestamp}->epoch %>" style="margin-right: 0;"> - <i class="material-icons left">undo</i> Checkin Rückgängig + <i class="material-icons left" aria-hidden="true">undo</i> Rückgängig </a> % } % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) { @@ -235,67 +284,145 @@ style="margin-right: 0;" data-station="<%= $journey->{arr_name}%>"> <i class="material-icons left">done</i> - Jetzt auschecken + Auschecken </a> % } % elsif ($journey->{arr_name}) { - % my $attrib = 'im'; + % my $attrib = 'in'; % if ($journey->{train_type} =~ m{ ^ (?: S | RB ) $ }x) { % $attrib = 'in der'; % } <a class="action-share blue-text right" style="margin-right: 0;" - % if (current_user()->{is_public} & 0x04 and $journey->{comment}) { - data-text="<%= $journey->{comment} %> (@ <%= $journey->{train_type} %> <%= $journey->{train_no} %> → <%= $journey->{arr_name} %>) #NowTräwelling #dbl" + % my $arr_text = q{}; + % if ($journey->{real_arrival}->epoch and $journey_visibility eq 'private') { + % $arr_text = $journey->{real_arrival}->strftime(' – Ankunft gegen %H:%M Uhr'); + % } + % if ($user->{comments_visible} and $journey->{comment}) { + data-text="<%= $journey->{comment} %> (@ <%= $journey->{train_type} %> <%= $journey->{train_no} %> → <%= $journey->{arr_name} %>) #travelynx" % } % else { - data-text="Ich bin gerade <%= $attrib %> <%= $journey->{train_type} %> <%= $journey->{train_no} %> nach <%= $journey->{arr_name} %> #NowTräwelling #dbl" + data-text="Ich bin gerade <%= $attrib %> <%= $journey->{train_type} %> <%= $journey->{train_no} %> nach <%= $journey->{arr_name} . $arr_text %> #travelynx" % } - % if (current_user()->{is_public} & 0x02) { - data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= current_user->{name} %>/<%= $journey->{sched_departure}->epoch %>" + % if ($journey_visibility eq 'public') { + data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= $user->{name} %>/<%= $journey->{sched_departure}->epoch %>" + % } + % elsif ($journey_visibility eq 'travelynx' or $journey_visibility eq 'followers' or $journey_visibility eq 'unlisted') { + data-url="<%= url_for('/status')->to_abs->scheme('https') %>/<%= $user->{name} %>/<%= $journey->{sched_departure}->epoch %>?token=<%= $journey->{dep_eva} %>-<%= $journey->{timestamp}->epoch % 337 %>" % } > - <i class="material-icons left">share</i> Teilen + <i class="material-icons left" aria-hidden="true">share</i> Teilen + </a> + % } + % else { + <a class="right" href="/journey/visibility"> + <i class="material-icons left"><%= visibility_icon($journey_visibility) %></i> Sichtbarkeit </a> % } </div> </div> + % if (@{stash('timeline') // []}) { + %= include '_timeline_link', timeline => stash('timeline'), from_checkin => 1 + % } + % if ($journey->{arr_name} and @{$journey->{extra_data}{him_msg} // []}) { + <div class="card" style="margin-top: <%= scalar @{stash('timeline') // []} ? '1.5rem' : '3em' %>;"> + <div class="card-content"> + <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title">Meldungen</span> + % if (@{$journey->{extra_data}{him_msg} // []}) { + <p style="margin-bottom: 2ex;"> + <ul> + % for my $message (@{$journey->{extra_data}{him_msg} // []}) { + <li> <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %></li> + % } + </ul> + </p> + % } + % if ($journey->{traewelling}{errored} and $journey->{traewelling_log_latest}) { + <p style="margin-bottom: 2ex;"> + <ul> + <li> <i class="material-icons tiny">warning</i> Träwelling: <%= $journey->{traewelling_log_latest} %></li> + </ul> + </p> + % } + % if ($journey->{traewelling_url}) { + <p style="margin-bottom: 2ex;"> + <ul> + <li> <i class="material-icons tiny">sync</i> Träwelling: <a href="<%= $journey->{traewelling_url} %>"><%= $journey->{traewelling_log_latest} %></a></li> + </ul> + </p> + % } + </div> + </div> + % } % if ($journey->{arr_name}) { <div class="card" style="margin-top: 3em;"> <div class="card-content"> <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title">Ziel ändern?</span> - <table> - <tbody> - % for my $station (@{$journey->{route_after}}) { - % my $is_dest = ($journey->{arr_name} and $station->[0] eq $journey->{arr_name}); - <tr><td><a style="<%= $is_dest? 'font-weight: bold;' : '' %>" class="action-checkout" data-station="<%= $station->[0] %>"><%= $station->[0] %> - % if ($station->[2] and $station->[2] eq 'cancelled') { - <span style="float: right;">entfällt</span> - % } - % elsif ($station->[1]{rt_arr}) { - <span style="float: right;"><%= $station->[1]{rt_arr}->strftime('%H:%M') %></span> - % } - % elsif ($station->[2] and $station->[2] eq 'additional') { - <span style="float: right;">Zusatzhalt</span> - % } - </a></td></tr> - % } - </tbody> - </table> - <p> - Falls das Backend ausgefallen ist oder der Zug aus anderen - Gründen verloren ging: <a class="action-checkout" - data-force="1" data-station="<%= $journey->{arr_name} - %>">Ohne Echtzeitdaten in <%= $journey->{arr_name} %> - auschecken</a>. - </p> - </div> - <div class="card-action"> - <a class="action-undo blue-text" data-id="in_transit" data-checkints="<%= $journey->{timestamp}->epoch %>" style="margin-right: 0;"> - <i class="material-icons left">undo</i> Checkin Rückgängig - </a> + <span class="card-title">Karte</span> + <div id="map" style="height: 70vh;"> + </div> + %= include '_map', with_map_header => 0, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') </div> </div> + % if ($journey->{extra_data}{manual}) { + <div class="card" style="margin-top: 3em;"> + <div class="card-content"> + <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title">Manueller Checkin</span> + </div> + <div class="card-action"> + <a class="action-undo blue-text" data-id="in_transit" data-checkints="<%= $journey->{timestamp}->epoch %>" style="margin-right: 0;"> + <i class="material-icons left" aria-hidden="true">undo</i> Checkin Rückgängig + </a> + </div> + </div> + % } + % else { + <div class="card" style="margin-top: 3em;"> + <div class="card-content"> + <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title">Ziel ändern?</span> + <div class="targetlist"> + % for my $station (@{$journey->{route_after}}) { + % my $is_dest = ($journey->{arr_name} and $station->[0] eq $journey->{arr_name}); + <a class="action-checkout tablerow" style="<%= $is_dest? 'font-weight: bold;' : '' %>" data-station="<%= $station->[1] // $station->[0] %>"> + <span><%= $station->[0] %></span> + <span> + %= include '_show_load_icons', station => $station + % if ($station->[2]{isCancelled}) { + entfällt + % } + % elsif ($station->[2]{rt_arr} or $station->[2]{sched_arr}) { + %= ($station->[2]{rt_arr} || $station->[2]{sched_arr})->strftime('%H:%M') + % } + % elsif ($station->[2]{rt_dep} or $station->[2]{sched_dep}) { + (<%= ($station->[2]{rt_dep} || $station->[2]{sched_dep})->strftime('%H:%M') %>) + % } + % elsif ($station->[2]{isAdditional}) { + Zusatzhalt + % } + </span> + </a> + <a class="nonflex" href="<%= resolve_sb_template($user->{sb_template}, name => $station->[0], eva => $station->[1], tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id} =~ s{[ #|]}{x}gr, dbris => $journey->{is_dbris} ? $journey->{backend_name} : q{}, efa => $journey->{is_efa} ? $journey->{backend_name} : q{}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}, is_iris => $journey->{is_iris}, motis => $journey->{is_motis} ? $journey->{backend_name} : q{}) %>"><i class="material-icons tiny"><%= $journey->{is_hafas} ? 'directions' : 'train' %></i></a> + % } + </div> + </div> + <div class="card-action"> + <a class="action-undo blue-text" data-id="in_transit" data-checkints="<%= $journey->{timestamp}->epoch %>" style="margin-right: 0;"> + <i class="material-icons left" aria-hidden="true">undo</i> Checkin Rückgängig + </a> + </div> + </div> + % } + <p> + Falls das Backend ausgefallen ist oder die Fahrt aus anderen + Gründen verloren ging: + </p> + <p class="center-align"> + <a class="action-checkout waves-light btn" + data-force="1" data-station="<%= $journey->{arr_name} + %>">Ohne Echtzeitdaten auschecken</a> + </p> % } </div> diff --git a/templates/_checked_out.html.ep b/templates/_checked_out.html.ep index ca9373d..21db335 100644 --- a/templates/_checked_out.html.ep +++ b/templates/_checked_out.html.ep @@ -1,13 +1,17 @@ <div class="card"> <div class="card-content"> <span class="card-title">Ausgecheckt</span> - <p>Aus <%= $journey->{train_type} %> <%= $journey->{train_no} %> - bis <a href="/s/<%= $journey->{arr_ds100} %>"><%= $journey->{arr_name} %></a></p> - % if (now()->epoch - $journey->{timestamp}->epoch < (30*60)) { - % if (my @connections = get_connecting_trains()) { - <span class="card-title" style="margin-top: 2ex;">Verbindungen</span> - <p>Zug auswählen zum Einchecken mit Zielwahl.</p> - %= include '_connections', connections => \@connections, checkin_from => $journey->{arr_ds100}; + <p>Aus + %= include '_format_train', journey => $journey + bis <a href="/s/<%= $journey->{arr_eva} %>?hafas=<%= $journey->{is_hafas} ? $journey->{backend_name} : q{} %>"><%= $journey->{arr_name} %></a></p> + % if (@{stash('connections_iris') // [] } or @{stash('connections_hafas') // []}) { + <span class="card-title" style="margin-top: 2ex;">Verbindungen</span> + <p>Fahrt auswählen zum Einchecken mit Zielwahl.</p> + % if (@{stash('connections_iris') // [] }) { + %= include '_connections', connections => stash('connections_iris'), checkin_from => $journey->{arr_eva}; + % } + % if (@{stash('connections_hafas') // [] }) { + %= include '_connections_hafas', connections => stash('connections_hafas'), checkin_from => $journey->{arr_eva}; % } % } </div> diff --git a/templates/_connections.html.ep b/templates/_connections.html.ep index 2837e93..1dd2718 100644 --- a/templates/_connections.html.ep +++ b/templates/_connections.html.ep @@ -1,89 +1,76 @@ -<div class="hide-on-med-and-up"><table class="striped"><tbody> +<ul class="collection departures connections"> % for my $res (@{$connections}) { - % my ($train, $via) = @{$res}; - % my $td_class = ''; + % my ($train, $via, $via_arr, $load) = @{$res}; + % $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{}; + % my $row_class = ''; % my $link_class = 'action-checkin'; % if ($train->is_cancelled) { - % $td_class = 'cancelled'; + % $row_class = 'cancelled'; % $link_class = 'action-cancelled-from'; % } - <tr> - <td class="<%= $td_class %>"> - % if ($checkin_from) { - <a class="<%= $link_class %>" data-station="<%= $train->station_uic %>" data-train="<%= $train->train_id %>" data-dest="<%= $via %>"><%= $train->line %></a> - % } - % else { - %= $train->line - % } - </td> - <td class="<%= $td_class %>"> - % if ($checkin_from) { - <a class="<%= $link_class %>" data-station="<%= $train->station_uic %>" data-train="<%= $train->train_id %>" data-dest="<%= $via %>"><%= $via %></a> - % } - % else { - %= $via - % } - </td> - <td> + % if ($checkin_from) { + <li class="collection-item <%= $row_class %> <%= $link_class %>" + data-station="<%= $train->station_uic %>" + data-train="<%= $train->train_id %>" + data-ts="<%= ($train->sched_departure // $train->departure)->epoch %>" + data-dest="<%= $via->{name} %>"> + % } + % else { + <li class="collection-item <%= $row_class %>"> + % } + <a class="dep-time" href="#"> % if ($train->departure_is_cancelled) { %= $train->sched_departure->strftime('%H:%M') - ⊖ % } % else { %= $train->departure->strftime('%H:%M') - % if ($train->departure_delay) { - %= sprintf('(%+d)', $train->departure_delay) - % } - <br/>Gleis <%= $train->platform || '?' %> % } - </td> - </tr> - % } -</tbody></table></div> -<div class="hide-on-small-only"><table class="striped"><tbody> - % for my $res (@{$connections}) { - % my ($train, $via) = @{$res}; - % my $td_class = ''; - % my $link_class = 'action-checkin'; - % if ($train->departure_is_cancelled) { - % $td_class = 'cancelled'; - % $link_class = 'action-cancelled-from'; - % } - <tr> - <td class="<%= $td_class %>"> - % if ($checkin_from) { - <a class="<%= $link_class %>" data-station="<%= $train->station_uic %>" data-train="<%= $train->train_id %>" data-dest="<%= $via %>"><%= $train->line %></a> + % if ($via_arr) { + → <%= $via_arr %> % } - % else { - %= $train->line + % if ($train->departure_delay) { + %= sprintf('(%+d)', $train->departure_delay) % } - </td> - <td class="<%= $td_class %>"> - % if ($checkin_from) { - <a class="<%= $link_class %>" data-station="<%= $train->station_uic %>" data-train="<%= $train->train_id %>" data-dest="<%= $via %>"><%= $via %></a> + </a> + <span class="connect-platform-wrapper"> + % if ($train->platform) { + <span>Gleis <%= $train->platform %></span> + % } + <span class="dep-line <%= $train->type // q{} %>"> + %= $train->line + </span> + </span> + <span class="dep-dest"> + % if ($train->is_cancelled) { + Fahrt nach <%= $via->{name} %> entfällt % } % else { - %= $via + %= $via->{name} % } - </td> - <td> - % if ($train->departure_is_cancelled) { - %= $train->sched_departure->strftime('%H:%M') + <br/> + % if ($load) { + % my ($first, $second) = load_icon($load); + <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> % } - % else { - %= $train->departure->strftime('%H:%M') - % if ($train->departure_delay) { - %= sprintf('(%+d)', $train->departure_delay) - % } + % if ($train->{interchange_icon}) { + <i class="material-icons tiny" aria-label="<%= $train->{interchange_text} %>"><%= $train->{interchange_icon} %></i> % } - </td><td> - % if (not $train->departure_is_cancelled) { - Gleis <%= $train->platform || '?' %> + % if ($train->{message_id}{96} or $train->{message_id}{97}) { + <i class="material-icons tiny" aria-label="Zug ist überbesetzt">warning</i> % } - % else { - fällt aus + % if ($train->{message_id}{82} or $train->{message_id}{85}) { + <i class="material-icons tiny" aria-label="Fehlende Wagen">remove</i> + % } + % if (($train->{message_id}{73} or $train->{message_id}{74} or $train->{message_id}{75} or $train->{message_id}{76} or $train->{message_id}{80}) and not $train->{message_id}{84}) { + <i class="material-icons tiny" aria-label="Abweichende Wagenreihung">compare_arrows</i> + % } + % if ($train->{message_id}{83} or $train->{message_id}{93} or $train->{message_id}{95}) { + <i class="material-icons tiny" aria-label="Eingeschränkte Barrierefreiheit">info_outline</i> + % } + % if ($train->{message_id}{70} or $train->{message_id}{71}) { + <i class="material-icons tiny" aria-label="Ohne WLAN">portable_wifi_off</i> % } - </td> - </tr> + </span> + </li> % } -</tbody></table></div> +</ul> diff --git a/templates/_connections_hafas.html.ep b/templates/_connections_hafas.html.ep new file mode 100644 index 0000000..3b995b5 --- /dev/null +++ b/templates/_connections_hafas.html.ep @@ -0,0 +1,57 @@ +<ul class="collection departures connections"> + % for my $res (@{$connections}) { + % my ($train, $via, $via_arr, $hafas_service) = @{$res}; + % $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{}; + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($train->is_cancelled) { + % $row_class = 'cancelled'; + % $link_class = 'action-cancelled-from'; + % } + % if ($checkin_from) { + <li class="collection-item <%= $row_class %> <%= $link_class %>" + data-hafas="<%= $hafas_service %>" + data-station="<%= $train->station_eva %>" + data-train="<%= $train->id %>" + data-ts="<%= ($train->sched_datetime // $train->datetime)->epoch %>" + data-dest="<%= $via->{name} %>"> + % } + % else { + <li class="collection-item <%= $row_class %>"> + % } + <a class="dep-time" href="#"> + % if ($train->is_cancelled) { + %= $train->sched_datetime->strftime('%H:%M') + % } + % else { + %= $train->datetime->strftime('%H:%M') + % } + % if ($via_arr) { + → <%= $via_arr %> + % } + % if ($train->delay) { + %= sprintf('(%+d)', $train->delay) + % } + </a> + <span class="connect-platform-wrapper"> + % if ($train->platform) { + <span> + % if (($train->type // q{}) =~ m{ ast | bus | ruf }ix) { + Steig + % } + % else { + Gleis + % } + %= $train->platform + </span> + % } + <span class="dep-line <%= $train->type // q{} %>"> + %= $train->line + </span> + </span> + <span class="dep-dest"> + %= $via->{name} + </span> + </li> + % } +</ul> diff --git a/templates/_departures_dbris.html.ep b/templates/_departures_dbris.html.ep new file mode 100644 index 0000000..dbd1a70 --- /dev/null +++ b/templates/_departures_dbris.html.ep @@ -0,0 +1,55 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->dep->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-dbris="<%= $dbris %>" + data-station="<%= $result->stop_eva %>" + data-train="<%= $result->id %>" + data-suffix="<%= $result->maybe_line_no %>" + data-ts="<%= ($result->sched_dep // $result->dep)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->dep->strftime('%H:%M') + % if ($result->delay) { + (<%= sprintf('%+d', $result->delay) %>) + % } + % elsif (not defined $result->delay and not $result->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->type // q{} %>"> + %= $result->line + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->destination // $result->via_last %> entfällt + % } + % else { + %= $result->destination // $result->via_last + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_departures_efa.html.ep b/templates/_departures_efa.html.ep new file mode 100644 index 0000000..26af13f --- /dev/null +++ b/templates/_departures_efa.html.ep @@ -0,0 +1,57 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->datetime->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-efa="<%= $efa %>" + data-station="<%= $result->stop_id_num %>" + data-train="<%= $result->id %>" + data-ts="<%= ($result->sched_datetime // $result->datetime)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->datetime->strftime('%H:%M') + % if ($result->delay) { + (<%= sprintf('%+d', $result->delay) %>) + % } + % elsif (not defined $result->delay and not $result->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= ($result->type // q{}) =~ tr{a-zA-Z_-}{}cdr %>"> + %= $result->line + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->destination %> entfällt + % } + % else { + %= $result->destination + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % if ($result->occupancy) { + <i class="material-icons tiny" aria-hidden="true"><%= efa_load_icon($result->occupancy) %></i> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_departures_hafas.html.ep b/templates/_departures_hafas.html.ep new file mode 100644 index 0000000..5825ba0 --- /dev/null +++ b/templates/_departures_hafas.html.ep @@ -0,0 +1,61 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->datetime->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-hafas="<%= $hafas %>" + data-station="<%= $result->station_eva %>" + data-train="<%= $result->id %>" + data-ts="<%= ($result->sched_datetime // $result->datetime)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->datetime->strftime('%H:%M') + % if ($result->delay) { + (<%= sprintf('%+d', $result->delay) %>) + % } + % elsif (not defined $result->delay and not $result->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->type // q{} %>"> + %= $result->line + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->destination %> entfällt + % } + % else { + %= $result->destination + % if ($result->load and $result->load->{SECOND}) { + % my ($first, $second) = load_icon($result->load); + % if ($first ne 'help_outline') { + <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> + % } + <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> + % } + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_departures_iris.html.ep b/templates/_departures_iris.html.ep new file mode 100644 index 0000000..d96fd37 --- /dev/null +++ b/templates/_departures_iris.html.ep @@ -0,0 +1,58 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->departure_is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->departure->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-station="<%= $result->station_uic %>" + data-train="<%= $result->train_id %>" + data-ts="<%= ($result->sched_departure // $result->departure)->epoch %>" + > + <a class="dep-time" href="#"> + % if ($result->departure_hidden) { + (<%= $result->departure->strftime('%H:%M') %>) + % } + % else { + %= $result->departure->strftime('%H:%M') + % } + % if ($result->departure_delay) { + (<%= sprintf('%+d', $result->departure_delay) %>) + % } + % elsif (not $result->has_realtime and $result->start->epoch < $now_epoch) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->type // q{} %>"> + %= $result->line + </span> + <span class="dep-dest"> + % if ($result->departure_is_cancelled) { + Fahrt nach <%= $result->destination %> entfällt + % } + % else { + %= $result->destination + % for my $checkin (@{$checkin_by_train->{$result->train_id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_departures_motis.html.ep b/templates/_departures_motis.html.ep new file mode 100644 index 0000000..2ebc5de --- /dev/null +++ b/templates/_departures_motis.html.ep @@ -0,0 +1,54 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->stopover->departure->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-motis="<%= $motis %>" + data-station="<%= $result->stopover->stop->id %>" + data-train="<%= $result->id %>" + data-ts="<%= ($result->stopover->departure)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->stopover->departure->strftime('%H:%M') + % if ($result->stopover->delay) { + (<%= sprintf('%+d', $result->stopover->delay) %>) + % } + % elsif (not $result->stopover->is_realtime and not $result->stopover->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->mode %>" style="background-color: #<%= $result->route_color // q{} %>;"> + %= $result->route_name + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->headsign %> entfällt + % } + % else { + %= $result->headsign + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_footer.html.ep b/templates/_footer.html.ep deleted file mode 100644 index 4f778c3..0000000 --- a/templates/_footer.html.ep +++ /dev/null @@ -1,9 +0,0 @@ -<div class="row" style="margin-top: 5em;"> - <div class="col s12 center-align grey-text"> - <a href="/impressum">Impressum</a> - <span style="margin-left: 0.5em; margin-right: 0.5em;">–</span> - <a href="/impressum">Datenschutz</a> - <span style="margin-left: 0.5em; margin-right: 0.5em;">–</span> - <a href="/about">travelynx</a> v<%= $version // '???' %> - </div> -</div> diff --git a/templates/_format_train.html.ep b/templates/_format_train.html.ep new file mode 100644 index 0000000..cb81211 --- /dev/null +++ b/templates/_format_train.html.ep @@ -0,0 +1,12 @@ +% if ($journey->{extra_data}{wagonorder_pride}) { + 🏳️🌈 +% } +<span class="dep-line <%= ($journey->{train_type} // q{}) =~ tr{a-zA-Z_-}{}cdr %>"> + % if (not $journey->{is_motis}) { + <%= $journey->{train_type} %> + % } + <%= $journey->{train_line} // $journey->{train_no}%> +</span> +% if ($journey->{train_line}) { + <%= $journey->{train_no} %> +% } diff --git a/templates/_history_months.html.ep b/templates/_history_months.html.ep index 6a83d74..2dc0141 100644 --- a/templates/_history_months.html.ep +++ b/templates/_history_months.html.ep @@ -1,12 +1,23 @@ <div class="row"> <div class="col s12"> <ul class="pagination"> - % for my $month (history_months()) { - % my $link_to = $month->[0]; - % my $text = $month->[1]; - % my $class = $link_to eq $current ? 'active' : 'waves-effect'; - <li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li> + % my ($prev, $current, $next) = journeys->get_nav_months(uid => current_user->{id}, year => $year, month => $month); + % if ($prev) { + <li class="waves-effect waves-light"><a href="/history/<%= $prev->[0] %>"><i class="material-icons">chevron_left</i></a></li> % } + % else { + <li class="disabled"><a href="#!"><i class="material-icons">chevron_left</i></a></li> + % } + % if ($current) { + <li class="" style="min-width: 8em;"><a href="/history/<%= $current->[0] %>"><%= $current->[1] %></a></li> + % } + % if ($next) { + <li class="waves-effect waves-light"><a href="/history/<%= $next->[0] %>"><i class="material-icons">chevron_right</i></a></li> + % } + % else { + <li class="disabled"><a href="#!"><i class="material-icons">chevron_right</i></a></li> + % } + <li class=""><a href="/history/<%= $year %>"><%= $year %></a></li> </ul> </div> </div> diff --git a/templates/_history_months_for_year.html.ep b/templates/_history_months_for_year.html.ep new file mode 100644 index 0000000..1d035ab --- /dev/null +++ b/templates/_history_months_for_year.html.ep @@ -0,0 +1,18 @@ +<div class="row"> + <div class="col s12"> + <div class="collection"> + % for my $month (journeys->get_months_for_year(uid => current_user->{id}, year => $year)) { + % if (defined $month->[2]) { + <a class="collection-item" href="/history/<%= $month->[0] %>"><%= $month->[1] %> + % if (defined $month->[2]{km_route}) { + <span class="secondary-content"><%= sprintf('%.f', $month->[2]{km_route}) %> km</span> + % } + </a> + % } + % else { + <div class="collection-item disabled"><%= $month->[1] %></div> + % } + % } + </div> + </div> +</div> diff --git a/templates/_history_stats.html.ep b/templates/_history_stats.html.ep index 8197ed1..cbdbb13 100644 --- a/templates/_history_stats.html.ep +++ b/templates/_history_stats.html.ep @@ -1,35 +1,8 @@ -% if (@{$stats->{inconsistencies}}) { - <div class="row"> - <div class="col s12"> - <div class="card caution-color"> - <div class="card-content white-text"> - <i class="material-icons small right">warning</i> - <span class="card-title">Inkonsistente Reisedaten</span> - <p> - Die folgenden Abfahrtszeiten liegen vor der Ankunftszeit der - vorherigen Zugfahrt und wurden bei der Wartezeitberechnung - ignoriert. - <ul> - % for my $date (@{$stats->{inconsistencies}}) { - <li><%= $date %></li> - % } - </ul> - </p> - </div> - </div> - </div> - </div> -% } - <div class="row"> <div class="col s12"> <table class="striped"> <tr> <th scope="row">Fahrten</th> - <td><%= $stats->{num_journeys} %></td> - </tr> - <tr> - <th scope="row">Züge</th> <td><%= $stats->{num_trains} %></td> </tr> <tr> @@ -39,17 +12,25 @@ </tr> <tr> <th scope="row">Fahrtzeit</th> - <td><%= sprintf('%02d:%02d', $stats->{min_travel_real} / 60, $stats->{min_travel_real} % 60) %> Stunden - (nach Fahrplan: <%= sprintf('%02d:%02d', $stats->{min_travel_sched} / 60, $stats->{min_travel_sched} % 60) %>)<td> + <td><%= $stats->{min_travel_real_strf} %> Stunden + (nach Fahrplan: <%= $stats->{min_travel_sched_strf} %>)</td> </tr> <tr> <th scope="row">Wartezeit (nur Umstiege)</th> - <td><%= sprintf('%02d:%02d', $stats->{min_interchange_real} / 60, $stats->{min_interchange_real} % 60) %> Stunden + <td><%= $stats->{min_interchange_real_strf} %> Stunden + % if (@{$stats->{inconsistencies}}) { + <br/><br/>Für Wartezeitberechnung nicht berücksichtigte Fahrten:<br/> + % for my $field (@{$stats->{inconsistencies}}) { + <a href="/journey/<%= $field->{ignored}{id} %>"><%= $field->{ignored}{train} %> ab <%= $field->{ignored}{dep} %></a> + (Konflikt: <a href="/journey/<%= $field->{conflict}{id} %>"><%= $field->{conflict}{train} %> an <%= $field->{conflict}{arr} %></a>)<br/> + % } + % } + </td> </tr> <tr> <th scope="row">Kumulierte Verspätung</th> - <td>Bei Abfahrt: <%= sprintf('%02d:%02d', $stats->{delay_dep} / 60, $stats->{delay_dep} % 60) %> Stunden<br/> - Bei Ankunft: <%= sprintf('%02d:%02d', $stats->{delay_arr} / 60, $stats->{delay_arr} % 60) %> Stunden</td> + <td>Bei Abfahrt: <%= $stats->{delay_dep_strf} %> Stunden<br/> + Bei Ankunft: <%= $stats->{delay_arr_strf} %> Stunden</td> </tr> </table> </div> diff --git a/templates/_history_trains.html.ep b/templates/_history_trains.html.ep index d422165..166d74d 100644 --- a/templates/_history_trains.html.ep +++ b/templates/_history_trains.html.ep @@ -1,36 +1,33 @@ <div class="row"> <div class="col s12"> - <table class="striped"> - <thead> - <tr> - <th>Datum</th> - <th>Zug</th> - <th>Von</th> - <th>Nach</th> - </tr> - </thead> - <tbody> - % for my $travel (@{$journeys}) { - % my $detail_link = '/journey/' . $travel->{id}; - <tr> - <td><%= $travel->{sched_departure}->strftime($date_format) %></td> - <td><a href="<%= $detail_link %>"><%= $travel->{type} %> <%= $travel->{line} // $travel->{no} %></a></td> - <td> - <a href="<%= $detail_link %>" class="unmarked"> - % if (param('cancelled')) { - %= $travel->{sched_departure}->strftime('%H:%M') + <ul class="collection history"> + % my $olddate = ''; + % for my $travel (@{$journeys}) { + % my $detail_link = '/journey/' . $travel->{id}; + % if (my $prefix = stash('link_prefix')) { + % $detail_link = $prefix . $travel->{id}; + % } + % my $date = $travel->{sched_departure}->strftime($date_format); + % if ($olddate ne $date) { + <li class="collection-item history-date-change"> + <b><%= $date %></b> + </li> + % $olddate = $date + % } + <li class="collection-item"> + <a href="<%= $detail_link %>"> + <span class="dep-line <%= ($travel->{type} // q{}) =~ tr{a-zA-Z_-}{}cdr %>"> + % if (length($travel->{type}) < 5 and not $travel->{is_motis}) { + <%= $travel->{type} %> % } - % else { - <%= $travel->{rt_departure}->strftime('%H:%M') %> - % if ($travel->{sched_departure} != $travel->{rt_departure}) { - (<%= sprintf('%+d', ($travel->{rt_departure}->epoch - $travel->{sched_departure}->epoch) / 60) %>) - % } - % } - <br/> - <%= $travel->{from_name} %> - </a> - </td> - <td> + <%= $travel->{line} // $travel->{no}%> + </span> + </a> + + <ul class="route-history"> + <li> + <i class="material-icons tiny" aria-label="nach">radio_button_unchecked</i> + <a href="<%= $detail_link %>" class="unmarked"> % if (param('cancelled') and $travel->{sched_arrival}->epoch != 0) { %= $travel->{sched_arrival}->strftime('%H:%M') @@ -40,17 +37,34 @@ <i class="material-icons">timer_off</i> % } else { %= $travel->{rt_arrival}->strftime('%H:%M'); - % if ($travel->{sched_arrival} != $travel->{rt_arrival}) { - (<%= sprintf('%+d', ($travel->{rt_arrival}->epoch - $travel->{sched_arrival}->epoch) / 60) %>) + % if ($travel->{delay_arr} and int($travel->{delay_arr} / 60)) { + (<%= sprintf('%+d', $travel->{delay_arr} / 60) %>) % } % } % } - <br/> - <%= $travel->{to_name} %> - </a></td> - </tr> - % } - </tbody> - </table> + <strong><%= $travel->{to_name} %></strong> + </a> + </li> + + <li> + <i class="material-icons tiny" aria-label="von">play_circle_filled</i> + + <a href="<%= $detail_link %>" class="unmarked"> + % if (param('cancelled')) { + %= $travel->{sched_departure}->strftime('%H:%M') + % } + % else { + <%= $travel->{rt_departure}->strftime('%H:%M') %> + % if ($travel->{delay_dep} and int($travel->{delay_dep} / 60)) { + (<%= sprintf('%+d', $travel->{delay_dep} / 60) %>) + % } + % } + <strong><%= $travel->{from_name} %></strong> + </a> + </li> + </ul> + </li> + % } + </ul> </div> </div> diff --git a/templates/_history_years.html.ep b/templates/_history_years.html.ep index 768c438..5591799 100644 --- a/templates/_history_years.html.ep +++ b/templates/_history_years.html.ep @@ -1,12 +1,18 @@ <div class="row"> <div class="col s12"> - <ul class="pagination"> - % for my $year (history_years()) { - % my $link_to = $year->[0]; - % my $text = $year->[1]; - % my $class = $link_to eq $current ? 'active' : 'waves-effect'; - <li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li> - % } - </ul> + % my @years = journeys->get_nav_years(uid => current_user->{id}); + % if (@years) { + <ul class="pagination"> + % for my $year (@years) { + % my $link_to = $year->[0]; + % my $text = $year->[1]; + % my $class = $link_to eq $current ? 'active' : 'waves-effect'; + <li class="<%= $class %>"><a href="/history/<%= $link_to %>"><%= $text %></a></li> + % } + </ul> + % } + % else { + Keine Fahrten gefunden. + % } </div> </div> diff --git a/templates/_history_years_list.html.ep b/templates/_history_years_list.html.ep new file mode 100644 index 0000000..e91ebca --- /dev/null +++ b/templates/_history_years_list.html.ep @@ -0,0 +1,13 @@ +<div class="row"> + <div class="col s12"> + <div class="collection"> + % for my $year (journeys->get_years(uid => current_user->{id})) { + <a class="collection-item" href="/history/<%= $year->[0] %>"><%= $year->[1] %> + % if (defined $year->[2]{km_route}) { + <span class="secondary-content"><%= sprintf('%.f', $year->[2]{km_route}) %> km</span> + % } + </a> + % } + </div> + </div> +</div> diff --git a/templates/_invalid_input.html.ep b/templates/_invalid_input.html.ep index 4cebf29..f8c4e2f 100644 --- a/templates/_invalid_input.html.ep +++ b/templates/_invalid_input.html.ep @@ -2,14 +2,7 @@ <div class="col s12"> <div class="card caution-color"> <div class="card-content white-text"> - % if ($invalid eq 'csrf') { - <span class="card-title">Ungültiger CSRF-Token</span> - <p>Sind Cookies aktiviert? Ansonsten könnte es sich um einen - Fall von <a - href="https://de.wikipedia.org/wiki/Cross-Site-Request-Forgery">CSRF</a> - handeln.</p> - % } - % elsif ($invalid eq 'credentials') { + % if ($invalid eq 'credentials') { <span class="card-title">Ungültige Logindaten</span> <p>Falscher Account oder falsches Passwort.</p> % } @@ -78,6 +71,10 @@ <p>Aus Sicherheitsgründen kann der Account nur nach Passworteingabe gelöscht werden.</p> % } + % elsif ($invalid eq 'denylist') { + <span class="card-title">Registrierung deaktiviert</span> + <p>Für diesen Zugang ist derzeit keine Registrierung möglich.</p> + % } % else { <span class="card-title">Fehler</span> <p><%= $invalid %></p> diff --git a/templates/_map.html.ep b/templates/_map.html.ep index cceec01..223bd68 100644 --- a/templates/_map.html.ep +++ b/templates/_map.html.ep @@ -1,16 +1,18 @@ -<div class="row"> - <div class="col s12"> - <div id="map" style="height: 500px;"> +% if (stash('with_map_header') // 1) { + <div class="row"> + <div class="col s12"> + <div id="map" style="height: 70vh;"> + </div> </div> </div> -</div> -<div class="row"> - <div class="col s12"> - <span style="color: #f03;">●</span> Ein-/Ausstiegsstation<br/> - <span style="color: #f09;">—</span> Luftlinie zwischen Unterwegshalten + <div class="row"> + <div class="col s12"> + <span style="color: #f03;">●</span> Ein-/Ausstiegsstation<br/> + <span style="color: #673ab7;">—</span> Streckenverlauf oder Luftlinie + </div> </div> -</div> +% } <script> var map = L.map('map').setView([51.306, 9.712], 6); @@ -25,24 +27,30 @@ var stations = [ % } ]; -var routes = [ -% for my $pair ( @{stash('station_pairs') // [] } ) { -[[<%= $pair->[0][0] %>,<%= $pair->[0][1] %>],[<%= $pair->[1][0] %>,<%= $pair->[1][1] %>]], +var routes = []; +var pl; +% for my $line_group ( @{ stash('polyline_groups') // [] } ) { + routes = [ <%= $line_group->{polylines} %> ]; + pl = L.polyline(routes, {color: '<%= $line_group->{color} %>', opacity: <%= $line_group->{opacity} %>}).addTo(map); + % if ($line_group->{fit_bounds}) { + if (routes.length) { + map.fitBounds(pl.getBounds()); + } + % } +% } + +% if (my $b = stash('bounds')) { + map.fitBounds([[<%= $b->[0][0] %>,<%= $b->[0][1] %>],[<%= $b->[1][0] %>,<%= $b->[1][1] %>]]); % } -]; for (var station_id in stations) { L.circle(stations[station_id][0], { color: '#f03', + opacity: 0.7, fillColor: '#f03', fillOpacity: 0.5, radius: 250 }).bindPopup(stations[station_id][1]).addTo(map); } -var pl = L.polyline(routes, {color: '#f09', opacity: 0.5}).addTo(map); -if (routes.length) { - map.fitBounds(pl.getBounds()); -} - </script> diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep index 2ff7ac1..32b193a 100644 --- a/templates/_public_status_card.html.ep +++ b/templates/_public_status_card.html.ep @@ -1,36 +1,47 @@ -<div class="autorefresh"> +<div class="autorefresh" data-from-profile="<%= stash('from_profile') ? 1 : 0 %>"> % if ($journey->{checked_in}) { <div class="card"> <div class="card-content"> - <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title"><%= $name %> ist unterwegs</span> - % if ($public_level & 0x04 and $journey->{comment}) { - <p>„<%= $journey->{comment} %>“</p> - % } - <p> - % my $url = 'https://marudor.de/details/' . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . DateTime->now(time_zone => 'Europe/Berlin')->epoch . '000'; - % if ($journey->{train_line}) { - <div class="center-align"><a href="<%= $url %>"><b><%= $journey->{train_type} %> <%= $journey->{train_line} %></b> <%= $journey->{train_no} %></a></div> + <i class="material-icons right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title"> + % if (stash('from_profile')) { + Unterwegs mit <%= include '_format_train', journey => $journey %> + % } + % elsif (stash('from_timeline')) { + <a href="/status/<%= $name %>"><%= $name %></a>: <%= include '_format_train', journey => $journey %> % } % else { - <div class="center-align"><a href="<%= $url %>"><b><%= $journey->{train_type} %> <%= $journey->{train_no} %></b></a></div> + <a href="/p/<%= $name %>"><%= $name %></a> ist unterwegs + % } + <i class="material-icons right"><%= visibility_icon($journey->{effective_visibility_str}) %></i> + % if (not $journey->{extra_data}{rt}) { + <i class="material-icons right grey-text">gps_off</i> + % } + </span> + % if ($privacy->{comments_visible} and $journey->{comment}) { + <div>„<%= $journey->{comment} %>“</div> + % } + <div> + % if (not stash('from_profile') and not stash('from_timeline')) { + <div class="center-align"> + %= include '_format_train', journey => $journey + </div> % } <div class="center-align countdown" data-duration="<%= $journey->{journey_duration} // 0 %>" - data-arrival="<%= $journey->{real_arrival}->epoch %>"> - % if ($journey->{departure_countdown} > 120) { - Abfahrt in <%= sprintf('%.f', $journey->{departure_countdown} / 60) %> Minuten + % if (param('token')) { + data-token="<%= $journey->{dep_eva} %>-<%= $journey->{timestamp}->epoch % 337 %>-<%= $journey->{sched_departure}->epoch %>" % } - % elsif ($journey->{departure_countdown} > 60) { - Abfahrt in einer Minute + data-arrival="<%= $journey->{real_arrival}->epoch %>"> + % if ($journey->{departure_countdown} > 60) { + Abfahrt in <%= journeys->min_to_human(int($journey->{departure_countdown} / 60)) %> % } % elsif ($journey->{departure_countdown} > 0) { Abfahrt in weniger als einer Minute % } % elsif (defined $journey->{arrival_countdown}) { % if ($journey->{arrival_countdown} > 60) { - Ankunft in <%= sprintf('%.f', $journey->{arrival_countdown} / 60) %> - Minute<%= sprintf('%.f', $journey->{arrival_countdown} / 60) == 1 ? '' : 'n' %> + Ankunft in <%= journeys->min_to_human(int($journey->{arrival_countdown} / 60)) %> % } % elsif ($journey->{arrival_countdown} > 0) { Ankunft in weniger als einer Minute @@ -49,8 +60,8 @@ <div class="progress" style="height: 1ex;"> <div class="determinate" style="width: <%= sprintf('%.2f', 100 * ($journey->{journey_completion} // 0)); %>%;"></div> </div> - </p> - <p> + </div> + <div class="status-card-progress-annot"> <div style="float: left;"> <b><%= $journey->{dep_name} %></b><br/> <b><%= $journey->{real_departure}->strftime('%H:%M') %></b> @@ -80,19 +91,32 @@ % if ($journey->{arr_name} and $station->[0] eq $journey->{arr_name}) { % last; % } - % if (($station->[1]{rt_arr_countdown} // 0) > 0) { - <%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %> - % if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60); + % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') + % if ($station->[2]{arr_delay}) { + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } - % if (($station->[1]{rt_dep_countdown} // 0) > 0) { - <%= $station->[0] %><br/> - <%= $station->[1]{rt_arr}->strftime('%H:%M') %> → - <%= $station->[1]{rt_dep}->strftime('%H:%M') %> - % if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60); + % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{dep}) { + %= $station->[0] + <br/> + % if ($station->[2]{arr}) { + <%= $station->[2]{arr}->strftime('%H:%M') %> → + % } + %= $station->[2]{dep}->strftime('%H:%M') + % if ($station->[2]{dep_delay}) { + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } @@ -105,37 +129,145 @@ % if ($journey->{arr_name} and $station->[0] eq $journey->{arr_name}) { % last; % } - % if (($station->[1]{rt_arr_countdown} // 0) > 0) { + % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { Nächster Halt:<br/> - <%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %> - % if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60); + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') + % if ($station->[2]{arr_delay}) { + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } - % if (($station->[1]{rt_dep_countdown} // 0) > 0) { + % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{arr} and $station->[2]{dep}) { Aktueller Halt:<br/> - <%= $station->[0] %><br/> - <%= $station->[1]{rt_arr}->strftime('%H:%M') %> → - <%= $station->[1]{rt_dep}->strftime('%H:%M') %> - % if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) { - %= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60); + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') + → + %= $station->[2]{dep}->strftime('%H:%M') + % if ($station->[2]{dep_delay}) { + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } % } </div> - </p> + </div> + % if ($journey->{extra_data}{cancelled_destination}) { + <div style="margin-bottom: 2ex;"> + <i class="material-icons tiny" aria-hidden="true">error</i> + Der Halt an der Zielstation <b><%= + $journey->{extra_data}{cancelled_destination} %></b> entfällt. + </div> + % } % if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) { - <p style="margin-bottom: 2ex;"> + <div style="margin-top: 2ex;"> <ul> % for my $message (reverse @{$journey->{messages} // []}) { % if ($journey->{sched_departure}->epoch - $message->[0]->epoch < 1800) { <li> <i class="material-icons tiny">warning</i> <%= $message->[0]->strftime('%H:%M') %>: <%= $message->[1] %></li> % } % } + % for my $message (@{$journey->{extra_data}{qos_msg} // []}) { + <li> <i class="material-icons tiny">info</i> <%= $message->[0]->strftime('%H:%M') %>: <%= $message->[1] %></li> + % } </ul> - </p> + </div> + % } + % if (@{$journey->{extra_data}{him_msg} // []}) { + <div style="margin-top: 2ex;"> + <ul> + % for my $message (@{$journey->{extra_data}{him_msg} // []}) { + % if (not stash('from_timeline') or $message->{prio} and $message->{prio} eq 'HOCH') { + <li> <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %></li> + % } + % } + </ul> + </div> + % } + % if (stash('station_coordinates')) { + <div id="map" style="height: 70vh;"> + </div> + %= include '_map', with_map_header => 0, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') + % } + % if ( @{$journey->{wagongroups} // []} ) { + % if (stash('from_timeline')) { + <div class="wagons" style="margin-top: 2ex;"> + % for my $wagongroup (@{$journey->{wagongroups}}) { + %= $wagongroup->{desc} // $wagongroup->{name} + % if ($wagongroup->{designation}) { + „<%= $wagongroup->{designation} %>“ + % } + % if ($wagongroup->{to}) { + → <%= $wagongroup->{to} %> + % } + <br/> + % } + </div> + % } + % else { + <div class="wagons" style="margin-top: 2ex;"> + Wagen:<br/> + %= include '_wagons', wagongroups => $journey->{wagongroups}; + </div> + % } + % } + % if (not stash('from_timeline')) { + <div style="margin-top: 2ex;"> + Route:<br/> + % my $before = 1; + % my $within = 0; + % my $at_startstop = 0; + % for my $station (@{$journey->{route}}) { + % if (($station->[1] and $station->[1] == $journey->{dep_eva}) or $station->[0] eq $journey->{dep_name}) { + % $within = 1; $at_startstop = 1; + % } + % elsif ($journey->{arr_eva} and (($station->[1] and $station->[1] == $journey->{arr_eva}) or $station->[0] eq $journey->{arr_name})) { + % $within = 0; $at_startstop = 1; + % } + % else { + % $at_startstop = 0; + % } + <span style="color: #808080;"> + % if ($before and $station->[2]{sched_dep}) { + %= $station->[2]{sched_dep}->strftime('%H:%M') + % } + % elsif (not $before and $station->[2]{sched_arr}) { + %= $station->[2]{sched_arr}->strftime('%H:%M') + % } + </span> + % if ($at_startstop or $within) { + %= $station->[0] + % } + % else { + <span style="color: #808080;"><%= $station->[0] %></span> + % } + <span> + %= include '_show_load_icons', station => $station + </span> + <span style="color: #808080;"> + % if ($before and $station->[2]{rt_dep} and $station->[2]{dep_delay}) { + %= sprintf('%+d', $station->[2]{dep_delay} / 60) + % } + % elsif (not $before and $station->[2]{rt_arr} and $station->[2]{arr_delay}) { + %= sprintf('%+d', $station->[2]{arr_delay} / 60) + % } + </span> + % if (($station->[1] and $station->[1] == $journey->{dep_eva}) or $station->[0] eq $journey->{dep_name}) { + % $before = 0; + % } + <br/> + % } + </div> % } </div> </div> @@ -144,10 +276,25 @@ <div class="card"> <div class="card-content"> <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title"><%= $name %> ist gerade nicht eingecheckt</span> - <p> - Zuletzt gesehen in <%= $journey->{arr_name} %>. - </p> + % if (stash('from_profile')) { + <span class="card-title">Aktuell nicht eingecheckt</span> + % } + % else { + <span class="card-title"><a href="/p/<%= $name %>"><%= $name %></a> ist gerade nicht eingecheckt</span> + % } + <div> + % if ($journey->{arr_name}) { + Zuletzt gesehen + % if ($journey->{real_arrival}->epoch) { + %= $journey->{real_arrival}->strftime('am %d.%m.%Y') + in <b><%= $journey->{arr_name} %></b> + %= $journey->{real_arrival}->strftime('(Ankunft um %H:%M Uhr)') + % } + % else { + in <b><%= $journey->{arr_name} %></b> + % } + % } + </div> </div> </div> % } diff --git a/templates/_show_load_icons.html.ep b/templates/_show_load_icons.html.ep new file mode 100644 index 0000000..21093b9 --- /dev/null +++ b/templates/_show_load_icons.html.ep @@ -0,0 +1,11 @@ +% if ($station->[2]{load}{SECOND}) { + % my ($first, $second) = load_icon($station->[2]{load}); + % if ($first ne 'help_outline') { + <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> + % } + <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> +% } +% elsif ($station->[2]{efa_load}) { + % my ($icon) = efa_load_icon($station->[2]{efa_load}); + <i class="material-icons tiny" aria-hidden="true"><%= $icon %></i> +% } diff --git a/templates/_timeline-checked-in.html.ep b/templates/_timeline-checked-in.html.ep new file mode 100644 index 0000000..10c5e26 --- /dev/null +++ b/templates/_timeline-checked-in.html.ep @@ -0,0 +1,14 @@ +% for my $journey (@{$journeys}) { + <div class="row"> + <div class="col s12 autorefresh"> + %= include '_public_status_card', name => $journey->{followee_name}, privacy => {}, journey => $journey, from_timeline => 1 + </div> + </div> +% } +% if (not @{$journeys}) { + <div class="row"> + <div class="col s12 autorefresh center-align"> + <i>Gerade sind keine Accounts mit für dich sichtbaren Checkins unterwegs</i> + </div> + </div> +% } diff --git a/templates/_timeline_link.html.ep b/templates/_timeline_link.html.ep new file mode 100644 index 0000000..4b9c2a5 --- /dev/null +++ b/templates/_timeline_link.html.ep @@ -0,0 +1,16 @@ +<div> + <a class="timeline-link" href="/timeline/in-transit"> + % if (@{$timeline} <= 2) { + <strong><%= $timeline->[0]->{followee_name} %></strong> + % } + % if (@{$timeline} == 1) { + ist gerade <%= stash('from_checkin') ? 'auch' : q{} %> unterwegs + % } + % elsif (@{$timeline} == 2) { + und <strong><%= $timeline->[1]->{followee_name} %></strong> sind gerade <%= stash('from_checkin') ? 'auch' : q{} %> unterwegs + % } + % else { + <strong><%= scalar @{$timeline} %></strong> Accounts sind gerade <%= stash('from_checkin') ? 'auch' : q{} %> unterwegs + % } + </a> +</div> diff --git a/templates/_wagons.html.ep b/templates/_wagons.html.ep index 8918273..4090f11 100644 --- a/templates/_wagons.html.ep +++ b/templates/_wagons.html.ep @@ -1,7 +1,22 @@ % for my $wagongroup (@{$wagongroups // []}) { - Wagenverbund <%= $wagongroup->{name} %> von <b><%= $wagongroup->{from} %></b> nach <b><%= $wagongroup->{to} %></b> als <b><%= $journey->{type} %> <%= $wagongroup->{no} %></b><br/> + %= $wagongroup->{desc} // $wagongroup->{name} + % my ($wagon_number) = ($wagongroup->{name} =~ m{ ^ ICE 0* (\d+) $ }x); + % if ($wagongroup->{designation}) { + „<%= $wagongroup->{designation} %>“ + % } + % elsif ($wagon_number and my $group_name = app->ice_name->{$wagon_number}) { + „<%= $group_name %>“ + % } + als <b><%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %></b> + % if ($wagongroup->{from}) { + von <b><%= $wagongroup->{from} %></b> + % } + % if ($wagongroup->{to}) { + nach <b><%= $wagongroup->{to} %></b> + % } + <br/> % for my $wagon (@{$wagongroup->{wagons}}) { - % if (length($wagon->{id}) == 12) { + % if (length($wagon->{id}) == 12 or length($wagon->{id}) == 14) { <span><%= substr($wagon->{id}, 0, 2) %></span><span><%= substr($wagon->{id}, 2, 2) %></span><span><%= substr($wagon->{id}, 4, 1) %></span><span class="wagonclass"><%= substr($wagon->{id}, 5, 3) %></span><span class="wagonnum"><%= substr($wagon->{id}, 8, 3) %></span><span class="checksum"><%= substr($wagon->{id}, 11) %></span> % } % elsif ($wagon->{id}) { diff --git a/templates/about.html.ep b/templates/about.html.ep index 17424f1..e2b148d 100644 --- a/templates/about.html.ep +++ b/templates/about.html.ep @@ -1,11 +1,21 @@ <div class="row"> <div class="col s12"> <a href="https://finalrewind.org/projects/travelynx">travelynx</a> v<%= stash('version') // '???' %><br/> - Entwickelt von <a href="https://twitter.com/derfnull">@derfnull</a><br/> - Backend: + Entwickelt von <a href="https://finalrewind.org">derf</a> + und <a href="https://github.com/derf/travelynx/graphs/contributors">weiteren</a><br/> + <a href="<%= app->config->{ref}{source} // 'https://git.finalrewind.org/travelynx' %>">Quelltext</a> lizensiert unter AGPL v3<br/><br/> + Backends: + <a href="https://finalrewind.org/projects/Travel-Status-DE-DBRIS/">Travel::Status::DE::DBRIS</a> + v<%= $Travel::Status::DE::DBRIS::VERSION %>, + <a href="https://finalrewind.org/projects/Travel-Status-DE-EFA/">Travel::Status::DE::EFA</a> + v<%= $Travel::Status::DE::EFA::VERSION %>, + <a href="https://finalrewind.org/projects/Travel-Status-DE-HAFAS/">Travel::Status::DE::HAFAS</a> + v<%= $Travel::Status::DE::HAFAS::VERSION %>, <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a> - v<%= $Travel::Status::DE::IRIS::VERSION %><br/> - <a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellendaten</a> + v<%= $Travel::Status::DE::IRIS::VERSION %> und + <a href="https://finalrewind.org/projects/Travel-Status-MOTIS/">Travel::Status::MOTIS</a> + v<%= $Travel::Status::MOTIS::VERSION %><br/> + Haltestellendaten © DB Station&Service AG, Europaplatz 1, 10557 Berlin, lizensiert unter CC-BY 4.0 @@ -13,11 +23,26 @@ </div> <div class="row"> + <div class="col s12"> + <p> + Travelynx ist ein kostenfreies, privat betriebenes Projekt ohne + Verfügbarkeitsgarantie. Unangekündigte Downtimes oder eine + kurzfristige Einstellung dieser Seite sind nicht vorgesehen, aber + möglich. Feature Requests, Bug Reports und sonstige Nachrichten + werden je nach Kapazität und Motivation zeitnah, verzögert oder gar + nicht bearbeitet / beantwortet. + </p> + </div> +</div> + +<div class="row"> <div class="col s12 m12 l4 center-align" style="margin-top: 1em;"> - <a href="https://twitter.com/derfnull" class="waves-effect waves-light btn"><i class="material-icons left">message</i>Kontakt</a> + <a href="https://social.skyshaper.org/derf" class="waves-effect waves-light btn"><i class="material-icons left">message</i>Kontakt</a> </div> <div class="col s12 m12 l4 center-align" style="margin-top: 1em;"> - <a href="https://github.com/derf/travelynx/issues" class="waves-effect waves-light btn"><i class="material-icons left">bug_report</i>Bugs?</a> + % if (my $issue_url = app->config->{ref}{issues}) { + <a href="<%= $issue_url %>" class="waves-effect waves-light btn"><i class="material-icons left">bug_report</i>Bugs?</a> + % } </div> <div class="col s12 m12 l4 center-align" style="margin-top: 1em;"> <a href="/changelog" class="waves-effect waves-light btn"><i class="material-icons left">list</i>Changelog</a> diff --git a/templates/account.html.ep b/templates/account.html.ep index e6a4576..e4bf38d 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -1,4 +1,4 @@ -% if (my $invalid = stash('invalid')) { +% if (my $invalid = flash('invalid')) { %= include '_invalid_input', invalid => $invalid % } @@ -7,7 +7,10 @@ <div class="col s12"> <div class="card success-color"> <div class="card-content white-text"> - % if ($success eq 'mail') { + % if ($success eq 'name') { + <span class="card-title">Name geändert</span> + % } + % elsif ($success eq 'mail') { <span class="card-title">Mail-Adresse geändert</span> % } % elsif ($success eq 'password') { @@ -16,28 +19,38 @@ % elsif ($success eq 'privacy') { <span class="card-title">Einstellungen zu öffentlichen Account-Daten geändert</span> % } + % elsif ($success eq 'social') { + <span class="card-title">Einstellungen zur Interaktionen mit anderen Accounts geändert</span> + % } + % elsif ($success eq 'traewelling') { + <span class="card-title">Träwelling-Verknüpfung aktualisiert</span> + % } % elsif ($success eq 'use_history') { <span class="card-title">Einstellungen zu vorgeschlagenen Verbindungen geändert</span> % } % elsif ($success eq 'webhook') { <span class="card-title">Web Hook aktualisiert</span> % } + % elsif ($success eq 'clear_notifications') { + <span class="card-title">Benachrichtigungen gelesen</span> + % } </div> </div> </div> </div> % } -<h1>Account</h1> % my $acc = current_user(); -% my $hook = get_webhook(); -% my $use_history = account_use_history($acc->{id}); +% my $hook = users->get_webhook(uid => $acc->{id}); +% my $traewelling = traewelling->get(uid => $acc->{id}); +% my $use_history = users->use_history(uid => $acc->{id}); <div class="row"> <div class="col s12"> + <h2>Account</h2> <table class="striped"> <tr> <th scope="row">Name</th> - <td><%= $acc->{name} %></td> + <td><a href="/account/name"><i class="material-icons">edit</i></a><%= $acc->{name} %></td> </tr> <tr> <th scope="row">Mail</th> @@ -60,20 +73,31 @@ </td> </tr> <tr> - <th scope="row">Öffentliche Daten</th> + <th scope="row">Sichtbarkeit</th> <td> <a href="/account/privacy"><i class="material-icons">edit</i></a> - % if ($acc->{is_public} == 0) { - <span style="color: #999999;">Keine</span> - % } - % if ($acc->{is_public} & 0x01) { - Aktueller Status (nur für angemeldete Accounts) + <i class="material-icons">check</i><i class="material-icons"><%= visibility_icon($acc->{default_visibility_str}) %></i> + • <i class="material-icons">history</i><i class="material-icons"><%= visibility_icon($acc->{past_visibility_str}) %></i> + </td> + </tr> + <tr> + <th scope="row">Interaktion</th> + <td> + <a href="/account/social"><i class="material-icons">edit</i></a> + % if ($acc->{accept_follows}) { + <span>Accounts können dir direkt folgen</span> % } - % elsif ($acc->{is_public} & 0x02) { - Aktueller Status + % elsif ($acc->{accept_follow_requests}) { + <span>Accounts können dir auf Anfrage folgen + % if ($num_rx_follow_requests == 1) { + – <a href="/account/social/follow-requests-received"><strong>eine</strong> offene Anfrage</a> + % } elsif ($num_rx_follow_requests) { + – <a href="/account/social/follow-requests-received"><strong><%= $num_rx_follow_requests %></strong> offene Anfragen</a> + % } + </span> % } - % if ($acc->{is_public} & 0x04) { - mit Kommentar + % else { + <span style="color: #999999;">Accounts können dir nicht folgen</span> % } </td> </tr> @@ -88,13 +112,45 @@ Aktiv, noch nicht ausgeführt % } % elsif ($hook->{errored}) { - Aktiv, fehlerhaft <i class="material-icons">error</i> + Aktiv, fehlerhaft <i class="material-icons" aria-hidden="true">error</i> % } % else { Aktiv % } </td> </tr> + % if (config->{traewelling}{oauth}) { + <tr> + <th scope="row">Träwelling</th> + <td> + Wird wegen Inkompatibilität zwischen bahn.de und transitous derzeit nicht unterstützt + <!-- + <a href="/account/traewelling"><i class="material-icons">edit</i></a> + % if (not ($traewelling->{token})) { + <span style="color: #999999;">Nicht verknüpft</span> + % } + % elsif ($traewelling->{errored}) { + Fehlerhaft <i class="material-icons" aria-hidden="true">error</i> + % } + % else { + Verknüpft mit <%= $traewelling->{data}{user_name} // $traewelling->{email} %> + % if ($traewelling->{expired}) { + – Login-Token abgelaufen <i class="material-icons" aria-hidden="true">error</i> + % } + % elsif ($traewelling->{expiring}) { + – Login-Token läuft bald ab <i class="material-icons" aria-hidden="true">warning</i> + % } + % elsif ($traewelling->{pull_sync}) { + – Checkins in Träwelling werden von travelynx übernommen + % } + % elsif ($traewelling->{push_sync}) { + – Checkins in travelynx werden zu Träwelling weitergereicht + % } + % } + --> + </td> + </tr> + % } <tr> <th scope="row">Registriert am</th> <td><%= $acc->{registered_at}->strftime('%d.%m.%Y %H:%M') %></td> @@ -113,15 +169,101 @@ </div> </div> -<h2>API</h2> -% my $token = get_api_token(); +% if ($num_rx_follow_requests or $num_tx_follow_requests or $num_followers or $num_following or $num_blocked) { + <div class="row"> + <div class="col s12"> + <h2>Interaktion</h2> + <p> + <a href="/p/<%= $acc->{name} %>">Öffentliches Profil</a> + </p> + <table class="striped"> + <tr> + <th scope="row">Anfragen</th> + <td> + % if ($num_rx_follow_requests == 0) { + <span style="color: #999999;">keine eingehend</span> + % } + % elsif ($num_rx_follow_requests == 1) { + <a href="/account/social/follow-requests-received"><strong>eine</strong> eingehend</a> + % } + % else { + <a href="/account/social/follow-requests-received"><strong><%= $num_rx_follow_requests %></strong> eingehend</a> + % } + <br/> + % if ($num_tx_follow_requests == 0) { + <span style="color: #999999;">keine ausgehend</span> + % } + % elsif ($num_tx_follow_requests == 1) { + <a href="/account/social/follow-requests-sent"><strong>eine</strong> ausgehend</a> + % } + % else { + <a href="/account/social/follow-requests-sent"><strong><%= $num_tx_follow_requests %></strong> ausgehend</a> + % } + </td> + </tr> + <tr> + <th scope="row">Dir folg<%= $num_followers == 1 ? 't' : 'en' %></th> + <td> + % if ($num_followers == 0) { + <span style="color: #999999;">keine Accounts</span> + % } + % elsif ($num_followers == 1) { + <a href="/account/social/followers"><strong>ein</strong> Account</a> + % } + % else { + <a href="/account/social/followers"><strong><%= $num_followers %></strong> Accounts</a> + % } + </td> + </tr> + <tr> + <th scope="row">Du folgst</th> + <td> + % if ($num_following == 0) { + <span style="color: #999999;">keinen Accounts</span> + % } + % elsif ($num_following == 1) { + <a href="/account/social/follows"><strong>einem</strong> Account</a> + % } + % else { + <a href="/account/social/follows"><strong><%= $num_following %></strong> Accounts</a> + % } + </td> + </tr> + <tr> + <th scope="row">Blockiert</th> + <td> + % if ($num_blocked == 0) { + <span style="color: #999999;">keine Accounts</span> + % } + % elsif ($num_blocked == 1) { + <a href="/account/social/blocks"><strong>ein</strong> Account</a> + % } + % else { + <a href="/account/social/blocks"><strong><%= $num_blocked %></strong> Accounts</a> + % } + </td> + </tr> + </table> + </div> + </div> +% } +% else { + <div class="row"> + <div class="col s12"> + <a href="/p/<%= $acc->{name} %>">Öffentliches Profil</a> + </div> + </div> +% } + +% my $token = stash('api_token') // {}; <div class="row"> <div class="col s12"> + <h2>API</h2> <p> Die folgenden API-Token erlauben den passwortlosen automatisierten Zugriff auf API-Endpunkte. Bitte umsichtig behandeln – sobald ein Token gesetzt ist, können damit ohne Logindaten alle zugehörigen API-Aktionen - ausgeführt werden. + ausgeführt werden. <a href="/api">Dokumentation</a>. </p> <table class="striped"> <tr> @@ -170,7 +312,6 @@ %= end </td> </tr>--> - % if (app->mode eq 'development') { <tr> <th scope="row">Travel</th> <td> @@ -217,22 +358,13 @@ %= end </td> </tr> - % } </table> </div> </div> <div class="row"> <div class="col s12"> - <a href="/api">Dokumentation</a> - </div> -</div> - - -<h2>Export</h2> - -<div class="row"> - <div class="col s12"> + <h2>Export</h2> <ul> <li><a href="/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li> </ul> @@ -240,9 +372,9 @@ </div> % if (not $acc->{deletion_requested}) { - <h2>Löschen</h2> <div class="row"> <div class="col s12"> + <h2>Löschen</h2> <p> Der Löschauftrag wird vorgemerkt und erst nach drei Tagen umgesetzt, bis dahin kann er jederzeit zurückgenommen werden. Nach diff --git a/templates/add_intransit.html.ep b/templates/add_intransit.html.ep new file mode 100644 index 0000000..9d711c9 --- /dev/null +++ b/templates/add_intransit.html.ep @@ -0,0 +1,93 @@ +<h1>Manuell einchecken</h1> +% if ($error) { + <div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">Ungültige Eingabe</span> + <p><%= $error %></p> + </div> + </div> + </div> + </div> +% } +<div class="row"> + <div class="col s12"> + <p> + Falls die gesuchte Abfahrt nicht vom ausgewählten Backend verfügbar ist, z.B. da es sich um eine Sonderfahrt handelt, ist hier ein manueller Checkin möglich. + Nach dem Checkin werden alle Daten so beibehalten wie sie eingegeben wurden; Änderungen sind erst nach dem Auschecken möglich. + </p> + <ul> + <li>Eingabe der Fahrt als „Typ Linie Nummer“ oder „Typ Nummer“, z.B. + „ICE 100“, „S 1 31133“ oder „ABR RE11 26720“</li> + <li>Wenn Nummer nicht bekannt oder vorhanden: einen beliebigen Integer eintragen, z.B. „S 5X 0“ oder „U 11 0“</li> + <li>Zeitangaben im Format DD.MM.YYYY HH:MM</li> + <li>Das ausgewählte Backend bestimmt die verfügbaren Halte für Start, Ziel und Route. Siehe auch <a href="/static/stops.csv">stops.csv</a></li> + </ul> + </div> +</div> +<div class="row"> + <div class="col s12 center-align"> + % if (current_user->{backend_id}) { + <a href="/account/select_backend?redirect_to=/checkin/add" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= current_user->{backend_name} %></a> + % } + % else { + <a href="/account/select_backend?redirect_to=/checkin/add" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">train</i>IRIS</a> + % } + </div> +</div> +%= form_for '/checkin/add' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="input-field col s12"> + %= text_field 'train', id => 'train', class => 'validate', required => undef, pattern => '[0-9a-zA-Z]+ +[0-9a-zA-Z]* *[0-9]+' + <label for="train">Fahrt (Typ Linie Nummer)</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_field 'dep_station', id => 'dep_station', class => 'autocomplete validate', autocomplete => 'off', required => undef + <label for="dep_station">Start (Name oder ID)</label> + </div> + <div class="input-field col s12"> + %= text_field 'sched_departure', id => 'sched_departure', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' + <label for="sched_departure">Geplante Abfahrt</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_field 'arr_station', id => 'arr_station', class => 'autocomplete validate', autocomplete => 'off', required => undef + <label for="arr_station">Ziel (Name oder ID)</label> + </div> + <div class="input-field col s12"> + %= text_field 'sched_arrival', id => 'sched_arrival', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' + <label for="sched_arrival">Geplante Ankunft</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_area 'route', id => 'route', class => 'materialize-textarea' + <label for="route">Halte (optional)</label><br/> + Eine Station pro Zeile, wahlweise Unterwegshalte oder komplette Route<br/> + Format: <i>Name</i> oder <i>Name</i> @ <i>Zeitpunkt</i> (inkl. Datum, siehe oben) + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_field 'comment' + <label for="comment">Kommentar</label> + </div> + </div> + <div class="row"> + <div class="col s3 m3 l3"> + </div> + <div class="col s6 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Einchecken + <i class="material-icons right">send</i> + </button> + </div> + <div class="col s3 m3 l3"> + </div> + </div> +%= end diff --git a/templates/add_journey.html.ep b/templates/add_journey.html.ep index c3bc01c..cade37e 100644 --- a/templates/add_journey.html.ep +++ b/templates/add_journey.html.ep @@ -1,13 +1,15 @@ -<h1>Zugfahrt eingeben</h1> -% if (not get_oldest_journey_ts()) { +<h1>Fahrt eingeben</h1> +% if (not journeys->get_oldest_ts(uid => current_user->{id})) { <div class="row"> <div class="col s12"> <div class="card info-color"> <div class="card-content"> <span class="card-title">Hinweis</span> <p>travelynx ist darauf ausgelegt, über die Hauptseite in - Echtzeit in Züge ein- und auszuchecken. Die manuelle - Eingabe von Zugfahrten ist nur als Notlösung vorgesehen.</p> + Echtzeit in Verkehrsmittel ein- und auszuchecken. Die manuelle + Eingabe von Fahrten ist nur als Notlösung vorgesehen. + Hier werden derzeit nur Zugfahrten im DB-Netz + (IRIS-Backend) unterstützt.</p> </div> </div> </div> @@ -28,18 +30,31 @@ <div class="row"> <div class="col s12"> <ul> - <li>Eingabe des Zugs als „Zug Typ Nummer“ oder „Zug Nummer“, z.B. + <li>Eingabe der Fahrt als „Typ Linie Nummer“ oder „Typ Nummer“, z.B. „ICE 100“, „S 1 31133“ oder „ABR RE11 26720“</li> + <li>Wenn Nummer nicht bekannt oder vorhanden: einen beliebigen Integer eintragen, z.B. „S 5X 0“ oder „U 11 0“</li> <li>Zeitangaben im Format DD.MM.YYYY HH:MM</li> + <li>Das ausgewählte Backend bestimmt die verfügbaren Halte für Start, Ziel und Route. Siehe auch <a href="/static/stops.csv">stops.csv</a></li> </ul> </div> </div> +<div class="row"> + <div class="col s12 center-align"> + % my $self_link = url_for('add_journey'); + % if (current_user->{backend_id}) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= current_user->{backend_name} %></a> + % } + % else { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">train</i>IRIS</a> + % } + </div> +</div> %= form_for '/journey/add' => (method => 'POST') => begin %= csrf_field <div class="row"> <div class="input-field col s12 m6 l6"> %= text_field 'train', id => 'train', class => 'validate', required => undef, pattern => '[0-9a-zA-Z]+ +[0-9a-zA-Z]* *[0-9]+' - <label for="train">Zug (Typ Linie Nummer)</label> + <label for="train">Fahrt (Typ Linie Nummer)</label> </div> <div class="input-field col s12 m6 l6"> <label> @@ -50,8 +65,8 @@ </div> <div class="row"> <div class="input-field col s12"> - %= text_field 'dep_station', id => 'dep_station', class => 'autocomplete validate', required => undef - <label for="dep_station">Start (Name oder DS100)</label> + %= text_field 'dep_station', id => 'dep_station', class => 'autocomplete validate', autocomplete => 'off', required => undef + <label for="dep_station">Start (Name oder ID)</label> </div> <div class="input-field col s12"> %= text_field 'sched_departure', id => 'sched_departure', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' @@ -64,8 +79,8 @@ </div> <div class="row"> <div class="input-field col s12"> - %= text_field 'arr_station', id => 'arr_station', class => 'autocomplete validate', required => undef - <label for="arr_station">Ziel (Name oder DS100)</label> + %= text_field 'arr_station', id => 'arr_station', class => 'autocomplete validate', autocomplete => 'off', required => undef + <label for="arr_station">Ziel (Name oder ID)</label> </div> <div class="input-field col s12"> %= text_field 'sched_arrival', id => 'sched_arrival', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' @@ -79,7 +94,9 @@ <div class="row"> <div class="input-field col s12"> %= text_area 'route', id => 'route', class => 'materialize-textarea' - <label for="route">Unterwegshalte (optional, eine Station pro Zeile, DS100 möglich)</label> + <label for="route">Halte (optional)</label><br/> + Eine Station pro Zeile, wahlweise Unterwegshalte oder komplette Route<br/> + Format: <i>Name</i> oder <i>Name</i> @ <i>Zeitpunkt</i> (inkl. Datum, siehe oben) </div> </div> <div class="row"> diff --git a/templates/api_documentation.html.ep b/templates/api_documentation.html.ep index e5d026f..099474c 100644 --- a/templates/api_documentation.html.ep +++ b/templates/api_documentation.html.ep @@ -1,10 +1,6 @@ % my $api_root = $self->url_for('/api/v1')->to_abs->scheme('https'); -% my $token = {}; -% my $uid; -% if (is_user_authenticated()) { - % $uid = current_user()->{id}; - % $token = get_api_token(); -% } +% my $token = stash('api_token') // {}; +% my $uid = stash('uid') // q{}; <h1>API</h1> @@ -31,32 +27,55 @@ <p style="font-family: Monospace;"> {<br/> "deprecated" : true / false, (falls true: Diese API-Version wird irgendwann abgeschaltet, bitte auf eine neue umsteigen)<br/> + "actionTime" : 1234567, (UNIX-Timestamp des letzten Checkin/Checkout)<br/> "checkedIn" : true / false,<br/> + "comment": "Kommentar",<br/> + "backend": {<br/> + "id": 1,<br/> + "name": "DB",<br/> + "type": "HAFAS",<br/> + },<br/> "fromStation" : { (letzter Checkin)<br/> "name" : "Essen Hbf",<br/> - "ds100" : "EE",<br/> + "ds100" : "EE", (ggf. null)<br/> "uic" : 8000098,<br/> "latitude" : 51.451355,<br/> "longitude" : 7.014793,<br/> + "platform" : "12", (ggf. null)<br/> "scheduledTime": 1556083680,<br/> - "realTime": 1556083680,<br/> + "realTime": 1556083680<br/> },<br/> "toStation" : { (zugehöriger Checkout. Wenn noch nicht eingetragen, sind alle Felder null)<br/> "name" : "Essen Stadtwald",<br/> - "ds100" : "EESA",<br/> + "ds100" : "EESA", (ggf. null)<br/> "uic" : 8001896,<br/> "latitude" : 51.422853,<br/> "longitude" : 7.023296,<br/> + "platform" : "2", (ggf. null)<br/> "scheduledTime": 1556083980, (ggf. null)<br/> - "realTime": 1556083980, (ggf. null)<br/> + "realTime": 1556083980 (ggf. null)<br/> + },<br/> + "intermediateStops" : [ (Unterwegshalte zwischen fromStation und toStation) <br/> + {<br/> + "name" : "Essen Süd",<br/> + "scheduledArrival" : 1556083800, (ggf. null)<br/> + "realArrival" : 1556083800, (ggf. null, nach Ankunft identisch mit scheduledArrival)<br/> + "scheduledDeparture" : 1556083860, (ggf. null)<br/> + "realDeparture" : 1556083860 (ggf. null, nach Abfahrt identisch mit scheduledDeparture)<br/> },<br/> + …<br/> + ],<br/> "train" : {<br/> - "type" : "S", (aktueller / letzter Zugtyp)<br/> - "line" : "6", (Linie als String, nicht immer numerisch, ggf. null)<br/> - "no" : "30634", (Zugnummer als String)<br/> - "id" : "7512500863736016593", (IRIS-spezifische Zug-ID)<br/> + "type" : "S", (aktueller / letzter Fahrttyp)<br/> + "line" : "6", (Linie als String, nicht immer numerisch, ggf. null)<br/> + "no" : "30634", (Fahrtnummer als String, ggf. null oder leer)<br/> + "id" : "7512500863736016593" (IRIS- oder HAFAS-spezifische Fahrt-ID)<br/> + "hafasId" : "1|224479|0|80|30082023" (HAFAS-spezifische Fahrt-ID falls bekannt, ggf. null)<br/> },<br/> - "actionTime" : 1234567, (UNIX-Timestamp des letzten Checkin/Checkout)<br/> + "visibility" : {<br/> + "desc": "private" / "unlisted" / "followers" / "travelynx" / "public",<br/> + "level": 10 / 30 / 60 / 80 / 100<br/> + }<br/> } </p> <p> @@ -65,8 +84,6 @@ </div> </div> -% if (app->mode eq 'development') { - <h2>Travel</h2> <div class="row"> <div class="col s12"> @@ -74,26 +91,26 @@ Checkin per API. Sobald eine Zielstation bekannt ist, erfolgt der Checkout wie beim Webinterface automatisch zehn Minuten nach Ankunft. Bitte beachten: Es wird nicht überprüft, ob die angegebene Zielstation - in der vorgesehenen Route des Zugs vorkommt oder nicht. + in der vorgesehenen Route der Fahrt vorkommt oder nicht. </p> <p> - Falls du zum Checkinzeitpunkt bereits in einen anderen Zug eingecheckt + Falls du zum Checkinzeitpunkt bereits in eine andere Fahrt eingecheckt bist, wirst du zunächst am gewählten Startbahnhof aus diesem ausgecheckt. - Der Checkout erfolgt unabhängig davon, ob der vorherige Zug an dieser + Der Checkout erfolgt unabhängig davon, ob die vorherige Fahrt an dieser Station verkehrt oder nicht. Falls nach einem Checkin ohne Zielwahl innerhalb von 48 Stunden kein Zielbahnhof nachgetragen wird, wird der Checkin automatisch rückgängig gemacht. </p> <p> - Das Verhalten des Checkout-Endpunkts hängt vom Zeitpunkt ab. Wenn der - Zug den angegebenen Zielbahnhof bereits erreicht hat, wird dort + Das Verhalten des Checkout-Endpunkts hängt vom Zeitpunkt ab. Wenn die + Fahrt den angegebenen Zielbahnhof bereits erreicht hat, wird dort ausgecheckt. Andernfalls wird das Reiseziel aktualisiert und etwa zehn Minuten nach Ankunft automatisch ausgecheckt. </p> <p style="font-family: Monospace;"> curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>"}' <%= $api_root %>/travel </p> - <p>Payload zum Einchecken, optional mit Zielwahl:</p> + <p>Payload zum Einchecken per IRIS-Backend (Schienenverkehr DE/DB), optional mit Zielwahl:</p> <p style="font-family: Monospace;"> {<br/> "token" : "<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>",<br/> @@ -107,6 +124,21 @@ "comment" : "Beliebiger Text" (optional, überschreibt vorherigen Kommentar)<br/> } </p> + <p>Payload zum Einchecken per HAFAS-Backend (Nahverkehr und außerhalb DE/DB), optional mit Zielwahl. fromStation und toStation müssen mit den Unterwegshalten übereinstimmen, z.B. "Hauptbahnhof (U Gleis 2+4), Essen (Ruhr)" statt "Essen Hbf".</p> + <p style="font-family: Monospace;"> + {<br/> + "token" : "<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>",<br/> + "action" : "checkin",<br/> + "dbris" : "bahn.de", (DBRIS-Instanz – Default: bahn.de)<br/> + "hafas" : null, (HAFAS-Instanz, falls verwendet, sonste null)<br/> + "train" : {<br/> + "journeyID" : "2|#VN#1#ST#1742845592#PI#0#ZI#315136#TA#0#DA#270325#1S#8000080#1T#1841#LS#8006486#LT#2024#PU#80#RT#1#CA#RE#ZE#10773#ZB#RE10773#PC#3#FR#8000080#FT#1841#TO#8006486#TT#2024#",<br/> + }<br/> + "fromStation" : 8000080, (Name oder EVA-Nummer – bei bahn.de nur EVA-Nummer)<br/> + "toStation" : 8006486, (optional, Name oder EVA-Nummer – bei bahn.de nur EVA-Nummer)<br/> + "comment" : "Beliebiger Text" (optional, überschreibt vorherigen Kommentar)<br/> + } + </p> <p>Payload zur Wahl eines neuen Ziels, wenn bereits eingecheckt:</p> <p style="font-family: Monospace;"> {<br/> @@ -152,7 +184,16 @@ <div class="row"> <div class="col s12"> <p> - Manueller Import vergangener Zugfahrten (eine Fahrt pro API-Aufruf). + Manueller Import vergangener Fahrten (eine Fahrt pro API-Aufruf). + </p> + <p> + Bitte beachten: fromStation, toStation und intermediateStops werden + mit Fuzzy Matching eingelesen. Falls ein unbekannter Stationsname + einer anderen, bekannten Station hinreichend ähnelt, kann dieser + dadurch ersetzt werden. Bei Unsicherheiten empfiehlt sich ein + <em>dryRun</em> und ein Vergleich der zurückgegebenen Stationsnamen + mit den eingegebenen. Komplett unbekannte Stationsnamen führen + standardmäßig zu einem Fehler (siehe <em>lax</em>) </p> <p style="font-family: Monospace;"> curl -X POST -H "Content-Type: application/json" -d '{"token":"<%= $uid %>-<%= $token->{import} // 'TOKEN' %>"}' <%= $api_root %>/import @@ -161,28 +202,26 @@ <p style="font-family: Monospace;"> {<br/> "token" : "<%= $uid %>-<%= $token->{import} // 'TOKEN' %>",<br/> - "dryRun" : true/false, (optional: wenn true, wird die Eingabe validiert, aber keine Zugfahrt angelegt)<br/> - "lax" : true/Fals, (optional: wenn true, werden unbekannte Unterwegshalte akzeptiert)<br/> - "cancelled" : true/false, (Zugausfall?)<br/> + "dryRun" : true/false, (optional: wenn true, wird die Eingabe validiert, aber keine Fahrt angelegt)<br/> + "lax" : true/false, (optional: wenn true, werden unbekannte Unterwegshalte akzeptiert)<br/> + "cancelled" : true/false, (Ausfall?)<br/> "train" : {<br/> - "type" : "S", (Zugtyp, z.B. ICE, RE, S)<br/> + "type" : "S", (Typ, z.B. ICE, RE, S, U)<br/> "line" : "6", (Linie als String, bei Zügen ohne Linie wie IC/ICE u.ä. null)<br/> - "no" : "30634", (Zugnummer als String)<br/> + "no" : "30634", (Nummer als String, ggf. null oder leer)<br/> },<br/> "fromStation" : { (Start / Checkin)<br/> - "name" : "Essen Hbf", (Name oder DS100)<br/> - "scheduledTime": 1556083680, (UNIX-Timestamp)<br/> - "realTime": 1556083680, (UNIX-Timestamp, optional, default == scheduledTime)<br/> + "name" : "Essen Hbf", (Name oder DS100)<br/> + "scheduledTime": 1556083680, (UNIX-Timestamp)<br/> + "realTime": 1556083680, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> "toStation" : { (Ziel / Checkout)<br/> "name" : "Essen Stadtwald", (Name oder DS100)<br/> "scheduledTime": 1556083980, (UNIX-Timestamp)<br/> "realTime": 1556083980, (UNIX-Timestamp, optional, default == scheduledTime)<br/> },<br/> - "route" : [ (optionale Liste mit Unterwegshalten als Name oder DS100, darf keine Stationen vor Checkin oder nach Checkout beinhalten)<br/> - "Essen Hbf",<br/> + "intermediateStops" : [ (optionale Liste mit Unterwegshalten als Name oder DS100, darf keine Stationen vor Checkin oder nach Checkout beinhalten)<br/> "Essen Süd",<br/> - "Essen Stadtwald"<br/> ],<br/> "comment" : "Beliebiger Text" (optionaler Freitext-Kommentar)<br/> } @@ -193,8 +232,10 @@ <p style="font-family: Monospace;"> {<br/> "success" : true,<br/> - "id" : 1234, (ID der eingetragenen Zugfahrt)<br/> - "result" : { ... } (Eingetragene Daten, Datenformat nicht näher spezifiziert und beliebig variabel)<br/> + "deprecated" : true / false, (falls true: Diese API-Version wird irgendwann abgeschaltet, bitte auf eine neue umsteigen)<br/> + "id" : 1234, (ID der eingetragenen Fahrt)<br/> + "result" : { ... } (Eingetragene Daten. Das Datenformat kann sich + ohne Berücksichtigung der API-Version ändern)<br/> } </p> <p> @@ -203,10 +244,9 @@ <p style="font-family: Monospace;"> {<br/> "success" : false,<br/> + "deprecated" : true / false, (falls true: Diese API-Version wird irgendwann abgeschaltet, bitte auf eine neue umsteigen)<br/> "error" : "Begründung"<br/> } </p> </div> </div> - -% } diff --git a/templates/bad_gateway.html.ep b/templates/bad_gateway.html.ep new file mode 100644 index 0000000..07bf29e --- /dev/null +++ b/templates/bad_gateway.html.ep @@ -0,0 +1,27 @@ +<div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">502 Bad Gateway</span> + <p> + Das von travelynx genutzte Backend hat einen Fehler zurückgegeben. + travelynx hat keine Möglichkeiten, diese Situation zu beheben. + % if (stash('select_new_backend')) { + Versuche es in ein paar Sekunden bis Minuten noch einmal oder <a href="/account/select_backend">wähle ein anderes Backend</a>. + % } + % else { + Versuche es in ein paar Sekunden bis Minuten noch einmal. + % } + </p> + </div> + </div> + </div> +</div> +<div class="row"> + <div class="col s12"> + <p>Details:</p> + <p style="font-family: monospace;"> + %= $message + </p> + </div> +</div> diff --git a/templates/bad_request.html.ep b/templates/bad_request.html.ep new file mode 100644 index 0000000..5d401da --- /dev/null +++ b/templates/bad_request.html.ep @@ -0,0 +1,19 @@ +<div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">400 Bad Request</span> + % if (stash('csrf')) { + <p>Ungültiger CSRF-Token. Dieser dient zum Schutz vor Cross-Site Request Forgery.</p> + <p>Falls du von einer externen Seite hierhin geleitet wurdest, wurde möglicherweise (erfolglos) versucht, deinen Account anzugreifen. Falls du von travelynx selbst aus hier angekommen bist, kann es sich um eine fehlerhafte Cookie-Konfiguration im Browser, eine abgelaufene Session (→ bitte nochmal versuchen) oder du einen Bug in travelynx handeln (→ bitte melden).</p> + % } + % elsif (my $m = stash('message')) { + <p><%= $m %></p> + % } + % else { + <p>Diese Anfrage ist ungültig. Ursache kann z.B. eine abgelaufene Session oder ein Bug in travelynx sein.</p> + % } + </div> + </div> + </div> +</div> diff --git a/templates/change_name.html.ep b/templates/change_name.html.ep new file mode 100644 index 0000000..9262734 --- /dev/null +++ b/templates/change_name.html.ep @@ -0,0 +1,47 @@ +% if (my $invalid = stash('invalid')) { + %= include '_invalid_input', invalid => $invalid +% } + +<h1>Name ändern</h1> +<div class="row"> + <div class="col s12"> + <p> + Hier kannst du den Namen deines Accounts ändern. Dieser bestimmt die + URL deiner <a href="/status/<%= $name %>">Status</a>- und <a + href="/p/<%= $name %>">Profilseite</a> und den Accountnamen beim + Anmelden. + </p> + <p> + Beachte, dass der alte Name direkt nach der Umbenennung freigegeben + wird und von anderen Accounts belegt werden kann. Für die alten + Status- und Profilseiten werden keine Weiterleitungen eingerichtet. + </p> + </div> +</div> +%= form_for '/account/name' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">lock</i> + %= password_field 'password', id => 'password', class => 'validate', required => undef, autocomplete => 'current-password' + <label for="password">Aktuelles Passwort</label> + </div> + <div class="input-field col s12"> + <i class="material-icons prefix">account_circle</i> + %= text_field 'name', id => 'account', class => 'validate', required => undef, pattern => '[0-9a-zA-Z_-]+', maxlength => 60 + <label for="email">Neuer Name</label> + </div> + </div> + <div class="row"> + <div class="col s3 m3 l3"> + </div> + <div class="col s6 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="update_name"> + Ändern + <i class="material-icons right">send</i> + </button> + </div> + <div class="col s3 m3 l3"> + </div> + </div> +%= end diff --git a/templates/change_password.html.ep b/templates/change_password.html.ep index 29aa621..c49226a 100644 --- a/templates/change_password.html.ep +++ b/templates/change_password.html.ep @@ -15,12 +15,12 @@ <div class="row"> <div class="input-field col l6 m12 s12"> <i class="material-icons prefix">lock</i> - %= password_field 'newpw', id => 'password', class => 'validate', required => undef, minlength => 8, autocomplete => 'new-password' + %= password_field 'newpw', id => 'password', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' <label for="password">Neues Passwort</label> </div> <div class="input-field col l6 m12 s12"> <i class="material-icons prefix">lock</i> - %= password_field 'newpw2', id => 'password2', class => 'validate', required => undef, minlength => 8, autocomplete => 'new-password' + %= password_field 'newpw2', id => 'password2', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' <label for="password2">Passwort wiederholen</label> </div> </div> diff --git a/templates/changelog.html.ep b/templates/changelog.html.ep index 7ebc624..0d1ecc5 100644 --- a/templates/changelog.html.ep +++ b/templates/changelog.html.ep @@ -2,6 +2,814 @@ <div class="row"> <div class="col s12 m1 l1"> + 2.15 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Manuelle Checkins. Diese verhalten sich analog zu manuell + eingetragenen Fahrten, werden jedoch bis zur planmäßigen + Ankunftszeit als Checkin behandelt. Manuelle Echtzeitdaten-Updates + werden nicht unterstützt. Manuelle Checkins sind nur an Halten + möglich, die dem ausgewählten Backend bekannt sind. Ggf. wird + dieses Feature später um eine Möglichkeit für Echtzeitdaten-Updates + und/oder eine API erweitert. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Erfassung des Betreibers einer Fahrt, sofern verfügbar. + </p> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + EFA-Backends werden nun fast vollständig unterstützt und sind nicht + mehr experimentell. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Das manuelle Eintragen von Fahrten ist nun wieder möglich. Zudem + kann dabei nun ein beliebiges Backend ausgewählt werden; das + ausgewählte Backend bestimmt die verfügbaren Halte. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.14 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Experimentelle Unterstützung für Checkins via EFA-Backends. + Teilweise ist ein Checkin nur bei Fahrten mit Echtzeitdaten + möglich. Hierbei handelt es sich nach aktuellem Stand um eine + Einschränkung der verwendeten Backends. Unterstützung für + ausfallende Fahrten folgt später. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.13 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Experimentelle Unterstützung für Checkins via MOTIS-Backends + (derzeit transitous und RNV). Vielen Dank an <a href="https://github.com/networkException">networkException</a> + für die Implementierung der API und Einbindung in travelynx. + Träwelling-Synchronisierung ist noch nicht wiederhergestellt. + Time zones are currently somewhat wibbly-wobbly timey-wimey. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.12 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Kartografische Visualisierung der Route bei eigenen Checkins und auf + der Statusseite sowie Angaben zu Meldungen, Rollmaterial, Route und + Auslastung auf der Statusseite. Feinheiten wie die Markierung der + geschätzten aktuellen Zugposition oder eine regelmäßige + Aktualisierung ohne Zurücksetzen der Kartenansicht folgen später. + Die Kartenlinks zu dbf.finalrewind.org entfallen. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Das IRIS-TTS-Backend der Deutschen Bahn wird wegen zunehmend + schlechter Datenanreicherunngsmöglichkeiten nicht mehr + weiterentwickelt. Bei Checkins per IRIS-TTS stehen regelmäßig keine + Echtzeitdaten und insbesondere bei Nebenbahnen auch keine + Kartendaten zur Verfügung. In diesem Fall fehlt auch die + ersatzwiese Visualisierung der Luftlinie zwischen den + Unterwegshalten. Dies betrifft auch die Visualisierung in der + Fahrtenkarte. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Derzeit besteht wegen inkompatibler Backends keine + Synchronisierungsmöglichkeit zwischen Träwelling (transitous MOTIS) + und travelynx (DB IRIS-TTS / DB HAFAS / bahn.de). + MOTIS-Unterstützung in travelynx ist in Arbeit. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.11 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Neues Backend: bahn.de. Somit steht nach Abschaltung von DB HAFAS + und VRN HAFAS wieder ein Backend zur Verfügung, welches für + innerdeutschen Nah-, Regional- und Fernverkehr geeignet ist und + eine Synchronisierung mit Träwelling unterstützt. Teile der + Implementierung können noch unvollständig sein. Ebenso besteht die + Möglichkeit, dass es wegen Rate Limits auf Seiten von bahn.de nicht + immer zuverlässig nutzbar ist. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.10 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Neue HAFAS-Backends: PKP, SaarVV. + </p> + <p> + <i class="material-icons left" aria-label="Bug">warning</i> Das DB + HAFAS-Backend wurde am 8. Januar 2025 abgeschaltet und wird von + travelynx daher seit v2.9.11 nicht mehr angeboten. Als vorläufiger + Ersatz bietet sich das VRN HAFAS-Backend an. Eine Wieder-Anbindung + der DB mittels Travel::Status::DE::DBRIS ist in Arbeit. Bis dahin + ist keine Synchronisierung mit Traewelling möglich. + </p> + <p> + <i class="material-icons left" aria-label="Administration">announcement</i> + Das PKP HAFAS befindet sich hinter einem GeoIP-Filter und wird + daher in travelynx-Installationen außerhalb von travelynx.de + standardmäßig nicht angeboten. Sofern die travelynx-Instanz auf + einer geeigneten IP-Adresse betrieben wird oder eine solche per + Proxy erreichbar ist, lässt es sich über einen Eintrag in + travelynx.conf aktivieren. Als Nebenwirkung davon kann auch auf + beliebige andere HAFAS-Instanzen bei Bedarf über einen + Instanz-spezifischen Proxy zugegriffen werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.9 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Neue HAFAS-Backends: BVG, KVB, mobiliteit, RMV, RSAG, STV, VMT, + VOS, VRN, ZVV. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + HAFAS-Backends: verbesserte Unterstützung für Ringlinien. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Verbesserte Unterstützung für uneindeutige Stationsnamen. Berlin + Hbf ist beispielsweise intern in „Berlin Hbf“ (Gleise 1 bis 8), + „Berlin Hbf“ (Gleise 11 bis 14) und „Berlin Hbf (S-Bahn)“ (Gleise + 15 und 16) getrennt. Teile von travelynx gingen in der + Vergangenheit fälschlich davon aus, dass es keine Stationen mit + identischen Namen, aber unterschiedlichen internen IDs gebe. + Dies hat u.a. bei Fahrten von/nach Berlin Hbf und innerhalb von + Karlsruhe zu interessanten Bugs geführt. + </p> + <p> + <i class="material-icons left" aria-label="Bug">warning</i> + Reisen, die in travelynx 2.8.0 bis 2.8.30 mittels IRIS-Backend + geloggt wurden, können in Einzelfällen fehlerhafte Stationsangaben + enthalten. Der Bug betrifft alle Fahrten von/zu Stationen, die in + der von travelynx genutzten Stationsdatenbank zum Checkin-Zeitpunkt + nicht bekannt waren. Eine nachträgliche Korrektur dieser Fahrten + folgt ggf. in einem späteren Release. + </p> + <p> + <i class="material-icons left" aria-label="Administration">announcement</i> + travelynx verlinkt bei Registrierung und Anmeldung nun + instanzspezifische <a href="/tos">Nutzungsbedingungen</a>. Admins + sollten beim Update auf diese Version + templates/terms-of-service.html.ep anlegen. Die Nutzungsbedingungen + können beispielsweise Richtlinien für die Freitexte in + Checkin-Kommentaren und auf der Profilseite vorgeben oder + allgemeine Hinweise und Bedingungen zur Verfügbarkeit der + jeweiligen Instanz beinhalten. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.8 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Unterstützung von HAFAS-Backends abseits der Deutschen Bahn. Somit + sind zumeist akkurate Echtzeit- und Routendaten für Checkins u.a. + in Aachen, Berlin/Brandenburg, Hessen, Sachsen-Anhalt, + Schleswig-Holstein, Österreich und der Schweiz verfügbar. + Das Backend muss vor dem Checkin explizit ausgewählt werden. + Eine Synchronisierung mit Traewelling wird nur für DB (IRIS-TTS) – + vormals „Schienenverkehr“ – und DB (HAFAS) – vormals „Nahverkehr“ – + durchgeführt. Manuell eingetragene Fahrten sind vorerst ebenfalls + auf DB (HAFAS) beschränkt. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Stationssuche und Verbindungsvorschläge berücksichtigen nur noch + das ausgewählte Backend. Die bisherige Verknüpfung von DB (IRIS-TTS) + und DB (HAFAS) entfällt. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.7 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Checkins via Nahverkehr (HAFAS) speichern nun Polylines (Routen für + die Fahrtenkarte) und Wagenreihungen, sofern verfügbar. Sie sind + damit fast identisch zu Checkins via Schienenverkehr (IRIS); es + fehlen im Wesentlichen lediglich die mit Zeitstempel versehenen + Verspätungs- und Störungsmeldungen. + <p/> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Verbesserte (aber weiterhin nicht perfekte) Unterstützung für + Ringlinien. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Korrekte Verlinkung von HAFAS-basierten Abfahrtstafeln bei den + Unterwegshalten des aktuellen Checkins im Nahverkehrsmodus. Die + Konfigurationsmöglichkeit zur Auswahl zwischen bahn.expert und DBF + unter Account → Externe Dienste besteht wegen der Abhängigkeit des + Diensts vom genutzten Backend und zwecks besserer Wartbarkeit von + travelynx nun nicht mehr. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.6 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Übersichtlichere Darstellung vergangener Fahrten. + Patch von Cass Dingenskirchen, vielen Dank! + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Nahverkehr (HAFAS-Backend): Checkins in Fahrten, die mehr als 30 + Minuten vor/nach dem Anfragezeitpunkt liegen. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.5 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Übersichtlichere Abfahrstafel mit Kennzeichnung der verschiedenen + Arten von Verkehrsmitteln. Patch von Cass Dingenskirchen, vielen + Dank! + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.4 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Berücksichtigung verwandter Stationen (d.h. Stationen, die zwar + gleich heißen, aber intern unterschiedliche IDs haben) bei + Checkin-Vorschlägen für Nahverkehrsfahrten. Vorschläge für + Zugverbindungen gibt es aus dem Nahverkehrsmenü in vielen Fällen + ebenfalls, andersherum meist noch nicht. Die restlichen Feinheiten + dieses Themenkomplexes werden im Laufe der Zeit ausgebügelt. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.3 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Checkin-Vorschläge für Nahverkehrsfahrten. Die manuelle Angabe von + Nahverkehrszielen für Anschlusshinweise entfällt damit. Bei + größeren oder aus anderen Gründen im Backend komplexen Stationen + werden derzeit teilweise nicht alle möglichen Verbindungen + angegeben – dieser Aspekt wird in einem späteren Release + verbessert. Eine von der Auswahl von Nah- vs. Fernverkehr + unabhängige Liste mit Verbindungsvorschlägen folgt ebenfalls + später. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.2 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Hinweis für fehlende Echtzeitdaten (→ nur Fahrplandaten verfügbar) + bei der aktuellen Reise. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Korrekte Angabe der Unterwegshalte auch bei fehlenden Echtzeitdaten. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.1 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Vorschlag geeigneter Stationen bei Eingabe eines uneindeutigen + Namens auf der Startseite. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Fahrten, die vor Mitternacht begannen, zeigen nun auch nach + Mitternacht korrekte Echtzeitdaten an und gehen nicht fälschlich + von 24 Stunden Verspätung aus. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Vergangene Fahrten und letzte Fahrtziele werden nun anhand der + Abfahrtszeit und nicht anhand der Nummer des Eintrags ausgewählt. + Somit können manuelle Einträge für weit in der Vergangenheit + liegende Fahrten keine vor kurzem geloggten Fahrten mehr verdecken. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.0 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Checkins in Nahverkehrsmittel (Bus und Bahn) und Züge außerhalb + des DB-Netzes per HAFAS-Backend. Die verfügbaren Backends werden + per Icon Identifiziert: <i class="material-icons">train</i> IRIS + und <i class="material-icons">directions</i> HAFAS. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Aktuell beschränkt die HAFAS-Anbindung auf Stationssuche, Checkins + und Träwelling-Synchronisierung. + Eine Einbindung in die Verbindungssuche und das manuelle Nachtragen + von HAFAS-Fahrten folgen zu einem späteren Zeitpunkt. + </p> + <p> + <i class="material-icons left" aria-label="Nicht Rückwärtskompatibel">warning</i> + Stationsangaben (z.B. auf der Hauptseite, beim Import oder in der + API) müssen nun genau mit der gewünschten Station übereinstimmen. + Unbekannte Stationen werden an das HAFAS weitergereicht, welches + meist weniger Details bereitstellt als das IRIS. + Fuzzy Matching wird nicht mehr in der bisherigen Form unterstützt. + Sofern eine Station sowohl via IRIS als auch via HAFAS bekannt ist, + wird die IRIS-Version bevorzugt. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">warning</i> + Das ds100-Feld in API und Web Hook ist nun optional und bei + HAFAS-Checkins null. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.34 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Änderung">build</i> + Die Verknüpfung von travelynx zu Träwelling nutzt nun OAuth2 + anstelle eines passwortbasierten Logins. Einerseits ist OAuth2 eine + bedeutend elegantere Lösung; andererseits wird die Träwelling-API + für Passwortlogin bald abgeschaltet. Für bestehende + Träwelling-Verknüpfungen ergeben sich keine Veränderungen. + Neue Verknüpfungen sind weiterhin möglich und benötigen nun keine + Angabe von E-Mail und Passwort mehr. Selbst + gehostete travelynx-Instanzen, die die Träwelling-Verknüpfung + anbieten möchten, müssen ab soforn bei Träwelling eine eigene <a + href="https://traewelling.de/settings/applications">Anwendung + anlegen</a> und in travelynx konfigurieren. Bitte auch die neue + Dependency Mojolicious::Plugin::OAuth2 im cpanfile beachten. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Derzeit unterstützt travelynx neben Bahnhofsnamen auch EVA-IDs und + DS100/Ril100-Codes. In Zukunft werden in einzelnen Fällen nur noch + Bahnhofsnamen und EVA-IDs unterstützt. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.33 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Synchronisierung der Checkin-Sichtbarkeit von travelynx zu + Träwelling (Patch von networkException). + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + <a href="/timeline/in-transit">Timeline-Ansicht</a> mit aktuellen + Checkins gefolgter Accounts. Die Timeline wird von der Homepage + verlinkt, wenn passende Checkins vorliegen. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Angabe von passenden Checkins gefolgter Accounts in der + Abfahrtstafel (im Sinne von: „der folgende Account ist mit auch mit + diesem Zug unterwegs“). + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.32 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Angabe von Kommentaren und Sichtbarkeit in der JSON-API + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Editierbare Beschreibung und optionale Links auf der Profilseite. + Hier können beispielsweise Träwelling oder andere Social + Media-Accounts eingetragen werden. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Optional: folgen von Accounts. Die Sichtbarkeit von Checkins und vergangenen + Fahrten kann somit auf Follower eingeschränkt werden. Eine + Übersichtsseite mit aktuellen Checkins gefolgter Accounts (ähnlich + zur Timeline im Fediverse) folgt in einem späteren Release.<br/> + Für jeden Account kann individuell eingestellt werden, ob Accounts + ihm folgen können, ob Folge-Anfragen zunächst angenommen werden + müssen oder ob Folgen grundszätzlich nicht möglich ist. + Standardmäßig ist dieses Feature inaktiv: Folge(anfrage)n müssen + zunächst in den Einstellung aktiviert werden. Falls notwendig, + können einzelne Accounts blockiert und dadurch am Folgen + und am Stellen von Folge-Anfragen gehindert werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.31 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Vorhalten der Echtzeitdaten von Unterwegshalten. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.30 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Individuelle Sichtbarkeit für jede Fahrt. Optional können Fahrten + und Check-Ins nur mit einem explizit geteilten Link für andere + Personen sichtbar sein. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.29 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Jahresrückblick mit erweiterten Statistiken. + Der Rückblick ist jeweils ab dem 31.12. verfügbar. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.28 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Behandlung von nicht mehr im IRIS eingepflegten Stationen bei vergangenen Reisen. + Bislang hatten diese zu unvollständigen Reisestatistiken geführt. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.27 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Angabe von „Kein Zustieg“ (Abfahrtstafel) bzw. „Kein Ausstieg“ (Route) durch eingeklammerte Uhrzeiten „(HH:MM)“. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.26 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Angabe der erwarteten Zugauslastung bei Unterwegshalten und Anschlussvorschlägen, sofern verfügbar. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.25 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Interne Änderungen">star</i> + Umstellung der Träwelling-Anbindung auf Träwelling-API v1, da v0 + sukzessive abgeschaltet wird. API v1 ist noch nicht stabil. + </p> + <p> + <i class="material-icons left" aria-label="Interne Änderungen">star</i> + Nutzung eines internen HAFAS-mgate.exe-Clients anstelle von transport.rest. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.24 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Angabe der geschätzten Ankunft am Ziel bei Checkinvorschlägen. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Anzeige von Anschlussmöglichkeiten an den Nahverkehr (Bus und + Stadtbahn) unterhalb der Anschlusszüge. Da travelynx derzeit keine + Checkins in Nahverkehrsmittel unterstützt, muss die Liste relevanter + Ziele händisch unter Account → Verbindungen gepflegt werden. Sofern + eine zukünftige travelynx-Version Nahverkehrs-Checkins unterstützt, + entfällt diese Liste. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Optionale Verlinkung externer Dienste (z.B. DBF oder bahn.expert) + in der eigenen Checkin-Ansicht. Somit können von dort aus alle + Abfahrten an einer Ziel- oder Unterwegsstation eingesehen werden. + Dieses Feature ist standardmäßig deaktiviert und kann über + Account → Externe Dienste konfiguriert werden. + </p> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Checkinvorschläge für Anschlussverbindungen schauen weiter in die + Zukunft und enthalten weniger nutzlose Vorschläge (z.B. Rückfahrt zur + Ursprungsstation oder Weiterfahrt zu einem späteren Ziel mit dem + Folgetakt). + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.23 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Optionale Links zu externen Abfahrtsmonitoren in der Halteliste des + aktuell ausgewählten Zugs. Die Abfahrtstafelseite kann bei den + Account-Einstellungen konfiguriert werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.22 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Verbesserte Verknüpfung und Synchronisierung mit + <a href="https://traewelling.de">Träwelling</a>. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Inaktive Accounts erhalten nach einem Jahr eine E-Mail, die auf die + in vier Wochen folgende Löschung hinweist. Betreiber:innen einer + selbstgehosteten travelynx-Instanz müssen hierzu <i>base_url</i> + in travelynx.conf setzen (siehe examples/travelynx.conf). + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.21 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Annotation von Fahrten mit fehlenden Echtzeitdaten in der + Abfahrtstafel. Derzeit wird das Vorhandensein von Echtzeitdaten + noch nicht im Fahrtenbuch gespeichert. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Unterstützung von Maßnahmen zum Schutz vor E-Mail-Spam über das + Registrierungsformular. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.20 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Accountnamen können jetzt in den Einstellungen geändert werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.19 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Hinweis bei knapper Umstiegszeit zu Anschlussverbindungen. + </p> + <p> + <i class="material-icons left" aria-label="Verbesserung">star</i> + Übersichtlichere Navigation in der Fahrtenliste / Statistikansicht. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.18 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Autmatische Checkin-Synchronisierung mit + <a href="https://traewelling.de">Träwelling</a>. Checkins können + entweder von Träwelling zu travelynx oder von travelynx zu Träwelling + übernommen werden. Das Feature läuft vorerst als Public Beta. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.17 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Öffentliches Profil. Hier können auf Wunsch der aktuelle Status + sowie die letzten zehn Zugfahrten angezeigt werden. Diese sind + wahlweise gar nicht, nur mit Anmeldung oder öffentlich sichtbar. + Zugfahrten, die älter als vier Wochen sind, können komplett verborgen + werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.16 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Auswertung von Zugfahrten von/nach bestimmten Stationen + aufgeschlüsselt nach Jahr und Monat. Diese Daten können für die + Angaben zur Pendlerpauschale bei der Steuererklärung nützlich sein. + </p> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + CSV-Export aller Fahrten. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.15 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> Die + über „Teilen“ verfügbare Reisestatus-Seite kann nun auch Details + beendeter Zugfahrten anzeigen, wenn die entsprechende Option in den <a + href="/account/privacy">Privatsphäre-Einstellungen</a> aktiv ist. + </p> + <p> + <i class="material-icons left" aria-label="Bugfix">build</i> + Behandlung von Haltausfällen während der Reise bzw. nach dem Checkin. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.14 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left">add</i> API-Endpunkt zum Import + nicht in travelynx getrackter Zugfahrten. + </p> + <p> + <i class="material-icons left">add</i> Status-API: Angabe der + Unterwegshalte zwischen Start und Ziel. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.13 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left">add</i> API-Endpunkt zum Einchecken in + Züge, aktualisieren der Zielwahl und Zurücknehmen des letzten + Checkins. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 1.12 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left">add</i> Unterstützung der im + Fahrplanjahr 2020 hinzugekommenen und geänderten Stationen. Start + und Ziel von mit travelynx geloggten Fahrten sind nun vom + Stationsnamen unabhängig (d.h. Namensänderungen werden automatisch + übernommen). + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> 1.11 </div> <div class="col s12 m11 l11"> @@ -167,5 +975,3 @@ </ul> </div> </div> - -%= include '_footer', version => stash('version') diff --git a/templates/commute.html.ep b/templates/commute.html.ep new file mode 100644 index 0000000..26b2fbc --- /dev/null +++ b/templates/commute.html.ep @@ -0,0 +1,91 @@ +<div class="row"> + <div class="col s12"> + <p> + Hier werden nur Fahrten angezeigt, deren Start- oder Zielstation + den angegebenen Kriterien entpricht. Diese Daten können zum Beispiel für + die Angaben zur Pendlerpauschale bei der Steuererklärung genutzt + werden. + </p> + </div> +</div> + +%= form_for '/history/commute' => begin + <div class="row"> + <div class="input-field col s12 m12 l12"> + %= text_field 'year', id => 'year', class => 'validate', pattern => '[0-9][0-9][0-9][0-9]' + <label for="year">Jahr</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12 m12 l6"> + <div> + <label> + %= radio_button filter_type => 'exact' + <span>Name der Station ist:</span> + </label> + </div> + <div> + <label> + %= radio_button filter_type => 'substring' + <span>Name der Station enthält:</span> + </label> + </div> + <div> + <label> + %= radio_button filter_type => 'regex' + <span>Name der Station erfüllt den regulären Ausdruck:</span> + </label> + </div> + </div> + <div class="input-field col s12 m12 l6"> + %= text_field 'station', id => 'station', required => undef, class => 'autocomplete contrast-color-text', autocomplete => 'off' + <label for="station">Fahrtziel</label> + </div> + </div> + <div class="row"> + <div class="col s12 m12 l12 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="show"> + <i class="material-icons left" aria-hidden="true">send</i> + Anzeigen + </button> + </div> + </div> +%= end + +<h1><%= param('year') %></h1> +<div class="row"> + <div class="col s12 m12 l12"> + <p> + An <b><%= $total_journeys %></b> Tagen im Jahr wurde mindestens + eine Fahrt von oder zu + % if (param('filter_type') eq 'exact') { + der ausgewählten Station + % } + % else { + den ausgewählten Stationen + % } + eingetragen. + </p> + <table class="striped"> + <thead> + <tr> + <th>Monat</th> + <th>Tage mit Fahrten</th> + </tr> + </thead> + <tbody> + % for my $i (0 .. $#{$months}) { + <tr> + <td><%= $months->[$i] %></td> + <td><%= $count_by_month->{$i+1} // 0 %></td> + </tr> + % } + </tbody> + </table> + </div> +</div> + +% for my $i (0 .. $#{$months}) { + <h2><%= $months->[$i] %></h2> + %= include '_history_trains', date_format => '%a %d.%m.', journeys => $journeys_by_month->{$i+1} // [] +% } diff --git a/templates/departures.html.ep b/templates/departures.html.ep index e71fa25..6df48a8 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -1,103 +1,200 @@ <div class="row"> - <div class="col s12 center-align"> - <b><%= $station %></b> + <div class="col s8"> + <strong style="font-size: 120%;"> + <%= $station %> + </strong> % for my $related_station (sort { $a->{name} cmp $b->{name} } @{$related_stations}) { - <br/><%= $related_station->{name} %> + + <%= $related_station->{name} %> <br/> + % } + </div> + <div class="col s4 center-align"> + % my $self_link = url_for('sstation', station => $station // param('station')); + % if (param('dbris')) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('dbris') %></a> + % } + % elsif (param('hafas')) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('hafas') %></a> + % } + % elsif (param('motis')) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('motis') %></a> + % } + % else { + % if ($user->{backend_id}) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %></a> + % } + % else { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">train</i>IRIS</a> + % } % } </div> </div> -% my $status = $self->get_user_status; + % my $have_connections = 0; -% if ($status->{checked_in}) { +% if ($user_status->{checked_in}) { <div class="row"> <div class="col s12"> <div class="card"> <div class="card-content"> <span class="card-title">Aktuell eingecheckt</span> - <p>In <%= $status->{train_type} %> <%= $status->{train_no} %> - ab <%= $status->{dep_name} %></p> + <p>In + % if ( not $user_status->{is_motis} ) { + <%= $user_status->{train_type} %> + % } + + <%= $user_status->{train_line} // $user_status->{train_no} %> + + % if ( $user_status->{arr_name}) { + von <%= $user_status->{dep_name} %> nach <%= $user_status->{arr_name} %> + % } + % else { + ab <%= $user_status->{dep_name} %> + % } + </p> </div> <div class="card-action"> - <a class="action-checkout" data-station="<%= $ds100 %>" data-force="1"> - Hier auschecken - </a> + % if ($can_check_out) { + <a class="action-undo" data-hafas="<%= param('hafas') // q{} %>" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;"> + <i class="material-icons left" aria-hidden="true">undo</i> Rückgängig + </a> + <a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1"> + Hier auschecken + </a> + % } + % else { + <a class="action-undo" data-id="in_transit" data-checkints="<%= $user_status->{timestamp}->epoch %>" style="margin-right: 0;"> + <i class="material-icons left" aria-hidden="true">undo</i> Rückgängig + </a> + <a class="action-checkout right" data-hafas="<%= param('hafas') // q{} %>" data-station="<%= $eva %>" data-force="1"> + <i class="material-icons left" aria-hidden="true">gps_off</i> + Hier auschecken + </a> + % } </div> </div> </div> </div> % } -% elsif ($status->{timestamp_delta} < 180) { +% elsif ($user_status->{cancellation} and $station eq $user_status->{cancellation}{dep_name}) { + <div class="row"> + <div class="col s12"> + %= include '_cancelled_departure', journey => $user_status->{cancellation}; + </div> + </div> +% } +% elsif ($user_status->{timestamp_delta} < 180) { <div class="row"> <div class="col s12"> - %= include '_checked_out', journey => $status; + %= include '_checked_out', journey => $user_status; </div> </div> % } -% elsif (not param('train') and my @connections = get_connecting_trains(ds100 => $ds100)) { +% elsif (not param('train') and (@{stash('connections_iris') // []} or @{stash('connections_hafas') // []}) ) { % $have_connections = 1; <div class="row"> <div class="col s12"> - <p>Häufig genutzte Verbindungen – Zug auswählen zum Einchecken mit Zielwahl</p> - %= include '_connections', connections => \@connections, checkin_from => $ds100; + <p>Häufig genutzte Verbindungen – Fahrt auswählen zum Einchecken mit Zielwahl</p> + % if (@{stash('connections_iris') // []}) { + %= include '_connections', connections => stash('connections_iris'), checkin_from => $eva; + % } + % if (@{stash('connections_hafas') // []}) { + %= include '_connections_hafas', connections => stash('connections_hafas'), checkin_from => $eva; + % } </div> </div> % } + +<div class="row"> + <div class="col s4 center-align"> + % if ($dbris or $efa or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->subtract(hours => 1)->epoch}) %>"><i class="material-icons left" aria-hidden="true">chevron_left</i><span class="hide-on-small-only">früher</span></a> + % } + </div> + <div class="col s4 center-align"> + % if ($now_in_range) { + <a class="btn-small" href="#now"><i class="material-icons left" aria-hidden="true">vertical_align_center</i><span class="hide-on-small-only">Jetzt</span></a> + % } + </div> + <div class="col s4 center-align"> + % if ($dbris or $efa or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->add(hours => 1)->epoch}) %>"><span class="hide-on-small-only">später</span><i class="material-icons right" aria-hidden="true">chevron_right</i></a> + % } + </div> +</div> + <div class="row"> <div class="col s12"> <p> % if ($have_connections) { Alle Abfahrten – % } - % if (@{$results}) { - Zug auswählen zum Einchecken. + % if ($user_status->{checked_in} and not $can_check_out) { + Diese Station liegt nicht auf der Route deines <a href="/">aktuellen Checkins</a>. + Falls du aktuell nicht mit <b><%= $user_status->{train_type} %> <%= $user_status->{train_no} %></b> unterwegs bist, kannst du den Checkin rückgängig machen. + Falls es sich bei <b><%= $station %></b> um einen nicht in den Echtzeitdaten abgebildeten Zusatzhalt handelt, kannst du hier auchecken. + Da travelynx nicht weiß, welcher der beiden Fälle zutrifft, sind bis dahin keine neuen Checkins möglich. + % } + % elsif ($user_status->{checked_in} and not $user_status->{arr_eva}) { + Du bist bereits eingecheckt und hast noch kein Fahrtziel angegeben. + Bitte <a href="/">wähle zunächst ein Ziel</a>. + Neue Checkins sind erst nach Ankunft der aktuellen Fahrt möglich. + % } + % elsif ($user_status->{checked_in} and $user_status->{arrival_countdown} > 0) { + Deine aktuelle Fahrt ist <a href="/">noch unterwegs</a>. + Ein neuer Checkin ist erst nach Ankunft am ausgewählten Ziel möglich. + % } + % elsif (@{$results}) { + Fahrt auswählen zum Einchecken. % } % else { - Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor - und maximal 120 Minuten nach Abfahrt möglich. + % if ($dbris or $hafas) { + Keine Abfahrten im ausgewählten Zeitfenster + (<%= $datetime->strftime('%d.%m.%Y %H:%M') %> ± 30min). + % } + % else { + Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor + und maximal 120 Minuten nach Abfahrt möglich. + % } % } </p> - <table class="striped"> - <tbody> - % my $orientation_bar_shown = param('train'); - % my $now_epoch = now()->epoch; - % for my $result (@{$results}) { - % my $td_class = ''; - % my $link_class = 'action-checkin'; - % if ($result->departure_is_cancelled) { - % $td_class = "cancelled"; - % $link_class = 'action-cancelled-from'; - % } - % if (not $orientation_bar_shown and $result->departure->epoch < $now_epoch) { - % $orientation_bar_shown = 1; - <tr> - <td> - </td> - <td> - — Anfragezeitpunkt — - </td> - <td> - </td> - </tr> - % } - <tr> - <td> - <a class="<%= $link_class %>" data-station="<%= $result->station_uic %>" data-train="<%= $result->train_id %>"> - <%= $result->line %> - </a> - </td> - <td class="<%= $td_class %>"> - <a class="<%= $link_class %>" data-station="<%= $result->station_uic %>" data-train="<%= $result->train_id %>"> - <%= $result->destination %> - </a> - </td> - <td class="<%= $td_class %>"><%= $result->departure->strftime('%H:%M') %> - % if ($result->departure_delay) { - (<%= sprintf('%+d', $result->departure_delay) %>) - % } - </td> - </tr> - % } - </tbody> - </table> + % if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) { + % if ($dbris) { + %= include '_departures_dbris', results => $results, dbris => $dbris; + % } + % elsif ($efa) { + %= include '_departures_efa', results => $results, efa => $efa; + % } + % elsif ($hafas) { + %= include '_departures_hafas', results => $results, hafas => $hafas; + % } + % elsif ($motis) { + %= include '_departures_motis', results => $results, motis => $motis; + % } + % else { + %= include '_departures_iris', results => $results; + % } + % } + </div> +</div> + +<div class="row"> + <div class="col s4 center-align"> + % if ($dbris or $efa or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->subtract(hours => 1)->epoch}) %>"><i class="material-icons left" aria-hidden="true">chevron_left</i><span class="hide-on-small-only">früher</span></a> + % } + </div> + <div class="col s4 center-align"> + </div> + <div class="col s4 center-align"> + % if ($dbris or $efa or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->add(hours => 1)->epoch}) %>"><span class="hide-on-small-only">später</span><i class="material-icons right" aria-hidden="true">chevron_right</i></a> + % } </div> </div> + +% if (not $user_status->{checked_in}) { + <div class="row"> + <div class="col s12 center-align"> + <a class="btn-small" href="<%= url_for('checkinadd')->query({dbris => $dbris, efa => $efa, hafas => $hafas, motis => $motis, dep_station => $station}) %>"><i class="material-icons left" aria-hidden="true">add</i><span>manuell einchecken</span></a> + </div> + </div> +% } diff --git a/templates/disambiguation.html.ep b/templates/disambiguation.html.ep new file mode 100644 index 0000000..af7d1dd --- /dev/null +++ b/templates/disambiguation.html.ep @@ -0,0 +1,20 @@ +<div class="row"> + <div class="col s12"> + <div class="card info-color"> + <div class="card-content"> + <span class="card-title">Mehrdeutige Eingabe</span> + <p>„<%= $station %>“ ist nicht eindeutig. Bitte wähle eine der folgenden Optionen aus.</p> + </div> + </div> + </div> +</div> + +<div class="row"> + <div class="col s12"> + <ul class="suggestions"> + % for my $suggestion (@{$suggestions // []}) { + <li><a href="<%= url_for('station' => $suggestion->{eva}) . (param('hafas') ? '?hafas=' . param('hafas') : q{}) %>"><%= $suggestion->{name} %></a></li> + % } + </ul> + </div> +</div> diff --git a/templates/edit_comment.html.ep b/templates/edit_comment.html.ep index 81353a2..80c4110 100644 --- a/templates/edit_comment.html.ep +++ b/templates/edit_comment.html.ep @@ -1,11 +1,11 @@ -<h1>Zugfahrt kommentieren</h1> +<h1>Fahrt kommentieren</h1> % if ($error or not $journey->{checked_in}) { <div class="row"> <div class="col s12"> <div class="card caution-color"> <div class="card-content white-text"> <span class="card-title">Fehler</span> - <p>Du bist gerade nicht eingecheckt. Vergangene Zugfahrten + <p>Du bist gerade nicht eingecheckt. Vergangene Fahrten kannst du über die Editierfunktion in der History kommentieren.</p> </div> @@ -29,7 +29,7 @@ am <b><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></b> </p> - % if (current_user()->{is_public} & 0x04) { + % if (current_user()->{comments_visible}) { <p> Der hier eingetragene Text ist als Teil deines Nutzerstatus öffentlich sichtbar. diff --git a/templates/edit_journey.html.ep b/templates/edit_journey.html.ep index ff36381..cb867e5 100644 --- a/templates/edit_journey.html.ep +++ b/templates/edit_journey.html.ep @@ -1,11 +1,11 @@ -<h1>Zugfahrt bearbeiten</h1> +<h1>Fahrt bearbeiten</h1> % if ($error and $error eq 'notfound') { <div class="row"> <div class="col s12"> <div class="card caution-color"> <div class="card-content white-text"> <span class="card-title">Fehler</span> - <p>Zugfahrt nicht gefunden.</p> + <p>Fahrt nicht gefunden.</p> </div> </div> </div> @@ -39,11 +39,11 @@ </p> <p> Nach einer Änderung können die ursprünglich eingetragenen - Zeiten nicht mehr wiederhergestellt werden. + Daten nicht wiederhergestellt werden. </p> <table class="striped"> <tr> - <th scope="row">Zug</th> + <th scope="row">Fahrt</th> <td> <%= $journey->{type} %> <%= $journey->{no} %> % if ($journey->{line}) { @@ -61,38 +61,50 @@ </td> </tr> <tr> + <th scope="row">Start:</th> + <td class="input-field"> + %= text_field 'from_name', id => 'from_name', class => 'autocomplete validate', autocomplete => 'off', required => undef + </td> + </tr> + <tr> <th scope="row">Geplante Abfahrt</th> - <td> + <td class="input-field"> %= text_field 'sched_departure', id => 'sched_departure', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' </td> </tr> <tr> <th scope="row">Tatsächliche Abfahrt</th> - <td> + <td class="input-field"> %= text_field 'rt_departure', id => 'real_departure', class => 'validate', pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' </td> </tr> <tr> + <th scope="row">Ziel:</th> + <td class="input-field"> + %= text_field 'to_name', id => 'to_name', class => 'autocomplete validate', autocomplete => 'off', required => undef + </td> + </tr> + <tr> <th scope="row">Geplante Ankunft</th> - <td> + <td class="input-field"> %= text_field 'sched_arrival', id => 'sched_arrival', class => 'validate', required => undef, pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' </td> </tr> <tr> <th scope="row">Tatsächliche Ankunft</th> - <td> + <td class="input-field"> %= text_field 'rt_arrival', id => 'real_arrival', class => 'validate', pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9] +[0-9][0-9]:[0-9][0-9]' </td> </tr> <tr> <th scope="row">Route</th> - <td> + <td class="input-field"> %= text_area 'route', id => 'route', class => 'materialize-textarea' </td> </tr> <tr> <th scope="row">Kommentar</th> - <td> + <td class="input-field"> %= text_field 'comment' </td> </tr> @@ -108,7 +120,7 @@ <div class="col s6 m6 l6 center-align"> <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> Speichern - <i class="material-icons right">send</i> + <i class="material-icons right" aria-hidden="true">send</i> </button> </div> </div> diff --git a/templates/edit_profile.html.ep b/templates/edit_profile.html.ep new file mode 100644 index 0000000..55b1e1e --- /dev/null +++ b/templates/edit_profile.html.ep @@ -0,0 +1,60 @@ +<div class="row"> + <div class="col s12"> + <h1>Profil bearbeiten</h1> + </div> +</div> +%= form_for '/account/profile' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title"><%= $name %></span> + <p> + Markdown möglich, maximal 2000 Zeichen. + %= text_area 'bio', id => 'bio', class => 'materialize-textarea' + </p> + </div> + <div class="card-action"> + <a href="/p/<%= $name %>" class="waves-effect waves-light btn"> + Abbrechen + </a> + <button class="btn waves-effect waves-light right" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + </div> + </div> + </div> + <div class="row"> + <div class="col s12"> + Metadaten: Markdown-Links im Inhalt erlaubt, jeweils maximal 500 Zeichen + </div> + </div> + % for my $i (0 .. 10) { + <div class="row"> + <div class="input-field col l3 m12 s12"> + %= text_field "key_$i", id => "key_$i", maxlength => 50 + <label for="key_<%= $i %>">Attribut</label> + </div> + <div class="input-field col l9 m12 s12"> + %= text_field "value_$i", id => "value_$i", maxlength => 500 + <label for="value_<%= $i %>">Inhalt</label> + </div> + </div> + % } + <div class="row center-align"> + <div class="col s6"> + <a href="/p/<%= $name %>" class="waves-effect waves-light btn"> + Abbrechen + </a> + </div> + <div class="col s6"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + </div> +%= end diff --git a/templates/edit_visibility.html.ep b/templates/edit_visibility.html.ep new file mode 100644 index 0000000..9bf8d56 --- /dev/null +++ b/templates/edit_visibility.html.ep @@ -0,0 +1,123 @@ +<h1>Sichtbarkeit ändern</h1> +% if ($error) { + <div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">Fehler</span> + <p><%= $error // 'Du bist gerade nicht eingecheckt' %></p> + </div> + </div> + </div> + </div> +% } +% else { + %= form_for '/journey/visibility' => (method => 'POST') => begin + %= csrf_field + %= hidden_field 'dep_ts' => param('dep_ts') + %= hidden_field 'id' => param('id') + <div class="row"> + <div class="col s12"> + <p> + Fahrt mit + <b><%= $journey->{train_type} // $journey->{type} %> <%= $journey->{train_no} // $journey->{no} %></b> + von + <b><%= $journey->{dep_name} // $journey->{from_name} %></b> + nach + <b><%= $journey->{arr_name} // $journey->{to_name} // 'irgendwo' %></b> + am + <b><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></b> + </p> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'default' + <span>Einstellung aus dem Profil verwenden: <strong> + % if ($user_level eq 'public') { + Die Fahrt ist öffentlich sichtbar. + % } + % elsif ($user_level eq 'travelynx') { + Die Fahrt ist nur für auf dieser Seite angemeldete Accounts oder mit Link sichtbar. + % } + % elsif ($user_level eq 'followers') { + Die Fahrt ist nur für dir folgende Accounts oder mit Link sichtbar. + % } + % elsif ($user_level eq 'unlisted') { + Die Fahrt ist nur mit Link sichtbar. + % } + % else { + Die Fahrt ist nur für dich sichtbar. + % } + </strong> Änderungen der Profil-Einstellung werden auch nachträglich für diese Fahrt wirksam.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'public' + <span><i class="material-icons left"><%= visibility_icon('public') %></i>Öffentlich: Im Profil verlinkt und beliebig zugänglich.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'travelynx' + <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Intern: Personen, die dir folgen, die auf dieser Seite angemeldet sind oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'followers' + <span><i class="material-icons left"><%= visibility_icon('followers') %></i>Follower: Personen, die dir folgen oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'unlisted' + <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'private' + <span><i class="material-icons left"><%= visibility_icon('private') %></i>Privat: nur für dich sichtbar.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s6 m6 l6 center-align"> + <a href="/" class="waves-effect waves-light btn"> + Abbrechen + </a> + </div> + <div class="col s6 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + </div> + %= end +% } diff --git a/templates/exception.html.ep b/templates/exception.html.ep index 290efc5..9b8697c 100644 --- a/templates/exception.html.ep +++ b/templates/exception.html.ep @@ -20,8 +20,15 @@ Timestamp: %= DateTime->now(time_zone => 'Europe/Berlin')->strftime("%d/%b/%Y:%H:%M:%S %z") <br/><br/> - Message: - %= (split(qr{\n}, $exception->message))[0] + % if (ref($exception)) { + Trace:<br/> + % for my $line (split(qr{\n}, $exception->message)) { + <%= $line %><br/> + % } + % } + % else { + Message: <%= $exception %> + % } </p> </div> </div> diff --git a/templates/gateway_timeout.html.ep b/templates/gateway_timeout.html.ep new file mode 100644 index 0000000..9cf8690 --- /dev/null +++ b/templates/gateway_timeout.html.ep @@ -0,0 +1,27 @@ +<div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">504 Gateway Timeout</span> + <p> + Das von travelynx genutzte Backend hat nicht rechtzeitig reagiert. + travelynx hat keine Möglichkeiten, diese Situation zu beheben. + % if (stash('select_new_backend')) { + Versuche es in ein paar Sekunden bis Minuten noch einmal oder <a href="/account/select_backend">wähle ein anderes Backend</a>. + % } + % else { + Versuche es in ein paar Sekunden bis Minuten noch einmal. + % } + </p> + </div> + </div> + </div> +</div> +<div class="row"> + <div class="col s12"> + <p>Details:</p> + <p style="font-family: monospace;"> + %= $message + </p> + </div> +</div> diff --git a/templates/history.html.ep b/templates/history.html.ep index 93fa9f7..71d180f 100644 --- a/templates/history.html.ep +++ b/templates/history.html.ep @@ -1,58 +1,46 @@ -<h1>Fahrten</h1> +<h2>Fahrten</h2> -<div class="row"> - <div class="col s12"> - Hier finden sich alle bisherigen Zugfahrten und Statistiken für jedes - Jahr und jeden Monat. - </div> -</div> +Für Details ein Jahr auswählen. -<h2>Nach Jahr</h2> -%= include '_history_years', current => ''; -% if(0) { - <div class="row"> - <div class="col s12"> - Noch keine Fahrten. - </div> - </div> -% } - -<h2>Nach Monat</h2> -%= include '_history_months', current => ''; -% if(0) { - <div class="row"> - <div class="col s12"> - Noch keine Fahrten. - </div> - </div> -% } +%= include '_history_years_list'; <h2>Auswertungen</h2> <div class="row"> - <div class="col s12 m12 l12 center-align"> - <a href="/history/map" class="waves-effect waves-light btn"><i class="material-icons left">map</i> Fahrtenkarte</a> + <div class="col s12 m12 l5 center-align"> + <a href="/history/map" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">map</i> Fahrtenkarte</a> + </div> + <div class="col s12 m12 l2"> </div> + <div class="col s12 m12 l5 center-align"> + <a href="/history/commute" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">search</i> nach Station</a> </div> </div> <h2>Ausfälle und Verspätungen</h2> <div class="row"> <div class="col s12 m12 l5 center-align"> - <a href="/cancelled" class="waves-effect waves-light btn"><i class="material-icons left">cancel</i> Zugausfälle</a> + <a href="/cancelled" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">cancel</i> Ausfälle</a> </div> <div class="col s12 m12 l2"> </div> <div class="col s12 m12 l5 center-align"> - <a href="/fgr" class="waves-effect waves-light btn"><i class="material-icons left">feedback</i> Fahrgastrechte</a> + <a href="/fgr" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">feedback</i> Fahrgastrechte</a> </div> </div> <h2>Rohdaten</h2> <div class="row"> <div class="col s12 m12 l5 center-align"> - <a href="/history.json" class="waves-effect waves-light btn"><i class="material-icons left">cloud</i> JSON-Export</a> + <a href="/history.json" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">cloud</i> JSON-Export</a> + </div> + <div class="col s12 m12 l2"> </div> + <div class="col s12 m12 l5 center-align"> + <a href="/history.csv" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">list</i> CSV-Export</a> </div> +</div> +<div class="row"> + <div class="col s12 m12 l5 center-align"> </div> <div class="col s12 m12 l2"> </div> <div class="col s12 m12 l5 center-align"> - <a href="/journey/add" class="waves-effect waves-light btn"><i class="material-icons left">add</i> Neue Fahrt</a> + <a href="/journey/add" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">add</i> Neue Fahrt</a> </div> </div> diff --git a/templates/history_by_month.html.ep b/templates/history_by_month.html.ep index 7aae59d..c3b1004 100644 --- a/templates/history_by_month.html.ep +++ b/templates/history_by_month.html.ep @@ -1,11 +1,15 @@ -%= include '_history_months', current => "${year}/${month}"; - -<h1><%= stash('month_name') %> <%= stash('year') %></h1> +%= include '_history_months'; % if (stash('statistics')) { %= include '_history_stats', stats => stash('statistics'); % } +<div class="row"> + <div class="col s12 m12 l12 center-align"> + <a href="/history/map?filter_from=<%= $filter_from->strftime('%d.%m.%Y') %>&filter_to=<%= $filter_to->strftime('%d.%m.%Y') %>" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> + </div> +</div> + % if (stash('journeys')) { %= include '_history_trains', date_format => '%d.%m.', journeys => stash('journeys'); % } diff --git a/templates/history_by_year.html.ep b/templates/history_by_year.html.ep index c5cffa0..6aa0c2d 100644 --- a/templates/history_by_year.html.ep +++ b/templates/history_by_year.html.ep @@ -1,11 +1,36 @@ %= include '_history_years', current => $year; -<h1>Jahresrückblick <%= $year %></h1> - % if (stash('statistics')) { %= include '_history_stats', stats => stash('statistics'); % } +<div class="row"> + % if (stash('have_review')) { + <div class="col s12 m12 l5 center-align"> + <a href="/history/map?filter_from=1.1.<%= $year %>&filter_to=31.12.<%= $year %>" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> + </div> + <div class="col s12 m12 l2"> </div> + <div class="col s12 m12 l5 center-align"> + <a href="/history/<%= $year %>/review" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">camera_roll</i> Rückblick</a> + </div> + % } + % else { + <div class="col s12 m12 l12 center-align"> + <a href="/history/map?filter_from=1.1.<%= $year %>&filter_to=31.12.<%= $year %>" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> + </div> + % } +</div> + +%= include '_history_months_for_year'; + +% if (param('filter') and param('filter') eq 'single') { +<div class="row"> + <div class="col s12 m12 l12"> + <p>Die folgende Auflistung enthält nur Fahrten, deren Kombination aus Start und Ziel im aktuellen Jahr einmalig ist.</p> + </div> +</div> +% } + % if (stash('journeys')) { %= include '_history_trains', date_format => '%d.%m.', journeys => stash('journeys'); % } diff --git a/templates/history_map.html.ep b/templates/history_map.html.ep index 7bda06b..c2ff9ed 100644 --- a/templates/history_map.html.ep +++ b/templates/history_map.html.ep @@ -1,31 +1,141 @@ <div class="row"> <div class="col s12"> % if (@{$station_coordinates}) { - Alle bisherigen Zugfahrten + Fahrten % } % else { - Keine Zugfahrten gefunden. + Keine Fahrten + % } + % if (param('filter_type')) { + mit <strong><%= param('filter_type') %></strong> + % } + % if (stash('year')) { + im Jahr <strong><%= stash('year') %></strong> + % } + % elsif (param('filter_from') and param('filter_to')) { + zwischen dem <strong><%= param('filter_from') %></strong> und dem <strong><%= param('filter_to') %></strong> + % } + % elsif (param('filter_from')) { + ab dem <strong><%= param('filter_from') %></strong> + % } + % elsif (param('filter_to')) { + bis einschließlich <strong><%= param('filter_to') %></strong> + % } + % elsif (@{$station_coordinates}) { + in travelynx + % } + % if (not @{$station_coordinates}) { + gefunden % } </div> </div> -%= include '_map', station_coordinates => $station_coordinates, station_pairs => $station_pairs +%= include '_map', station_coordinates => $station_coordinates, polyline_groups => $polyline_groups -%= form_for '/history/map' => (method => 'POST') => begin - %= csrf_field +%= form_for '/history/map' => begin + <p> + Detailgrad: + </p> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button route_type => 'polyline' + <span>Nur Fahrten mit bekanntem Streckenverlauf eintragen</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button route_type => 'polybee' + <span>Streckenverlauf wenn bekannt, sonst Luftlinie zwischen Unterweghalten</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button route_type => 'beeline' + <span>Immer Luftlinie zwischen Unterwegshalten zeigen</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= check_box include_manual => 1 + <span>Manuelle Einträge ohne Unterwegshalte mitberücksichtigen</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s12 center-align"> + <button class="btn wave-effect waves-light" type="submit"> + Anzeigen + </button> + </div> + </div> + <p> + Weitere Filter: + </p> <div class="row"> <div class="input-field col s12"> - <label> - %= check_box include_manual => 1 - <span>Manuelle Einträge ohne Unterwegshalte mitberücksichtigen</span> - </label> + %= text_field 'filter_from', id => 'filter_from', class => 'validate', pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9]( +[0-9][0-9]:[0-9][0-9])?' + <label for="filter_from">Abfahrt ab (DD.MM.YYYY)</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_field 'filter_to', id => 'filter_to', class => 'validate', pattern => '[0-9][0-9]?[.][0-9][0-9]?[.][0-9][0-9][0-9][0-9]( +[0-9][0-9]:[0-9][0-9])?' + <label for="filter_to">Abfahrt bis (DD.MM.YYYY)</label> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + %= text_field 'filter_type', id => 'filter_type' + <label for="filter_tpye">Verkehrsmittel</label> </div> </div> <div class="row"> <div class="col s12 center-align"> - <button class="btn wave-effect waves-light" type="submit" name="action" value="go"> + <button class="btn wave-effect waves-light" type="submit"> Anzeigen </button> </div> </div> %= end + +<div class="row"> + <div class="col s12"> + <p> + Die eingezeichneten Routen stammen aus dem Backend, mit dem die Fahrt aufgezeichnet wurde. + Die Datenqualität variiert. + </p> + </div> +</div> + +% if (@{$skipped_journeys // []}) { + <div class="row"> + <div class="col s12"> + <p> + Die folgenden Fahrten wurden nicht eingezeichnet: + </p> + <p> + <ul> + % for my $pair (@{$skipped_journeys}) { + % my ($journey, $reason) = @{$pair}; + <li><a href="/journey/<%= $journey->{id} %>"><%= $journey->{type} %> <%= $journey->{no} %> <%= $journey->{from_name} %> → <%= $journey->{to_name} %></a>: <%= $reason %></li> + % } + </ul> + </p> + </div> + </div> +% } diff --git a/templates/journey.html.ep b/templates/journey.html.ep index 74ad962..31f9e94 100644 --- a/templates/journey.html.ep +++ b/templates/journey.html.ep @@ -4,7 +4,7 @@ <div class="card caution-color"> <div class="card-content white-text"> <span class="card-title">Fehler</span> - <p>Zugfahrt nicht gefunden.</p> + <p>Fahrt nicht gefunden.</p> </div> </div> </div> @@ -14,28 +14,35 @@ <div class="row"> <div class="col s12"> <p> - % if ($journey->{cancelled}) { - Ausgefallene Fahrt + % if (my $name = stash('username')) { + Checkin von <b><a href="/p/<%= $name %>"><%= $name %></a></b> + % } + % elsif ($journey->{cancelled}) { + <b>Ausgefallene Fahrt</b> vom <%= $journey->{checkin}->strftime('%d.%m.%Y um %H:%M Uhr') %> % } % else { - Fahrt + Checkin vom <%= $journey->{checkin}->strftime('%d.%m.%Y um %H:%M Uhr') %> % } % if ($journey->{edited} & 0x0020) { ∗ % } - von - <b><%= $journey->{from_name} %></b> - nach - <b><%= $journey->{to_name} %></b> - am - <b><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></b> + % if (my $v = stash('journey_visibility')) { + % if (stash('username')) { + <i class="material-icons right"><%= visibility_icon($v) %></i> + % } + % else { + <a class="right" href="/journey/visibility?id=<%= $journey->{id} %>"> + <i class="material-icons"><%= visibility_icon($v) %></i> + </a> + % } + % } </p> % if ($journey->{edited}) { <p> ∗ Daten wurden manuell eingetragen </p> % } - % if ($journey->{cancelled} or ($journey->{rt_arrival} and ($journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch) >= 3600)) { + % if (not stash('readonly') and ($journey->{cancelled} or ($journey->{rt_arrival} and ($journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch) >= 3600))) { <div style="text-align: center; margin-bottom: 1em;"> % my $form_target = sprintf('/journey/passenger_rights/FGR %s %s %s.pdf', $journey->{sched_departure}->ymd, $journey->{type}, $journey->{no}); %= form_for $form_target => (method => 'POST') => begin @@ -50,7 +57,7 @@ % } <table class="striped"> <tr> - <th scope="row">Zug</th> + <th scope="row">Fahrt</th> <td> <%= $journey->{type} %> <%= $journey->{no} %> % if ($journey->{line}) { @@ -59,19 +66,48 @@ </td> </tr> <tr> + <th scope="row">Von</th> + <td> + %= $journey->{from_name} + % if ($journey->{from_platform} and $journey->{to_platform}) { + (<%= $journey->{from_platform} %>) + % } + % if ($journey->{edited} & 0x0004) { + ∗ + % } + </td> + </tr> + <tr> + <th scope="row">Nach</th> + <td> + <%= $journey->{to_name} %> + % if ($journey->{from_platform} and $journey->{to_platform}) { + (<%= $journey->{to_platform} %>) + % } + % if ($journey->{edited} & 0x0400) { + ∗ + % } + </td> + </tr> + <tr> <th scope="row">Abfahrt</th> <td> % if ($journey->{cancelled}) { <i class="material-icons">cancel</i> - (Plan: <%= $journey->{sched_departure}->strftime('%H:%M'); %>) + (Plan: <%= $journey->{sched_departure}->strftime('%d.%m.%Y %H:%M'); %>) % } - % elsif ($journey->{rt_departure} != $journey->{sched_departure}) { - %= $journey->{rt_departure}->strftime('%H:%M'); - (<%= sprintf('%+d', ($journey->{rt_departure}->epoch - $journey->{sched_departure}->epoch) / 60) %>, - Plan: <%= $journey->{sched_departure}->strftime('%H:%M'); %>) + % elsif ($journey->{delay_dep}) { + %= ($journey->{rt_departure}->epoch % 60) ? $journey->{rt_departure}->strftime('%d.%m.%Y %H:%M:%S') : $journey->{rt_departure}->strftime('%d.%m.%Y %H:%M') + % if (int(abs($journey->{delay_dep}) / 60)) { + (<%= sprintf('%+d', ($journey->{rt_departure}->epoch - $journey->{sched_departure}->epoch) / 60) %>, Plan: + % } + % else { + (Plan: + % } + %= ($journey->{sched_departure}->epoch % 60) ? $journey->{sched_departure}->strftime('%H:%M:%S)') : $journey->{sched_departure}->strftime('%H:%M)') % } % else { - %= $journey->{sched_departure}->strftime('%H:%M'); + %= ($journey->{sched_departure}->epoch % 60) ? $journey->{sched_departure}->strftime('%d.%m.%Y %H:%M:%S') : $journey->{sched_departure}->strftime('%d.%m.%Y %H:%M'); % } % if ($journey->{edited} & 0x0003) { ∗ @@ -84,19 +120,24 @@ % if ($journey->{cancelled}) { <i class="material-icons">cancel</i> % if ($journey->{sched_arrival}->epoch != 0) { - (Plan: <%= $journey->{sched_arrival}->strftime('%H:%M'); %>) + (Plan: <%= $journey->{sched_arrival}->strftime('%d.%m.%Y %H:%M'); %>) % } % } % elsif ($journey->{rt_arrival}->epoch == 0 and $journey->{sched_arrival}->epoch == 0) { <i class="material-icons">timer_off</i> % } - % elsif ($journey->{rt_arrival} != $journey->{sched_arrival}) { - %= $journey->{rt_arrival}->strftime('%H:%M'); - (<%= sprintf('%+d', ($journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch) / 60) %>, - Plan: <%= $journey->{sched_arrival}->strftime('%H:%M'); %>) + % elsif ($journey->{delay_arr}) { + %= ($journey->{rt_arrival}->epoch % 60) ? $journey->{rt_arrival}->strftime('%d.%m.%Y %H:%M:%S') : $journey->{rt_arrival}->strftime('%d.%m.%Y %H:%M') + % if (int(abs($journey->{delay_arr}) / 60)) { + (<%= sprintf('%+d', ($journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch) / 60) %>, Plan: + % } + % else { + (Plan: + % } + %= ($journey->{sched_arrival}->epoch % 60) ? $journey->{sched_arrival}->strftime('%H:%M:%S)') : $journey->{sched_arrival}->strftime('%H:%M)') % } % else { - %= $journey->{sched_arrival}->strftime('%H:%M'); + %= ($journey->{sched_arrival}->epoch % 60) ? $journey->{sched_arrival}->strftime('%d.%m.%Y %H:%M:%S') : $journey->{sched_arrival}->strftime('%d.%m.%Y %H:%M'); % } % if ($journey->{edited} & 0x0300) { ∗ @@ -104,15 +145,18 @@ </td> </tr> <tr> - <th scope="row">Entfernung</th> + <th scope="row">Strecke</th> <td> % if ($journey->{skip_route}) { <i class="material-icons right">location_off</i> <%= numify_skipped_stations($journey->{skip_route}) %><br/> % } % if ($journey->{km_route} > 0.1) { - ca. <%= sprintf('%.f', $journey->{km_route}) %> km - (Luftlinie: <%= sprintf('%.f', $journey->{km_beeline}) %> km) + ca. <%= sprintf_km($journey->{km_route}) %> + (Luftlinie: <%= sprintf_km($journey->{km_beeline}) %>) + % } + % elsif ($journey->{km_beeline} > 0.1) { + (Luftlinie: <%= sprintf_km($journey->{km_beeline}) %>) % } % else { ? @@ -123,7 +167,7 @@ </td> </tr> <tr> - <th scope="row">Geschwindigkeit</th> + <th scope="row">Tempo</th> <td> % if ($journey->{skip_route}) { <i class="material-icons right">location_off</i> @@ -136,11 +180,22 @@ ∗ % } % } + % elsif ($journey->{km_beeline} > 0.1 and $journey->{kmh_beeline} > 0.01) { + (<%= sprintf('%.f', $journey->{kmh_beeline}) %> km/h) + % } % else { ? % } </td> </tr> + % if ($journey->{user_data}{operator} or scalar @{ $journey->{user_data}{operators} // [] }) { + <tr> + <th scope="row">Betrieb</th> + <td> + %= $journey->{user_data}{operator} // join(q{, }, @{$journey->{user_data}{operators}}) + </td> + </tr> + % } % if ($journey->{messages} and @{$journey->{messages}}) { <tr> <th scope="row">Meldungen</th> @@ -152,6 +207,16 @@ </td> </tr> % } + % if ($journey->{user_data}{him_msg} and @{$journey->{user_data}{him_msg}}) { + <tr> + <th scope="row">Meldungen</th> + <td> + % for my $message (@{$journey->{user_data}{him_msg} // []}) { + <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %><br/> + % } + </td> + </tr> + % } % if ($journey->{user_data} and $journey->{user_data}{comment}) { <tr> <th scope="row">Kommentar</th> @@ -160,34 +225,6 @@ </td> </tr> % } - <tr> - <th scope="row">Route</th> - <td> - % my $within = 0; - % my $at_startstop = 0; - % for my $station (@{$journey->{route}}) { - % if ($station->[0] eq $journey->{from_name}) { - % $within = 1; $at_startstop = 1; - % } - % elsif ($station->[0] eq $journey->{to_name}) { - % $within = 0; $at_startstop = 1; - % } - % else { - % $at_startstop = 0; - % } - % if ($at_startstop or $within) { - <%= $station->[0] %> - % } - % else { - <span style="color: #808080;"><%= $station->[0] %></span> - % } - % if ($journey->{edited} & 0x0010) { - ∗ - % } - <br/> - % } - </td> - </tr> % if ($journey->{user_data} and $journey->{user_data}{wagongroups} and not exists $journey->{user_data}{wagons}) { <tr> <th scope="row">Rollmaterial</th> @@ -214,47 +251,128 @@ </td> </tr> % } + <tr> + <th scope="row">Route</th> + <td> + % my $before = 1; + % my $within = 0; + % my $at_startstop = 0; + % for my $station (@{$journey->{route}}) { + % if (($station->[1] and $station->[1] == $journey->{from_eva}) or $station->[0] eq $journey->{from_name}) { + % $within = 1; $at_startstop = 1; + % } + % elsif (($station->[1] and $station->[1] == $journey->{to_eva}) or $station->[0] eq $journey->{to_name}) { + % $within = 0; $at_startstop = 1; + % } + % else { + % $at_startstop = 0; + % } + <span style="color: #808080;"> + % if ($before and $station->[2]{sched_dep}) { + %= $station->[2]{sched_dep}->strftime('%H:%M') + % } + % elsif (not $before and $station->[2]{sched_arr}) { + %= $station->[2]{sched_arr}->strftime('%H:%M') + % } + </span> + % if ($at_startstop or $within) { + %= $station->[0] + % } + % else { + <span style="color: #808080;"><%= $station->[0] %></span> + % } + % if ($journey->{edited} & 0x0010) { + ∗ + % } + % if ($within or $at_startstop) { + <span style="color: #808080;"> + % if ($before and $station->[2]{rt_dep} and $station->[2]{dep_delay}) { + %= sprintf('%+d', $station->[2]{dep_delay}) + % } + % elsif (not $before and $station->[2]{rt_arr} and $station->[2]{arr_delay}) { + %= sprintf('%+d', $station->[2]{arr_delay}) + % } + </span> + % } + % if (($station->[1] and $station->[1] == $journey->{from_eva}) or $station->[0] eq $journey->{from_name}) { + % $before = 0; + % } + <br/> + % } + </td> + </tr> </table> </div> </div> - <div class="row hide-on-small-only"> - <div class="col s12 m6 l6 center-align"> - <a class="waves-effect waves-light red btn action-delete" - data-id="<%= $journey->{id} %>" - data-checkin="<%= $journey->{checkin}->epoch %>" - data-checkout="<%= $journey->{checkout}->epoch %>"> - <i class="material-icons left">delete_forever</i> - Löschen - </a> - </div> - <div class="col s12 m6 l6 center-align"> - %= form_for '/journey/edit' => (method => 'POST') => begin - %= hidden_field 'journey_id' => param('journey_id') - <button class="btn waves-effect waves-light" type="submit" name="action" value="edit"> - <i class="material-icons left">edit</i> - Bearbeiten - </button> - %= end + % if (stash('polyline_groups')) { + %= include '_map', station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') + % } + <div class="row"> + <div class="col s12 grey-text"> + <i class="material-icons tiny" aria-hidden="true"><%= $journey->{is_hafas} ? 'directions' : 'train' %></i> + %= $journey->{backend_name} || 'IRIS' + #<%= $journey->{id} %> </div> </div> - <div class="row hide-on-med-and-up"> - <div class="col s12 m6 l6 center-align"> - %= form_for '/journey/edit' => (method => 'POST') => begin - %= hidden_field 'journey_id' => param('journey_id') - <button class="btn waves-effect waves-light" type="submit" name="action" value="edit"> - <i class="material-icons left">edit</i> - Bearbeiten - </button> - %= end + % if (not stash('readonly')) { + % if (stash('with_share')) { + <div class="row"> + <div class="col s12 m6 l6"> + </div> + <div class="col s12 m6 l6 center-align"> + <a class="btn waves-effect waves-light action-share" + % if (stash('journey_visibility') eq 'public') { + data-url="<%= url_for('public_journey', name => current_user()->{name}, id => $journey->{id} )->to_abs->scheme('https'); %>" + % } + % else { + data-url="<%= url_for('public_journey', name => current_user()->{name}, id => $journey->{id} )->to_abs->scheme('https'); %>?token=<%= $journey->{from_eva} %>-<%= $journey->{checkin_ts} % 337 %>-<%= $journey->{sched_dep_ts} %>" + % } + data-text="<%= stash('share_text') %>" + > + <i class="material-icons left" aria-hidden="true">share</i> Teilen + </a> + </div> + </div> + % } + <div class="row hide-on-small-only"> + <div class="col s12 m6 l6 center-align"> + <a class="waves-effect waves-light red btn action-delete" + data-id="<%= $journey->{id} %>" + data-checkin="<%= $journey->{checkin}->epoch %>" + data-checkout="<%= $journey->{checkout}->epoch %>"> + <i class="material-icons left">delete_forever</i> + Löschen + </a> + </div> + <div class="col s12 m6 l6 center-align"> + %= form_for '/journey/edit' => (method => 'POST') => begin + %= hidden_field 'journey_id' => param('journey_id') + <button class="btn waves-effect waves-light" type="submit" name="action" value="edit"> + <i class="material-icons left" aria-hidden="true">edit</i> + Bearbeiten + </button> + %= end + </div> </div> - <div class="col s12 m6 l6 center-align" style="margin-top: 1em;"> - <a class="waves-effect waves-light red btn action-delete" - data-id="<%= $journey->{id} %>" - data-checkin="<%= $journey->{checkin}->epoch %>" - data-checkout="<%= $journey->{checkout}->epoch %>"> - <i class="material-icons left">delete_forever</i> - Löschen - </a> + <div class="row hide-on-med-and-up"> + <div class="col s12 m6 l6 center-align"> + %= form_for '/journey/edit' => (method => 'POST') => begin + %= hidden_field 'journey_id' => param('journey_id') + <button class="btn waves-effect waves-light" type="submit" name="action" value="edit"> + <i class="material-icons left" aria-hidden="true">edit</i> + Bearbeiten + </button> + %= end + </div> + <div class="col s12 m6 l6 center-align" style="margin-top: 1em;"> + <a class="waves-effect waves-light red btn action-delete" + data-id="<%= $journey->{id} %>" + data-checkin="<%= $journey->{checkin}->epoch %>" + data-checkout="<%= $journey->{checkout}->epoch %>"> + <i class="material-icons left" aria-hidden="true">delete_forever</i> + Löschen + </a> + </div> </div> - </div> + % } % } diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep index fd71f9c..5ca0e9e 100644 --- a/templates/landingpage.html.ep +++ b/templates/landingpage.html.ep @@ -1,4 +1,6 @@ % if (is_user_authenticated()) { + % my $status = stash('user_status'); + % my $user = stash('user'); % if (stash('error')) { <div class="row"> <div class="col s12"> @@ -13,21 +15,23 @@ % } <div class="row"> <div class="col s12 statuscol"> - % my $status = get_user_status(); % if ($status->{checked_in}) { - %= include '_checked_in', journey => $status; + %= include '_checked_in', journey => $status, journey_visibility => stash('journey_visibility'); % } % elsif ($status->{cancelled}) { + % if ( @{stash('timeline') // [] } ) { + %= include '_timeline_link', timeline => stash('timeline') + % } <div class="card info-color"> <div class="card-content"> - <span class="card-title">Zugausfall dokumentieren</span> + <span class="card-title">Ausfall dokumentieren</span> <p>Prinzipiell wärest du nun eingecheckt in - <%= $status->{train_type} %> <%= $status->{train_no} %> - ab <%= $status->{dep_name} %>, doch dieser Zug fällt aus. - </p> - <p>Falls du den Zugausfall z.B. für ein Fahrgastrechteformular - dokumentieren möchtest, wähle bitte jetzt die geplante - Zielstation aus.</p> + %= include '_format_train', journey => $status + ab <%= $status->{dep_name} %>, doch diese Fahrt fällt aus. + </p> + <p>Falls du den Ausfall z.B. für Fahrgastrechte + dokumentieren möchtest, wähle bitte jetzt das + vorgesehene Ziel aus.</p> <table> <tbody> % my $is_after = 0; @@ -45,42 +49,68 @@ </div> % } % else { - <div class="card"> - <div class="card-content"> - <span class="card-title">Hallo, <%= current_user()->{name} %>!</span> - <p>Du bist gerade nicht eingecheckt.</p> - <div class="geolocation"> - <button class="btn waves-effect waves-light btn-flat">Stationen in der Umgebung abfragen</button> - </div> - %= form_for 'list_departures' => begin - <div class="input-field"> - %= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', required => undef - <label for="station">Manuelle Eingabe (Name oder DS100)</label> + % if ( @{stash('timeline') // [] } ) { + %= include '_timeline_link', timeline => stash('timeline') + % } + %= form_for 'list_departures' => begin + <div class="card"> + <div class="card-content"> + <span class="card-title">Hallo, <%= $user->{name} %>!</span> + <p>Du bist gerade nicht eingecheckt.</p> + <div class="geolocation" data-recent="<%= join('|', map { $_->{external_id_or_eva} . ';' . $_->{name} . ';' . $_->{dbris} . ';' . $_->{efa} . ';' . $_->{hafas} . ';' . $_->{motis} } @{stash('recent_targets') // []} ) %>" data-backend="<%= $user->{backend_id} %>"> + <a class="btn waves-effect waves-light btn-flat request">Stationen in der Umgebung abfragen</a> </div> - <div class="center-align"> - <button class="btn waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> - <i class="material-icons left">send</i> - Abfahrten - </button> + %= hidden_field backend_dbris => $user->{backend_dbris} + <div class="input-field"> + %= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef + <label for="station">Manuelle Eingabe</label> </div> - %= end + </div> + <div class="card-action"> + <a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true"><%= $user->{backend_hafas} ? 'directions' : 'train' %></i><%= $user->{backend_name} // 'IRIS' %></a> + <button class="btn right waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> + <i class="material-icons left" aria-hidden="true">send</i> + Abfahrten + </button> + </div> </div> - </div> + %= end % } </div> </div> - <h1>Letzte Fahrten</h1> - %= include '_history_trains', date_format => '%d.%m', journeys => [get_user_travels(limit => 5, with_datetime => 1)]; + % if (not $user->{backend_name}) { + <div class="row"> + <div class="col s12"> + <div class="card purple white-text"> + <div class="card-content"> + <span class="card-title">Legacy-Backend ausgewählt</span> + <p> + Das aktuell aktive IRIS-Backend wird nicht mehr weiterentwickelt und voraussichtlich bald von der Deutschen Bahn abgeschaltet. + Schon jetzt ist die Datenqualität wegen zunehmend schlechter Datenaufbereitungsmöglichkeiten oft unzureichend. + Das bahn.de-Backend ist in fast jeder Hinsicht besser geeignet; lediglich bei Verspätungs- und Servicemeldungen ist es geringfügig weniger detailliert und Checkin-Vorschläge werden derzeit nicht unterstützt. + </p> + </div> + <div class="card-action"> + <a class="btn btn-flat" href="/account/select_backend?redirect_to=/">Backend wechseln</a> + </div> + </div> + </div> + </div> + % } + <h2 style="margin-left: 0.75rem;">Letzte Fahrten</h2> + %= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => $user->{id}, limit => 5, with_datetime => 1)]; % } % else { <div class="row"> <div class="col s12"> <p> - Travelynx erlaubt das Einchecken in Züge im Netz der Deutschen - Bahn. So können die eigenen Fahrten später inklusive Echtzeitdaten - und eingetragenen Servicemeldungen nachvollzogen und brennende - Fragen wie „Wie viele Stunden habe ich letzten Monat im Zug - vebracht?“ beantwortet werden. + Travelynx erlaubt das Einchecken in Verkehrsmittel (Busse, + Bahnen, Züge) unter anderem in Deutschland, Österreich, der + Schweiz, Luxemburg, Irland, Dänemark und Teilen der USA. So + können die eigenen Fahrten später inklusive Echtzeitdaten und + eingetragenen Servicemeldungen nachvollzogen und brennende + Fragen wie „Wie viele Stunden war ich letzten Monat unterwegs?“ + beantwortet werden. </p> <p> Die Idee dazu kommt von <a @@ -91,10 +121,12 @@ <ul> <li>Protokoll von Fahrplan- und Echtzeitdaten an Start- und Zielbahnhof</li> - <li>Web-Hooks und <a href="/api">API</a> zum automatisierten Auslesen des aktuellen Status</li> + <li>Teilen von aktuellen und vergangenen Fahrten mit anderen Personen</li> + <li>Web-Hooks und <a href="/api">API</a> zum automatisierten Einchecken und Auslesen des aktuellen Status</li> <li>Statistiken über Reisezeiten und Verspätungen</li> <li>Unterstützung beim Ausfüllen von Fahrgastrechteformularen</li> - <li>Optional: Öffentlich sichtbarer Reisestatus</li> + <li>Optional: Öffentlicher Reisestatus und öffentliche Angaben zu vergangenen Fahrten</li> + <!-- <li>Optional: Verknüpfung mit Träwelling</li> --> </ul> </p> <p> @@ -111,12 +143,12 @@ <div class="col s1 m1 l3"> </div> <div class="col s10 m10 l6 center-align"> - <a href="/register" class="waves-effect waves-light btn"><i class="material-icons left">add</i>Registrieren</a> - <a href="/login" class="waves-effect waves-light btn"><i class="material-icons left">account_circle</i>Anmelden</a> + % if (not app->config->{registration}{disabled}) { + <a href="/register" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">add</i>Registrieren</a> + % } + <a href="/login" class="waves-effect waves-light btn"><i class="material-icons left" aria-hidden="true">account_circle</i>Anmelden</a> </div> <div class="col s1 m1 l3"> </div> </div> % } - -%= include '_footer', version => stash('version') diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep index bd9bf8d..b275335 100644 --- a/templates/layouts/default.html.ep +++ b/templates/layouts/default.html.ep @@ -10,14 +10,17 @@ % while (my ($key, $value) = each %{stash('twitter') // {}}) { <meta name="twitter:<%= $key %>" content="<%= $value %>"> % } - % my $av = 'v32'; # asset version + % while (my ($key, $value) = each %{stash('opengraph') // {}}) { + <meta property="og:<%= $key %>" content="<%= $value %>"> + % } + % my $av = 'v97'; # asset version <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-96x96.png" sizes="96x96"> - <link rel="apple-touch-icon" href="/static/<%= $av %>/icons/icon-120x120.png"> - <link rel="apple-touch-icon" sizes="180x180" href="/static/<%= $av %>/icons/icon-180x180.png"> - <link rel="apple-touch-icon" sizes="152x152" href="/static/<%= $av %>/icons/icon-152x152.png"> - <link rel="apple-touch-icon" sizes="167x167" href="/static/<%= $av %>/icons/icon-167x167.png"> + <link rel="apple-touch-icon" href="/static/<%= $av %>/icons/touch-icon-120x120.png"> + <link rel="apple-touch-icon" sizes="180x180" href="/static/<%= $av %>/icons/touch-icon-180x180.png"> + <link rel="apple-touch-icon" sizes="152x152" href="/static/<%= $av %>/icons/touch-icon-152x152.png"> + <link rel="apple-touch-icon" sizes="167x167" href="/static/<%= $av %>/icons/touch-icon-167x167.png"> <link rel="manifest" href="/static/<%= $av %>/manifest.json"> % if (session('theme') and session('theme') eq 'dark') { %= stylesheet "/static/${av}/css/dark.min.css", id => 'theme' @@ -43,37 +46,35 @@ currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } addStyleSheet(currentTheme, 'theme'); - - function toggleTheme() { - currentTheme = otherTheme[currentTheme] || 'light'; - localStorage.setItem('theme', currentTheme); - addStyleSheet(currentTheme, 'theme'); - } </script> %= stylesheet "/static/${av}/css/material-icons.css" - %= stylesheet "/static/${av}/css/local.css" % if (stash('with_map')) { %= stylesheet "/static/${av}/leaflet/leaflet.css" % } %= javascript "/static/${av}/js/jquery-3.4.1.min.js" %= javascript "/static/${av}/js/materialize.min.js" - %= javascript "/static/${av}/js/travelynx-actions.min.js" + % my $min = ".min"; + % if (app->mode eq 'development') { + % $min = q{}; + % } + %= javascript "/static/${av}/js/travelynx-actions${min}.js" % if (stash('with_geolocation')) { - %= javascript "/static/${av}/js/geolocation.min.js" + %= javascript "/static/${av}/js/geolocation${min}.js" % } % if (stash('with_autocomplete')) { - %= javascript "/static/${av}/js/autocomplete.min.js" + %= javascript "/dyn/${av}/autocomplete.js?backend_id=" . (stash('backend_id') // 1), defer => undef % } % if (stash('with_map')) { %= javascript "/static/${av}/leaflet/leaflet.js" % } </head> +% my $acc = is_user_authenticated() && current_user(); <body> <div class="navbar-fixed"> <nav class="deep-purple"> <div class="nav-wrapper container"> - <a href="/" class="brand-logo left">travelynx</a> + <a href="/" class="brand-logo left"><span class="ca">tr</span><span class="cb">av</span><span class="cc">e</span><span class="cd">ly</span><span class="ce">nx</span></a> <ul id="nav-mobile" class="right"> <li class="loading"> <div class="preloader-wrapper small" style="margin-top: 0.5em; margin-bottom: -1em;"> @@ -88,15 +89,12 @@ </div> </div> </li> - <li class="waves-effect waves-light"> - <a onClick="javascript:toggleTheme()"><i class="material-icons">invert_colors</i></a> - </li> - % if (is_user_authenticated()) { - <li class="<%= navbar_class('/history') %>"><a href='/history' title="History"><i class="material-icons">history</i></a></li> - <li class="<%= navbar_class('/account') %>"><a href="/account" title="Account"><i class="material-icons">account_circle</i></a></li> + % if ($acc) { + <li class="<%= navbar_class('/history') %>"><a href='/history' title="Vergangene Zugfahrten"><i class="material-icons" aria-label="Vergangene Zugfahrten">history</i></a></li> + <li class="<%= navbar_class('/account') %>"><a href="/account" title="Account"><i class="material-icons" aria-label="Account"><%= $acc->{notifications} ? 'notifications' : 'account_circle' %></i></a></li> % } % else { - <li class="<%= navbar_class('/about') %>"><a href='/about' title="About"><i class="material-icons">info_outline</i></a></li> + <li class="<%= navbar_class('/about') %>"><a href='/about' title="Über Travelynx"><i class="material-icons" aria-label="Über Travelynx">info_outline</i></a></li> % } </ul> </div> @@ -114,16 +112,43 @@ </div> % } +% if (app->config->{announcement}) { <div class="container"> - % if (is_user_authenticated()) { - % my $acc = current_user(); - % if ($acc and $acc->{deletion_requested}) { - %= include '_deletion_note', timestamp => $acc->{deletion_requested} - % } + <div class="row"> + <div class="col s12 caution-color white-text"> + %= app->config->{announcement} + </div> + </div> +</div> +% } + +<div class="container"> + % if ($acc and $acc->{deletion_requested}) { + %= include '_deletion_note', timestamp => $acc->{deletion_requested} % } %= content + <div class="row" style="margin-top: 5em;"> + <div class="col s12 center-align grey-text"> + <a href="/about">travelynx</a> v<%= $version // '???' %> + <span style="margin-left: 0.5em; margin-right: 0.5em;">–</span> + <a href="/impressum">Impressum</a> + <span style="margin-left: 0.5em; margin-right: 0.5em;">–</span> + <a href="/impressum">Datenschutz</a> + <span style="margin-left: 0.5em; margin-right: 0.5em;">–</span> + <a href="/legend">Legende</a> + </div> + </div> + <div class="row"> + <div class="col s12 center-align grey-text config"> + Farbschema: + <a onClick="javascript:setTheme('light')">hell</a> + · + <a onClick="javascript:setTheme('dark')">dunkel</a> + · + <a onClick="javascript:setTheme('default')">automatisch</a> + </div> + </div> </div> - <script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { diff --git a/templates/legend.html.ep b/templates/legend.html.ep new file mode 100644 index 0000000..3dc113a --- /dev/null +++ b/templates/legend.html.ep @@ -0,0 +1,111 @@ +<div class="row"> + <div class="col s12"> + <h2>Legende</h2> + <p>travelynx verwendet bei Angaben zu Zügen und Stationen die folgenden Symbole.</p> + <h3>Abfahrtstafel und Route</h3> + <table class="striped"> + <tbody> + <tr> + <td><i class="material-icons">gps_off</i></td> + <td>Keine Echtzeitdaten vorhanden. Bei den angegebenen Zeiten handelt es sich um Angaben aus dem Fahrplan.</td> + </tr> + <tr> + <td>(HH:MM)</td> + <td>Ein Einstieg (Abfahrtstafel) bzw. Ausstieg (Route) ist an dieser Station möglicherweise nicht vorgesehen.</td> + </tr> + <tr> + <td><i class="material-icons">train</i></td> + <td>Backend: Deutsche Bahn (bahn.de oder IRIS-TTS).</td> + </tr> + <tr> + <td><i class="material-icons">directions</i></td> + <td>Backend: HAFAS.</td> + </tr> + </tbody> + </table> + <h3>Anschlusszüge</h3> + <table class="striped"> + <tbody> + <tr> + <td><i class="material-icons">directions_run</i></td> + <td>Knapper Umstieg. Anschluss wird möglicherweise nicht erreicht.</td> + </tr> + <tr> + <td><i class="material-icons">warning</i></td> + <td>Der Zug ist überbesetzt. Möglicherweise sind keine freien Sitzplätze vorhanden.</td> + </tr> + <tr> + <td><i class="material-icons">info_outline</i></td> + <td>Eingeschränkte Barrierefreihet, z.B. fehlendes oder defektes rollstuhlgerechtes WC.</td> + </tr> + <tr> + <td><i class="material-icons">remove</i></td> + <td>Mindestens ein Wagen fehlt.</td> + </tr> + <tr> + <td><i class="material-icons">compare_arrows</i></td> + <td>Abweichende Wagenreihung.</td> + </tr> + <tr> + <td><i class="material-icons">portable_wifi_off</i></td> + <td>WLAN ganz oder teilweise ausgefallen.</td> + </tr> + </tbody> + </table> + <h3>Auslastung</h3> + <p>Die erwartete Auslastung der ersten und zweiten Wagenklasse wird bei Anschlussvorschlägen sowie bei Unterwegshalten des aktuellen Zuges angegeben, sofern verfügbar.</p> + <table class="striped"> + <tbody> + <tr> + <td><i class="material-icons">help_outline</i></td> + <td>Auslastung unbekannt</td> + </tr> + <tr> + <td><i class="material-icons">person_outline</i></td> + <td>Niedrige Auslastung</td> + </tr> + <tr> + <td><i class="material-icons">people</i></td> + <td>Hohe Auslastung</td> + </tr> + <tr> + <td><i class="material-icons">priority_high</i></td> + <td>Sehr hohe Auslastung</td> + </tr> + <tr> + <td><i class="material-icons">not_interested</i></td> + <td>Der Zug ist ausgebucht</td> + <tr> + </tbody> + </table> + <h3>Profil und Timeline</h3> + <table class="striped"> + <tbody> + <tr> + <td><i class="material-icons"><%= visibility_icon('public') %></i></td> + <td>Öffentlicher Checkin</td> + </tr> + <tr> + <td><i class="material-icons"><%= visibility_icon('travelynx') %></i></td> + <td>Nur für Angemeldete</td> + </tr> + <tr> + <td><i class="material-icons"><%= visibility_icon('followers') %></i></td> + <td>Nur für Follower</td> + </tr> + <tr> + <td><i class="material-icons"><%= visibility_icon('unlisted') %></i></td> + <td>Nur mit Link / Token</td> + </tr> + <tr> + <td><i class="material-icons"><%= visibility_icon('private') %></i></td> + <td>Privater Checkin</td> + </tr> + <!-- <tr> + <td><i class="material-icons">person_add</i></td> + <td>Mitfahren (Checkin übernehmen)</td> + <tr> --> + </tbody> + </table> + </div> +</div> diff --git a/templates/login.html.ep b/templates/login.html.ep index a5aa8e3..3a9cc1f 100644 --- a/templates/login.html.ep +++ b/templates/login.html.ep @@ -44,6 +44,15 @@ </div> </div> % } + % elsif ($from eq 'auth_required') { + <div class="card"> + <div class="card-content"> + <span class="card-title">Login notwendig</span> + <p>Die aufgerufene Seite ist nur mit travelynx-Account zugänglich.</p> + <p><a href="/">Über travelynx</a> · <a href="/register">Registrieren</a></p> + </div> + </div> + % } </div> </div> % } @@ -55,7 +64,7 @@ <div class="row"> <div class="input-field col s12"> <i class="material-icons prefix">account_circle</i> - %= text_field 'user', id => 'user', class => 'validate', required => undef, maxlength => 60, autocomplete => 'username' + %= text_field 'user', id => 'user', class => 'validate', required => undef, pattern => '[0-9a-zA-Z_-]+', maxlength => 60, autocomplete => 'username' <label for="user">Account</label> </div> <div class="input-field col s12"> @@ -65,6 +74,11 @@ </div> </div> <div class="row"> + <div class="col s12 m12 l12"> + Mit der Anmeldung stimmst du den <a href="/tos">Nutzungsbedingungen</a> zu. + </div> + </div> + <div class="row"> <div class="col s3 m3 l3"> </div> <div class="col s6 m6 l6 center-align"> @@ -87,6 +101,11 @@ <div class="col s3 m3 l3"> </div> </div> + % if (app->config->{registration}{disabled}) { + <div class="row" style="margin-top: 2em;"> + <div class="col s12 center-align"> + <em>Diese Instanz erlaubt derzeit keine Registrierung neuer Accounts</em> + </div> + </div> + % } %= end - -%= include '_footer', version => stash('version') diff --git a/templates/not_found.html.ep b/templates/not_found.html.ep index c2bc09f..411080a 100644 --- a/templates/not_found.html.ep +++ b/templates/not_found.html.ep @@ -3,7 +3,12 @@ <div class="card caution-color"> <div class="card-content white-text"> <span class="card-title">404 Not Found</span> - <p>Diese Seite gibt es hier nicht.</p> + % if (my $m = stash('message')) { + <p><%= $m %></p> + % } + % else { + <p>Diese Seite gibt es hier nicht.</p> + % } </div> </div> </div> diff --git a/templates/passengerrights.html.ep b/templates/passengerrights.html.ep index 3d5d21d..c189657 100644 --- a/templates/passengerrights.html.ep +++ b/templates/passengerrights.html.ep @@ -2,10 +2,10 @@ <div class="row"> <div class="col s12"> <p> - Gemäß der Fahrgastrechte im Eisenbahnverkehr besteht ab 60 Minuten - Verspätung am Ziel ein Entschädigungsanspruch gegenüber dem - Eisenbahnverkehrsunternehmen. Dieser kann mit dem - Fahrgastrechteformular geltend gemacht werden. + Ab 60 Minuten Verspätung am Ziel besteht in einigen Fällen ein + Entschädigungsanspruch gegenüber dem Eisenbahnverkehrsunternehmen. + Dieser kann mit dem Fahrgastrechteformular oder online geltend + gemacht werden. </p> <p> Die folgenden Zugfahrten sind wahrscheinliche Kandidaten dafür. @@ -73,3 +73,64 @@ </table> </div> </div> + +<div class="row"> + <div class="col s12"> + <p> + Bei Abo-Tickets besteht teilweise die Möglichkeit, bereits ab 20 + Minuten Verspätung Fahrten gesammelt zu Entschädigungszwecken + einzureichen. Die folgenden Zugfahrten sind Kandidaten dafür. + Fahrten mit einer Verspätung von 60 Minuten oder mehr werden hier + nicht aufgeführt. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12"> + <table class="striped"> + <thead> + <tr> + <th>Datum</th> + <th>Zug</th> + <th>Verspätung</th> + </tr> + </thead> + <tbody> + % for my $journey (@{$abo_journeys}) { + % my $detail_link = '/journey/' . $journey->{id}; + <tr> + <td><%= $journey->{sched_departure}->strftime('%d.%m.%Y') %></td> + <td><a href="<%= $detail_link %>"> + <%= $journey->{type} %> <%= $journey->{line} // $journey->{no} %> + → <%= $journey->{to_name} %> + % if ($journey->{connection}) { + % $detail_link = '/journey/' . $journey->{connection}{id}; + </a><br/><a href="<%= $detail_link %>"> + <%= $journey->{connection}{type} %> <%= $journey->{connection}{line} // $journey->{connection}{no} %> + → <%= $journey->{connection}{to_name} %> + % } + </a></td> + <td> + % if ($journey->{cancelled}) { + % if ($journey->{has_substitute}) { + Ausfall, Ersatzverbindung + %= sprintf('%+d', $journey->{substitute_delay}) + % } + % else { + Ausfall ohne Ersatzverbindung + % } + % } + % elsif ($journey->{connection}) { + %= sprintf('%+d, ggf. Anschluss verpasst', $journey->{delay}) + % } + % else { + %= sprintf('%+d', $journey->{delay}) + % } + </td> + </tr> + % } + </tbody> + </table> + </div> +</div> diff --git a/templates/privacy.html.ep b/templates/privacy.html.ep index e8e6459..8cd3a78 100644 --- a/templates/privacy.html.ep +++ b/templates/privacy.html.ep @@ -1,63 +1,176 @@ <h1>Öffentliche Daten</h1> <div class="row"> <div class="col s12"> - Hier kannst du auswählen, welche Aspekte deines Accounts bzw. deiner - Bahnfahrten öffentlich einsehbar sind. Öffentliche Daten sind - grundsätzlich für <i>alle</i> einsehbar, die die (leicht erratbare) URL - kennen. + Hier kannst du auswählen, welche Personengruppen deine Fahrten + bei travelynx einsehen können. Zusätzlich kannst du die + Sichtbarkeit vergangener Fahrten auf die letzten vier Wochen + einschränken. Nach dem Einchecken hast du im Checkin-Fenster + die Möglichkeit, für die aktuelle Fahrt eine abweichende Sichtbarkeit + einzustellen. </div> </div> %= form_for '/account/privacy' => (method => 'POST') => begin -<h2>Aktueller Status</h2> - %= csrf_field +%= csrf_field +<h2>Fahrten</h2> <div class="row"> <div class="input-field col s12"> - <label> - %= radio_button status_level => 'private' - <span>Nicht sichtbar</span> - </label> + <div> + <label> + %= radio_button status_level => 'public' + <span><i class="material-icons left"><%= visibility_icon('public') %></i>Öffentlich: Im Profil und auf der Statusseite eingebunden und beliebig zugänglich.</span> + </label> + </div> </div> </div> <div class="row"> <div class="input-field col s12"> - <label> - %= radio_button status_level => 'intern' - <span>Nur für angemeldete Accounts</span> - </label> + <div> + <label> + %= radio_button status_level => 'travelynx' + <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Intern: Personen, die dir folgen, die auf dieser Seite angemeldet sind oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'followers' + <span><i class="material-icons left"><%= visibility_icon('followers') %></i>Follower: Personen, die dir folgen oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'unlisted' + <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button status_level => 'private' + <span><i class="material-icons left"><%= visibility_icon('private') %></i>Privat: nur für dich sichtbar.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= check_box past_status => 1 + <span>Wenn nicht eingecheckt: letztes Fahrtziel anzeigen, sofern die zugehörige Reise für die aufrufende Person sichtbar ist. Caveat: Die derzeitige Implementierung dieses Features gibt preis, ob deine letzte Fahrt öffentlich / lokal sichtbar war (→ Ziel angegeben) oder nicht (→ kein Ziel angegeben).</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s12"> + Wenn du (mit passender Sichtbarkeit) eingecheckt bist, werden unter + <a href="/status/<%= $name %>">/status/<%= $name %></a> sowie <a + href="/p/<%= $name %>">/p/<%= $name %></a> Fahrt, Start- und + Zielstation sowie Abfahrts- und Ankunftszeit gezeigt. Andernfalls + wird angegeben, dass du gerade nicht eingecheckt seist. + </div> + </div> +<h2>Vergangene Fahrten</h2> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button history_level => 'public' + <span><i class="material-icons left"><%= visibility_icon('public') %></i>Öffentlich: Beliebig zugänglich.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button history_level => 'travelynx' + <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Intern: Personen, die dir folgen oder die auf dieser Seite angemeldet sind.</span> + </label> + </div> </div> </div> <div class="row"> <div class="input-field col s12"> + <div> + <label> + %= radio_button history_level => 'followers' + <span><i class="material-icons left"><%= visibility_icon('followers') %></i>Follower: Personen, die dir folgen.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button history_level => 'private' + <span><i class="material-icons left"><%= visibility_icon('private') %></i>Privat: nur für dich sichtbar.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s12"> + Diese Einstellung bestimmt die Sichtbarkeit vergangener Fahrten + unter <a href="/p/<%= $name %>">/p/<%= $name %></a>. Vergangene + Fahrten, die über die Standardeinstellung (siehe oben) oder per + individueller Einstellung für die aufrufende Person sichtbar sind, + werden hier verlinkt. Derzeit werden nur die letzten zehn Fahrten + angezeigt; in Zukunft wird dies ggf. auf sämtliche Fahrten im + gewählten Zeitraum erweitert. + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button history_age => 'month' + <span>Letzte vier Wochen</span> + </label> + </div><div> <label> - %= radio_button status_level => 'extern' - <span>Öffentlich</span> + %= radio_button history_age => 'infinite' + <span>Alle Fahrten</span> </label> + </div> </div> </div> <div class="row"> <div class="col s12"> - Hier kannst du auswählen, ob dein aktueller Status unter <a - href="/status/<%= $name %>">/status/<%= $name %></a> abrufbar ist. - Wenn du eingecheckt bist, werden dort Zug, Start- und Zielstation, - Abfahrts- und Ankunftszeit gezeigt; andernfalls lediglich der - Zielbahnhof der letzten Reise. Wann die letzte Reise beendet wurde, - wird bewusst nicht angegeben. + Hier kannst du auswählen, ob alle deiner vergangenen Fahrten für + Profil und Detailseiten in Frage kommen oder nur die letzten vier + Wochen zugänglich sein sollen. Sofern du sie auf die letzten vier + Wochen beschränkst, sind ältere Fahrten nur über einen mit + Hilfe des „Teilen“-Knopfs erstellten Links zugänglich. </div> </div> +<h2>Sonstiges</h2> <div class="row"> <div class="input-field col s12"> <label> %= check_box public_comment => 1 - <span>Mit Kommentar</span> + <span>Kommentare anzeigen</span> </label> </div> </div> <div class="row"> <div class="col s12"> - Wenn aktiv, wird in deinem aktuellen Status auch der optionale - Freitext-Kommentar der Zugfahrt gezeigt. Wenn du gerade nicht - eingecheckt bist oder dein aktueller Status nicht öffentlich ist, - hat dieses Feld keine Auswirkungen. + Wenn aktiv, sind von dir eingetragene Freitext-Kommentare in deinem + aktuellen Status sowie bei deinen vergangenen Fahrten sichtbar. + Diese Einstellung kann nicht pro Fahrt verändert werden. </div> </div> <div class="row"> diff --git a/templates/profile.html.ep b/templates/profile.html.ep new file mode 100644 index 0000000..a2c965c --- /dev/null +++ b/templates/profile.html.ep @@ -0,0 +1,92 @@ +% if (stash('error')) { + <div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">Fehler</span> + <p><%= stash('error') %></p> + </div> + </div> + </div> + </div> +% } +<div class="row"> + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title"><%= $name %> + % if ($following and $follows_me) { + <i class="material-icons right">group</i> + % } + % elsif ($follow_reqs_me) { + <span class="right"> + <a href="/account/social/follow-requests-received"><i class="material-icons right">notifications</i></a> + </span> + % } + % elsif ($is_self) { + <a href="/account/profile"><i class="material-icons right">edit</i></a> + % } + </span> + % if ($bio) { + %== $bio + % } + % if (@{$metadata // []}) { + <table class="striped"> + % for my $entry (@{$metadata}) { + <tr> + <th scope="row"><%= $entry->{key} %></th> + <td scope="row"><%== $entry->{value}{html} %></td> + </tr> + % } + </table> + % } + </div> + % if ($following or $follow_requested or $can_follow or $can_request_follow) { + <div class="card-action <%= ($can_follow or $can_request_follow) ? 'right-align' : q{} %>"> + %= form_for "/social-action" => (method => 'POST') => begin + %= csrf_field + %= hidden_field target => $uid + %= hidden_field redirect_to => 'profile' + % if ($following) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="unfollow"> + Nicht mehr folgen + </button> + % } + % elsif ($follow_requested) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="cancel_follow_request"> + Folge-Anfrage zurücknehmen + </button> + % } + % elsif ($can_follow or $can_request_follow) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="follow_or_request"> + <i class="material-icons left" aria-hidden="true">person_add</i> + % if ($follows_me) { + Zurückfolgen + % } + % else { + Folgen + % } + % if ($can_request_follow) { + anfragen + % } + </button> + % } + %= end + </div> + % } + </div> + </div> +</div> +<div class="row"> + <div class="col s12 publicstatuscol" data-user="<%= $name %>" data-profile="1"> + %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey, from_profile => 1, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') + </div> +</div> +% if ($journeys and @{$journeys}) { + <div class="row"> + <div class="col s12"> + <h2>Vergangene Fahrten</h2> + </div> + </div> + %= include '_history_trains', date_format => '%d.%m.%Y', link_prefix => "/p/${name}/j/", journeys => $journeys; +% } diff --git a/templates/register.html.ep b/templates/register.html.ep index 1983e92..f9a486a 100644 --- a/templates/register.html.ep +++ b/templates/register.html.ep @@ -3,6 +3,7 @@ % } %= form_for '/register' => (method => 'POST') => begin %= csrf_field + %= hidden_field dt => DateTime->now(time_zone => 'Europe/Berlin')->epoch <div class="row"> <div class="input-field col l6 m12 s12"> <i class="material-icons prefix">account_circle</i> @@ -16,16 +17,21 @@ </div> <div class="input-field col l6 m12 s12"> <i class="material-icons prefix">lock</i> - %= password_field 'password', id => 'password', class => 'validate', required => undef, minlength => 8, autocomplete => 'new-password' + %= password_field 'password', id => 'password', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' <label for="password">Passwort</label> </div> <div class="input-field col l6 m12 s12"> <i class="material-icons prefix">lock</i> - %= password_field 'password2', id => 'password2', class => 'validate', required => undef, minlength => 8, autocomplete => 'new-password' + %= password_field 'password2', id => 'password2', class => 'validate', required => undef, minlength => 8, maxlength => 10000, autocomplete => 'new-password' <label for="password2">Passwort wiederholen</label> </div> </div> <div class="row"> + <div class="col s12 m12 l12"> + Mit deiner Registrierung stimmst du den <a href="/tos">Nutzungsbedingungen</a> zu. + </div> + </div> + <div class="row"> <div class="col s3 m3 l3"> </div> <div class="col s6 m6 l6 center-align"> @@ -46,12 +52,14 @@ Anmeldung ist erst nach Bestätigung der Mail-Adresse möglich. </p> <p> - Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung - und für die "Passwort vergessen"-Funktionalität verwendet und nicht - an Dritte weitergegeben. Die <a - href="/impressum">Datenschutzerklärung</a> beschreibt weitere - erhobene Daten sowie deren Zweck und Speicherfristen. - Accounts werden nach einem Jahr ohne Aktivität automatisch gelöscht. + Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung, + für die "Passwort vergessen"-Funktionalität und für wichtige + Informationen über den Account verwendet und nicht an Dritte + weitergegeben. Die <a href="/impressum">Datenschutzerklärung</a> + beschreibt weitere erhobene Daten sowie deren Zweck und + Speicherfristen. Accounts werden nach einem Jahr ohne Aktivität per + E-Mail über die bevorstehende Löschung informiert und nach vier + weiteren Wochen ohne Aktivität automatisch gelöscht. </p> <p> Bitte beachten: Travelynx ist ein privat betriebenes Projekt ohne @@ -61,5 +69,3 @@ </p> </div> </div> - -%= include '_footer', version => stash('version') diff --git a/templates/select_backend.html.ep b/templates/select_backend.html.ep new file mode 100644 index 0000000..e3db44d --- /dev/null +++ b/templates/select_backend.html.ep @@ -0,0 +1,85 @@ +<div class="row"> + <div class="col s12"> + <h2>Backend auswählen</h2> + <p style="text-align: justify;"> + Das ausgewählte Backend bestimmt die Datenquelle für Fahrten in travelynx. + <a href="#help">Details</a>. + </p> + </div> +</div> +%= form_for '/account/select_backend' => (method => 'POST') => begin + % if (stash('redirect_to')) { + %= hidden_field 'redirect_to' => stash('redirect_to') + % } + % if (@{stash('suggestions') // []}) { + <div class="row"> + <div class="col s12"> + <h3>Vorschläge</h3> + <p style="text-align: justify;"> + Anhand der Zielstation der letzten Fahrt und den + empfohlenen Nutzungsbereichen der verfügbaren Backends + (soweit bekannt). + </p> + </div> + </div> + % for my $backend (@{ stash('suggestions') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } + % } + <div class="row"> + <div class="col s12"> + <h3>Empfohlen</h3> + <p style="text-align: justify;"> + <strong>bahn.de</strong> für Regional- und Fernverkehr in Deutschland. + <strong>ÖBB</strong> für Nah-, Regional- und Fernverkehr in Österreich sowie Regional- und Fernverkehr in der EU. + </p> + </div> + </div> + % for my $backend (grep { $_->{recommended} } @{ stash('backends') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } + <div class="row"> + <div class="col s12"> + <h3>Verbünde</h3> + <p style="text-align: justify;"> + Diese Backends sind meist die beste Wahl für + Nahverkehrsfahrten in der jeweiligen Region. + Backends außerhalb Deutschlands sind im Regelfall auch + für dortigen Regional- und Fernverkehr die beste Wahl. + </p> + </div> + </div> + % for my $backend (grep { $_->{association} } @{ stash('backends') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } + <div class="row"> + <div class="col s12"> + <h3>Experimentell oder abgekündigt</h3> + <p style="text-align: justify;"> + Einchecken auf eigene Gefahr. + </p> + </div> + </div> + % for my $backend (grep { $_->{experimental} or $_->{legacy} } @{ stash('backends') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } +%= end +<div class="row"> + <div class="col s12"> + <h2 id="help">Details</h2> + <p> + <strong>Deutsche Bahn: bahn.de</strong> ist eine gute Wahl für Fahrten des Nah-, Regional- und Fernverkehrs innerhalb Deutschlands. + Dieses Backend bietet überwiegend korrekte Echtzeit- und Kartendaten sowie Wagenreihungen. + Bei Nahverkehrsfahrten sind die Echtzeit- und Kartendaten meist nicht so gut wie bei den APIs des jeweiligen Verkehrsverbunds. + <p> + <strong>ÖBB</strong> liefern Kartendaten und Wagenreihungen für Fernverkehr in Deutschland und Umgebung, jedoch keine Meldungen. Echtzeitdaten sind teilweise verfügbar. + </p> + <p> + <strong>Deutsche Bahn: IRIS-TTS</strong> liefert Echtzeitdaten (nur am Start- und Zielbahnhof), Wagenreihungen und Verspätungsmeldungen für Regional- und Fernverkehr in Deutschland. Kartendaten und Angaben zu Unterwegshalten sind nur teilweise verfügbar. Dieses Backend wird nicht mehr weiterentwickelt. Die zugehörige API wird voraussichtlich im Laufe des Jahres 2025 abgeschaltet. + </p> + <p> + <strong>Transitous</strong> ist ein Aggregator für eine Vielzahl von Verkehrsunternehmen. + Die Datenqualität variiert. + </p> + </div> +</div> diff --git a/templates/social.html.ep b/templates/social.html.ep new file mode 100644 index 0000000..dafabc4 --- /dev/null +++ b/templates/social.html.ep @@ -0,0 +1,68 @@ +<h1>Interaktion</h1> +<div class="row"> + <div class="col s12"> + Hier kannst du einstellen, ob und wie dir Accounts folgen können. + Die hier vorgenommenen Einstellungen haben keinen Einfluss + darauf, ob/wie du anderen Accounts folgen kannst. + </div> +</div> +%= form_for '/account/social' => (method => 'POST') => begin +%= csrf_field +<h2>Folgen</h2> + <div class="row"> + <div class="input-field col s12"> + <p> + Accounts die dir folgen können alle Checkins sehen, die nicht als privat oder nur mit Link zugänglich vermerkt sind. + Später werden sie zusätzlich die Möglichkeit haben, deinen aktuellen Checkin (sofern sichtbar) als Teil einer Übersicht über die Checkins aller gefolgten Accounts direkt anzusehen (analog zur Timeline im Fediverse). + </p> + <p> + Du hast jederzeit die Möglichkeit, Accounts aus deiner Followerliste zu entfernen, Folge-Anfragen abzulehnen oder Accounts zu blockieren, so dass sie dir weder folgen noch neue Folge-Anfragen stellen können. + </p> + <p> + Angaben zu folgenden und gefolgten Accounts sind grundsätzlich nur für dich sichtbar. + </p> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'yes' + <span>Andere Accounts können dir direkt (ohne eine Anfrage zu stellen) folgen.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'request' + <span>Andere Accounts können dir Folge-Anfragen stellen. Du musst sie explizit annehmen, bevor sie dir folgen.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'no' + <span>Accounts können dir nicht folgen. Accounts, die dir bereits folgen, werden hiervon nicht berührt.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s3 m3 l3"> + </div> + <div class="col s6 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + <div class="col s3 m3 l3"> + </div> + </div> +%= end diff --git a/templates/social_list.html.ep b/templates/social_list.html.ep new file mode 100644 index 0000000..686e5c3 --- /dev/null +++ b/templates/social_list.html.ep @@ -0,0 +1,259 @@ +%= form_for "/social-action" => (method => 'POST') => begin +%= csrf_field +%= hidden_field redirect_to => '/account' +% my $count = scalar @{$entries}; +% if ($type eq 'follow-requests-received') { + <div class="row"> + <div class="col s12"> + <h2>Erhaltene Folge-Anfragen</h2> + </div> + </div> + % if ($notifications) { + <div class="row center-align"> + <div class="col s12"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="clear_notifications"> + <i class="material-icons left" aria-hidden="true">notifications_off</i> Als gelesen markieren + </button> + </div> + </div> + % } + <div class="row center-align"> + <div class="col s4"> + <i class="material-icons">block</i><br/> Blockieren + </div> + <div class="col s4"> + <i class="material-icons">cancel</i><br/> Ablehnen + </div> + <div class="col s4"> + <i class="material-icons">check</i><br/> Annehmen + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <button class="btn red waves-effect waves-light" type="submit" name="reject_follow_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">cancel</i> Alle ablehnen + </button> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <button class="btn waves-effect waves-light" type="submit" name="accept_follow_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">check</i> Alle annehmen + </button> + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s6"> + <button class="btn red waves-effect waves-light" type="submit" name="block" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">block</i> Alle blockieren + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'follow-requests-sent') { + <div class="row"> + <div class="col s12"> + <h2>Gestellte Folge-Anfragen</h2> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <i class="material-icons">cancel</i><br/> Zurücknehmen + </div> + </div> +% } +% elsif ($type eq 'followers') { + <div class="row"> + <div class="col s12"> + % if ($count == 1) { + <h2>Dir folgt ein Account</h2> + % } + % else { + <h2>Dir folgen <%= $count %> Accounts</h2> + % } + </div> + </div> + <div class="row center-align"> + <div class="col s4"> + <i class="material-icons">block</i><br/> Blockieren + </div> + <div class="col s4"> + <i class="material-icons">remove</i><br/> Entfernen + </div> + <div class="col s4"> + <i class="material-icons">person_add</i><br/> Zurückfolgen + </div> + </div> + <div class="row center-align"> + <div class="col s4"> + </div> + <div class="col s4"> + <i class="material-icons">access_time</i><br/> Folgen angefragt + </div> + <div class="col s4"> + <i class="material-icons">group</i><br/> Du folgst diesem Account + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s6"> + <button class="btn grey waves-effect waves-light" type="submit" name="remove_follower" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entfernen + </button> + </div> + <div class="col s6"> + <button class="btn waves-effect waves-light" type="submit" name="follow_or_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">group_add</i> Allen zurückfolgen + </button> + </div> + </div> + <div class="row center-align"> + <div class="col s6"> + <button class="btn red waves-effect waves-light" type="submit" name="block" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">block</i> Alle blockieren + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'follows') { + <div class="row"> + <div class="col s12"> + % if ($count == 1) { + <h2>Du folgst einem Account</h2> + % } + % else { + <h2>Du folgst <%= $count %> Accounts</h2> + % } + </div> + </div> + <div class="row center-align"> + <div class="col s6"> + <i class="material-icons">group</i><br/> Folgt dir + </div> + <div class="col s6"> + <i class="material-icons">remove</i><br/> Nicht mehr folgen + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s12"> + <button class="btn grey waves-effect waves-light" type="submit" name="unfollow" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entfernen + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'blocks') { + <div class="row"> + <div class="col s12"> + <h2>Blockierte Accounts</h2> + <p> + Blockierte Accounts können dir nicht folgen und keine Folge-Anfragen stellen. + Sie haben weiterhin Zugriff auf deine als öffentlich oder travelynx-intern markierten Checkins. + </p> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <i class="material-icons">remove</i><br/>Entblockieren + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s12"> + <button class="btn grey waves-effect waves-light" type="submit" name="unblock" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entblockieren + </button> + </div> + </div> + --> +% } +%= end + +<div class="row"> + <div class="col s12"> + %= form_for "/social-action" => (method => 'POST') => begin + %= csrf_field + %= hidden_field redirect_to => "/account/social/$type" + <table class="striped"> + % for my $entry (@{$entries}) { + <tr> + <td><a href="/p/<%= $entry->{name} %>"><%= $entry->{name} %></a></td> + % if ($type eq 'follow-requests-received') { + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="block" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="blockieren">block</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="reject_follow_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="ablehnen">cancel</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="accept_follow_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="annehmen">check</i> + </button> + </td> + % } + % elsif ($type eq 'follow-requests-sent') { + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="cancel_follow_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="zurücknehmen">cancel</i> + </button> + </td> + % } + % elsif ($type eq 'followers') { + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="block" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="blockieren">block</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="remove_follower" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="entfernen">remove</i> + </button> + </td> + <td class="right-align"> + % if ($entry->{following_back}) { + <i class="material-icons" aria-label="ihr folgt euch gegenseitig">group</i> + % } + % elsif ($entry->{followback_requested}) { + <i class="material-icons" aria-label="Zurückfolgen angefragt">access_time</i> + % } + % elsif ($entry->{can_follow_back} or $entry->{can_request_follow_back}) { + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="follow_or_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="zurückfolgen">person_add</i> + </button> + % } + </td> + % } + % elsif ($type eq 'follows') { + <td class="right-align"> + % if ($entry->{following_back}) { + <i class="material-icons" aria-label="ihr folgt euch gegenseitig">group</i> + % } + </td> + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="unfollow" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="entfolgen">remove</i> + </button> + </td> + % } + % elsif ($type eq 'blocks') { + <td class="right-align"> + <button class="btn-flat blue-text waves-effect waves-light" type="submit" name="unblock" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="von Blockliste entefrnen">remove</i> + </button> + </td> + % } + </tr> + % } + </table> + %= end + </div> +</div> diff --git a/templates/timeline-checked-in.html.ep b/templates/timeline-checked-in.html.ep new file mode 100644 index 0000000..0ed492e --- /dev/null +++ b/templates/timeline-checked-in.html.ep @@ -0,0 +1,3 @@ +<div class="timeline-in-transit"> + %= include '_timeline-checked-in', journeys => $journeys +</div> diff --git a/templates/traewelling.html.ep b/templates/traewelling.html.ep new file mode 100644 index 0000000..49b5c80 --- /dev/null +++ b/templates/traewelling.html.ep @@ -0,0 +1,227 @@ +% if (my $invalid = stash('invalid')) { + %= include '_invalid_input', invalid => $invalid +% } + +<h1>Träwelling</h1> + +% if (flash('new_traewelling')) { + <div class="row"> + <div class="col s12"> + % if ($traewelling->{token}) { + <div class="card success-color"> + <div class="card-content white-text"> + <span class="card-title">Träwelling verknüpft</span> + % my $user = $traewelling->{data}{user_name} // '???'; + <p>Dein travelynx-Account ist nun mit dem Träwelling-Account <b>@<%= $user %></b> verbunden.</p> + </div> + </div> + % } + % elsif (my $login_err = flash('login_error')) { + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">Login-Fehler</span> + <p>Der Login bei Träwelling ist fehlgeschlagen: <%= $login_err %></p> + </div> + </div> + % } + % elsif (my $logout_err = stash('logout_error')) { + <div class="card caution-color"> + <div class="card-content white-text"> + <span class="card-title">Logout-Fehler</span> + <p>Der Logout bei Träwelling ist fehlgeschlagen: <%= $logout_err %>. + Dein Token bei travelynx wurde dennoch gelöscht, so + dass nun kein Zugriff von travelynx auf Träwelling mehr + möglich ist. In den <a + href="https://traewelling.de/settings">Träwelling-Einstellungen</a> + kannst du ihn vollständig löschen.</p> + </div> + </div> + % } + </div> + </div> +% } + +% if ($traewelling->{token} and ($traewelling->{expired} or $traewelling->{expiring})) { + <div class="row"> + <div class="col s12"> + <div class="card caution-color"> + <div class="card-content white-text"> + % if ($traewelling->{expired}) { + <span class="card-title">Token abgelaufen</span> + % } + % else { + <span class="card-title">Token läuft bald ab</span> + % } + <p>Melde deinen travelynx-Account von Träwelling ab und + verbinde ihn mit deinem Träwelling-Passwort erneut, + um einen neuen Token zu erhalten.</p> + </div> + <div class="card-action"> + %= form_for '/account/traewelling' => (method => 'POST') => begin + %= csrf_field + <button class="btn-flat waves-effect waves-light white-text" type="submit" name="action" value="logout"> + <i class="material-icons left" aria-hidden="true">sync_disabled</i> + Abmelden + </button> + %= end + </div> + </div> + </div> + </div> +% } + +% if (not $traewelling->{token}) { + <div class="row"> + <div class="col s12"> + <p> + Hier hast du die Möglichkeit, deinen travelynx-Account mit einem + Account bei <a href="https://traewelling.de">Träwelling</a> zu + verknüpfen. Dies erlaubt die automatische Übernahme zukünftiger + Checkins zwischen den beiden Diensten. Checkins, die + vor dem Verknüpfen der Accounts stattgefunden haben, werden + nicht synchronisiert. Bei synchronisierten Checkins wird der + zugehörige Träwelling-Status von deiner travelynx-Statusseite + aus verlinkt. + </p> + </div> + </div> + <div class="row"> + %= form_for '/oauth/traewelling' => (method => 'POST') => begin + %= csrf_field + <div class="col s12 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="connect"> + Verknüpfen + <i class="material-icons right">send</i> + </button> + </div> + %= end + </div> +% } +% else { + <div class="row"> + <div class="col s12"> + <p> + Dieser travelynx-Account ist mit dem Träwelling-Account + % if (my $user = $traewelling->{data}{user_name}) { + <a href="https://traewelling.de/@<%= $user %>"><%= $user %></a> + % } + % else { + %= $traewelling->{email} + % } + verknüpft. Der aktuelle Token läuft <%= $traewelling->{expires_on}->strftime('am %d.%m.%Y um %H:%M Uhr') %> ab. + </p> + </div> + </div> + %= form_for '/account/traewelling' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button sync_source => 'none' + <span>Keine Synchronisierung</span> + </label> + </div> + </div> + <div class="input-field col s12"> + <div> + <label> + %= radio_button sync_source => 'travelynx' + <span>Checkin-Synchronisierung travelynx → Träwelling</span> + </label> + </div> + <div> + <label> + %= check_box toot => 1 + <span>… Checkin im Fediverse veröffentlichen</span> + </label> + </div> + <p>Die Synchronisierung erfolgt spätestens drei Minuten nach der + Zielwahl. Es werden ausschließlich Checkins mittels + DB (IRIS-TTS) und DB (HAFAS) synchornisiert. Beachte, dass + die Synchronisierung travelynx → Träwelling unabhängig von + der eingestellten Sichtbarkeit des Checkins erfolgt. + travelynx reicht die Sichtbarkeit aber an Träwelling + weiter. Träwelling-Checkins können von travelynx aktuell + nicht rückgängig gemacht werden. Eine nachträgliche + Änderung der Zielstation wird nicht übernommen. Fediverse + bezieht sich auf den in den <a + href="https://traewelling.de/settings">Träwelling-Einstellungen</a> + verknüpften Account.</p> + </div> + <div class="input-field col s12"> + <div> + <label> + %= radio_button sync_source => 'traewelling' + <span>Checkin-Synchronisierung Träwelling → travelynx</span> + </label> + </div> + <p>Alle fünf Minuten wird dein Status auf Träwelling abgefragt. + Falls du gerade in eingecheckt bist, wird der Checkin von + travelynx übernommen. Träwelling-Checkins in Züge + außerhalb des deutschen Schienennetzes werden noch nicht + unterstützt. Die Sichtbarkeit von Träwelling-Checkins wird + derzeit von travelynx nicht berücksichtigt.</p> + </div> + </div> + <div class="row hide-on-small-only"> + <div class="col s12 m6 l6 center-align"> + <button class="btn waves-effect waves-light red" type="submit" name="action" value="logout"> + Abmelden + <i class="material-icons right" aria-hidden="true">sync_disabled</i> + </button> + </div> + <div class="col s12 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="config"> + Speichern + <i class="material-icons right" aria-hidden="true">send</i> + </button> + </div> + </div> + <div class="row hide-on-med-and-up"> + <div class="col s12 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="config"> + Speichern + <i class="material-icons right" aria-hidden="true">send</i> + </button> + </div> + <div class="col s12 m6 l6 center-align" style="margin-top: 1em;"> + <button class="btn waves-effect waves-light red" type="submit" name="action" value="logout"> + Abmelden + <i class="material-icons right" aria-hidden="true">sync_disabled</i> + </button> + </div> + </div> + %= end + <h2>Status</h2> + <div class="row"> + <div class="col s12""> + % if ($traewelling->{latest_run}->epoch) { + Letzter Checkin <%= $traewelling->{latest_run}->strftime('am %d.%m.%Y um %H:%M:%S') %><br/> + % if ($traewelling->{errored}) { + <i class="material-icons left">error</i> + Fehler: <%= $traewelling->{data}{error} %> + % } + % } + % else { + Bisher wurde noch kein Checkin übernommen. + % } + </div> + </div> + <h2>Log</h2> + <div class="row"> + <div class="col s12""> + <ul> + % for my $log_entry (@{$traewelling->{data}{log} // []}) { + <li> + <%= $log_entry->[0]->strftime('%d.%m.%Y %H:%M:%S') %> – + % if ($log_entry->[2]) { + Träwelling <a href="https://traewelling.de/status/<%= $log_entry->[2] %>">#<%= $log_entry->[2] %></a> – + % } + %= $log_entry->[1] + </li> + % } + </ul> + </div> + </div> +% } diff --git a/templates/use_history.html.ep b/templates/use_history.html.ep index e8e129f..f91ca16 100644 --- a/templates/use_history.html.ep +++ b/templates/use_history.html.ep @@ -4,10 +4,14 @@ <p> Travelynx kann anhand deiner vergangenen Fahrten Verbindungen zum Einchecken vorschlagen. Fährst zu z.B regelmäßig von Dortmund Hbf - nach Essen Hbf, werden dir in Dortmund bevorzugt Züge angezeigt, die über - Essen fahren. Bei Auswahl dieser wird nicht nur in den Zug eingecheckt, + nach Essen Hbf, werden dir in Dortmund bevorzugt Fahrten angezeigt, die + Essen passieren. Bei Auswahl dieser wird nicht nur in die Fahrt eingecheckt, sondern auch direkt Essen Hbf als Ziel eingetragen. <p/> + <p> + Beachte, dass nicht alle von travelynx unterstützten Backends die + für dieses Feature notwendigen Daten bereitstellen. + </p> <!-- <p> Falls du das nicht nützlich findest oder nicht möchtest, dass deine regelmäßigen (Anschluss-)Züge auf deinem Bildschirm sichtbar sind, diff --git a/templates/user_status.html.ep b/templates/user_status.html.ep index 78ef547..bf11004 100644 --- a/templates/user_status.html.ep +++ b/templates/user_status.html.ep @@ -1,5 +1,5 @@ <div class="row"> <div class="col s12 publicstatuscol" data-user="<%= $name %>"> - %= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey + %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') </div> </div> diff --git a/templates/webhooks.html.ep b/templates/webhooks.html.ep index 930732d..280ba27 100644 --- a/templates/webhooks.html.ep +++ b/templates/webhooks.html.ep @@ -88,12 +88,13 @@ Gültige Werte für reason sind derzeit: <ul> <li><b>ping</b> (nach jeder gespeicherten Änderung in diesem Formular)</li> - <li><b>checkin</b> (in einen Zug eingecheckt – Zielstation ist noch nicht bekannt)</li> - <li><b>update</b> (eingecheckt und Ziel gewählt oder geändert)</li> - <li><b>checkout</b> (aus einem Zug ausgecheckt)</li> + <li><b>checkin</b> (eingecheckt – Zielstation ist noch nicht bekannt)</li> + <li><b>update</b> (eingecheckt und Ziel/Kommentar/Sichtbarkeit geändert)</li> + <li><b>checkout</b> (ausgecheckt)</li> <li><b>undo</b> (checkin oder checkout wurde rückgängig gemacht)</li> </ul> - Falls der Zug das Ziel bei der Zielwahl schon erreicht hat, wird ohne + Falls die Fahrt das Ziel bei der Zielwahl schon erreicht hat, wird ohne <b>update</b> direkt ein <b>checkout</b> abgeschickt. </p> + </div> </div> diff --git a/templates/year_in_review.html.ep b/templates/year_in_review.html.ep new file mode 100644 index 0000000..0518dc1 --- /dev/null +++ b/templates/year_in_review.html.ep @@ -0,0 +1,169 @@ +<div class="row"> + <div class="col s12 m12 l12"> + <div class="carousel carousel-slider center"> + <div class="carousel-item" href="#one"> + <h2>Jahresrückblick <%= $year %></h2> + <p> + Du hast in diesem Jahr <strong><%= $stats->{num_trains} %> Fahrten</strong> von und zu <strong><%= $review->{num_stops} %> Betriebsstellen</strong> in travelynx erfasst. + % if ($stats->{num_trains} > 365) { + Das sind mehr als <strong><%= $review->{trains_per_day} %> Fahrten pro Tag</strong>! + % } + </p> + <p> + % if ($review->{traveling_min_total} > 525) { + Insgesamt hast du mindestens <strong><%= $review->{traveling_percentage_year} %> des Jahres</strong> + (<%= $review->{traveling_time_year} %>) unterwegs verbracht. + % } + % else { + Insgesamt hast du mindestens <strong><%= $review->{traveling_time_year} %></strong> unterwegs verbracht. + % } + </p> + <p> + Dabei hast du ca. <strong><%= $review->{km_route} %> km</strong> (Luftlinie: <%= $review->{km_beeline} %> km) zurückgelegt. + % if ($review->{km_circle} > 1) { + Das entspricht <strong><%= $review->{km_circle_h} %> Fahrten um die Erde</strong>. + % } + % elsif ($review->{km_diag} > 1) { + Das entspricht <strong><%= $review->{km_diag_h} %> Reisen zum Mittelpunkt der Erde und zurück</strong>. + % } + </p> + <p> + <em>Hier streichen</em> 🐈 <em>oder unten klicken für nächste Seite</em> + </p> + </div> + <div class="carousel-item" href="#two"> + <h2>Eine typische Fahrt</h2> + <p> + % if ($review->{typical_stops_3} and $review->{typical_type_1}) { + … führte dich mit + % if ($review->{typical_type_1} eq 'S') { + einer <strong>S-Bahn</strong> + % } + % else { + einem <strong><%= $review->{typical_type_1} %></strong> + % } + durch das Dreieck <strong><%= join(' / ', @{$review->{typical_stops_3}}) %></strong>. + % } + % elsif ($review->{typical_stops_2}) { + … befand sich jederzeit auf deiner Pendelstrecke zwischen <strong><%= $review->{typical_stops_2}[0] %></strong> und <strong><%= $review->{typical_stops_2}[1] %></strong>. + % } + </p> + <p> + Im Mittel benötigte sie <strong><%= $review->{typical_time} %></strong> für eine Entfernung von ca. <strong><%= $review->{typical_km} %> km</strong> (<%= $review->{typical_kmh} %> km/h). + </p> + % if ($review->{typical_delay_dep} == 0 and $review->{typical_delay_arr} == 0) { + <p>Außerdem war sie <strong>komplett pünktlich</strong>. Beeindruckend!</p> + % } + % elsif ($review->{typical_delay_dep} > 0 and $review->{typical_delay_arr} > 0) { + <p>Sie fuhr <strong><%= $review->{typical_delay_dep_h} %></strong> zu spät + % if ($review->{typical_delay_arr} < $review->{typical_delay_dep}) { + ab, konnte aber einen Teil der Verspätung wieder herausholen. + Ihr Ziel erreichte sie nur noch <strong><%= $review->{typical_delay_arr_h} %></strong> später als vorgesehen. + % } + % elsif ($review->{typical_delay_arr} == $review->{typical_delay_dep}) { + ab und kam mit der gleichen Verspätung am Ziel an. + % } + % else { + ab und schlich mit <strong>+<%= $review->{typical_delay_arr} %></strong> ins Ziel. + % } + % } + </div> + <div class="carousel-item" href="#three"> + <h2>High Scores</h2> + % if ($review->{longest_t_id}) { + <p><a href="/journey/<%= $review->{longest_t_id} %>">Längste Fahrt</a>: + <strong><%= $review->{longest_t_time} %></strong> mit <strong><%= $review->{longest_t_type} %> <%= $review->{longest_t_lineno} %></strong> von <%= $review->{longest_t_from} %> nach <%= $review->{longest_t_to} %>.</p> + % if ($review->{longest_km_id} == $review->{longest_t_id}) { + <p>Mit <strong><%= $review->{longest_km_km} %> km</strong> war sie gleichzeitig deine weiteste Fahrt.</p> + % } + % } + % if ($review->{longest_km_id} and $review->{longest_km_id} != $review->{longest_t_id}) { + <p><a href="/journey/<%= $review->{longest_km_id} %>">Größte Entfernung</a>: + <strong><%= $review->{longest_km_km} %> km</strong> mit <strong><%= $review->{longest_km_type} %> <%= $review->{longest_km_lineno} %></strong> von <%= $review->{longest_km_from} %> nach <%= $review->{longest_km_to} %>.</p> + % } + % if ($review->{shortest_t_id}) { + <p><a href="/journey/<%= $review->{shortest_t_id} %>">Kürzeste Fahrt</a>: + <strong><%= $review->{shortest_t_time} %></strong> mit <strong><%= $review->{shortest_t_type} %> <%= $review->{shortest_t_lineno} %></strong> von <%= $review->{shortest_t_from} %> nach <%= $review->{shortest_t_to} %>.</p> + % if ($review->{shortest_km_id} == $review->{shortest_t_id}) { + <p>Mit <strong><%= $review->{shortest_km_m} %> m</strong> war sie gleichzeitig dein kleinster Katzensprung.</p> + % } + % } + % if ($review->{shortest_km_id} and $review->{shortest_km_id} != $review->{shortest_t_id}) { + <p><a href="/journey/<%= $review->{shortest_km_id} %>">Kleinster Katzensprung</a>: + <strong><%= $review->{shortest_km_m} %> m</strong> mit <strong><%= $review->{shortest_km_type} %> <%= $review->{shortest_km_lineno} %></strong> von <%= $review->{shortest_km_from} %> nach <%= $review->{shortest_km_to} %>.</p> + % } + </div> + <div class="carousel-item" href="#four"> + <h2>Oepsie Woepsie</h2> + % if ($review->{issue1_count}) { + <p><strong><%= $review->{issue_percent} %></strong> aller Fahrten liefen nicht wie vorgesehen ab.<br/> + Die häufigsten Anmerkungen waren:</p> + % for my $i (1 .. 3) { + % if ($review->{"issue${i}_count"}) { + <p><strong><%= $review->{"issue${i}_count"} %>×</strong> „<%= $review->{"issue${i}_text"} %>“</p> + % } + % } + % } + <p>Lediglich <strong><%= $review->{punctual_percent_h} %></strong> der Fahrten waren pünktlich auf die Minute.</p> + </div> + <div class="carousel-item" href="#five"> + <h2>De trein is stukkie wukkie</h2> + <p> + % if ($review->{fgr_percent} >= 0.1) { + <strong><%= $review->{fgr_percent_h} %></strong> deiner Fahrten hatten mindestens eine Stunde Verspätung + % } + % if ($review->{cancel_count}) { + % if ($review->{fgr_percent} >= 0.1) { + und <strong><%= $review->{cancel_count} %></strong> kamen gar nicht erst am Ziel an. + % } + % else { + <strong><%= $review->{cancel_count} %></strong> deiner geplanten Fahrten sind ausgefallen. + % } + % } + </p> + % if ($review->{most_delayed_id}) { + <p> + Mit <strong><%= $review->{most_delayed_delay_arr} %></strong> hatte <a href="/journey/<%= $review->{most_delayed_id} %>"><%= $review->{most_delayed_type} %> <%= $review->{most_delayed_lineno} %></a> <%= $review->{most_delayed_from} %> → <%= $review->{most_delayed_to} %> die größte Verspätung. + </p> + % } + % if ($review->{most_delay_id}) { + <p> + Die Fahrt mit <a href="/journey/<%= $review->{most_delay_id} %>"><%= $review->{most_delay_type} %> <%= $review->{most_delay_lineno} %></a> + von <%= $review->{most_delay_from} %> nach <%= $review->{most_delay_to} %> verlief besonders gemächlich: + sie dauerte <strong><%= $review->{most_delay_delta} %></strong> länger als geplant. + </p> + % } + % if ($review->{most_undelay_id}) { + <p> + In <a href="/journey/<%= $review->{most_undelay_id} %>"><%= $review->{most_undelay_type} %> <%= $review->{most_undelay_lineno} %></a> + wurde hingegen Vmax ausgereizt und die Strecke von + <%= $review->{most_undelay_from} %> nach <%= $review->{most_undelay_to} %> + <strong><%= $review->{most_undelay_delta} %></strong> schneller absolviert als vorgesehen. + </p> + % } + </div> + <div class="carousel-item" href="#six"> + <h2>Last, but not least</h2> + % if ($review->{top_trip_count}) { + <p> + <strong><%= $review->{top_trip_percent_h} %></strong> deiner Check-Ins konzentrierten sich auf diese Strecken:<br/> + % for my $i (0 .. $#{$review->{top_trips}}) { + % my $trip = $review->{top_trips}[$i]; + <%= join(q{ }, @{$trip}) %><br/> + % } + </p> + % } + % if ($review->{single_trip_count}) { + <p> + <a href="/history/<%= $year %>?filter=single"><strong><%= $review->{single_trip_percent_h} %></strong> aller Verbindungen</a> bist du nur genau <strong>einmal</strong> gefahren. Zum Beispiel:<br/> + % for my $i (0 .. $#{$review->{single_trips}}) { + % my $trip = $review->{single_trips}[$i]; + <%= $trip->[0] %> → <%= $trip->[1] %><br/> + % } + </p> + % } + <p><em>Thank you for traveling with travelynx</em></p> + </div> + </div> + </div> +</div> |