diff options
-rwxr-xr-x | lib/Travelynx.pm | 112 | ||||
-rw-r--r-- | lib/Travelynx/Command/database.pm | 11 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 32 | ||||
-rw-r--r-- | templates/_checked_in.html.ep | 4 | ||||
-rw-r--r-- | templates/_checked_out.html.ep | 2 | ||||
-rw-r--r-- | templates/_connections.html.ep | 8 | ||||
-rw-r--r-- | templates/account.html.ep | 16 | ||||
-rw-r--r-- | templates/changelog.html.ep | 17 | ||||
-rw-r--r-- | templates/departures.html.ep | 61 | ||||
-rw-r--r-- | templates/use_history.html.ep | 62 |
10 files changed, 278 insertions, 47 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index d313b25..3e65963 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -317,7 +317,7 @@ sub startup { 'checkin' => sub { my ( $self, $station, $train_id ) = @_; - my $status = $self->get_departures( $station, 140, 30 ); + my $status = $self->get_departures( $station, 140, 40 ); if ( $status->{errstr} ) { return ( undef, $status->{errstr} ); } @@ -753,6 +753,10 @@ sub startup { return $res_h->{id}; } + if ( $opt{readonly} ) { + return; + } + $self->pg->db->insert( 'stations', { @@ -1495,14 +1499,11 @@ sub startup { ); $self->helper( - 'get_connection_targets' => sub { + 'get_latest_dest_id' => sub { my ( $self, %opt ) = @_; - my $uid = $opt{uid} // $self->current_user->{id}; - my $threshold = $opt{threshold} - // DateTime->now( time_zone => 'Europe/Berlin' ) - ->subtract( weeks => 6 ); - my $db = $opt{db} // $self->pg->db; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; my $journey = $db->select( 'in_transit', ['checkout_station_id'], { user_id => $uid } )->hash; @@ -1525,6 +1526,37 @@ sub startup { return; } + return $journey->{checkout_station_id}; + } + ); + + $self->helper( + 'get_connection_targets' => sub { + my ( $self, %opt ) = @_; + + my $uid = $opt{uid} //= $self->current_user->{id}; + my $threshold = $opt{threshold} + // DateTime->now( time_zone => 'Europe/Berlin' ) + ->subtract( weeks => 6 ); + my $db = $opt{db} //= $self->pg->db; + my $min_count = $opt{min_count} // 3; + + my $dest_id; + + if ( $opt{ds100} ) { + $dest_id = $self->get_station_id( + ds100 => $opt{ds100}, + readonly => 1 + ); + } + else { + $dest_id = $self->get_latest_dest_id(%opt); + } + + if ( not $dest_id ) { + return; + } + my $res = $db->query( qq{ select @@ -1539,11 +1571,12 @@ sub startup { order by count desc; }, $uid, - $journey->{checkout_station_id}, + $dest_id, $threshold ); - my @destinations = $res->hashes->grep( sub { shift->{count} > 2 } ) - ->map( sub { shift->{dest} } )->each; + my @destinations + = $res->hashes->grep( sub { shift->{count} >= $min_count } ) + ->map( sub { shift->{dest} } )->each; return @destinations; } ); @@ -1552,21 +1585,41 @@ sub startup { 'get_connecting_trains' => sub { my ( $self, %opt ) = @_; - my $status = $self->get_user_status; + my $uid = $opt{uid} //= $self->current_user->{id}; + my $use_history = $self->account_use_history($uid); + + my ( $ds100, $exclude_via, $exclude_train_id, $exclude_before ); + + if ( $opt{ds100} ) { + if ( $use_history & 0x01 ) { + $ds100 = $opt{ds100}; + } + } + else { + if ( $use_history & 0x02 ) { + my $status = $self->get_user_status; + $ds100 = $status->{arr_ds100}; + $exclude_via = $status->{dep_name}; + $exclude_train_id = $status->{train_id}; + $exclude_before = $status->{real_arrival}->epoch; + } + } - if ( not $status->{arr_ds100} ) { + if ( not $ds100 ) { return; } my @destinations = $self->get_connection_targets(%opt); - @destinations = grep { $_ ne $status->{dep_name} } @destinations; + + if ($exclude_via) { + @destinations = grep { $_ ne $exclude_via } @destinations; + } if ( not @destinations ) { return; } - my $stationboard - = $self->get_departures( $status->{arr_ds100}, 0, 60 ); + my $stationboard = $self->get_departures( $ds100, 0, 40 ); if ( $stationboard->{errstr} ) { return; } @@ -1576,16 +1629,19 @@ sub startup { if ( not $train->departure ) { next; } - if ( $train->departure->epoch < $status->{real_arrival}->epoch ) + if ( $exclude_before + and $train->departure->epoch < $exclude_before ) { next; } - if ( $train->train_id eq $status->{train_id} ) { + if ( $exclude_train_id + and $train->train_id eq $exclude_train_id ) + { next; } my @via = ( $train->route_post, $train->route_end ); for my $dest (@destinations) { - if ( $via_count{$dest} < 3 + if ( $via_count{$dest} < 2 and List::Util::any { $_ eq $dest } @via ) { push( @results, [ $train, $dest ] ); @@ -1609,6 +1665,24 @@ sub startup { ); $self->helper( + 'account_use_history' => sub { + my ( $self, $uid, $value ) = @_; + + if ($value) { + $self->pg->db->update( + 'users', + { use_history => $value }, + { id => $uid } + ); + } + else { + return $self->pg->db->select( 'users', ['use_history'], + { id => $uid } )->hash->{use_history}; + } + } + ); + + $self->helper( 'get_user_travels' => sub { my ( $self, %opt ) = @_; @@ -2113,6 +2187,7 @@ sub startup { $authed_r->get('/account')->to('account#account'); $authed_r->get('/account/privacy')->to('account#privacy'); $authed_r->get('/account/hooks')->to('account#webhook'); + $authed_r->get('/account/insight')->to('account#insight'); $authed_r->get('/ajax/status_card.html')->to('traveling#status_card'); $authed_r->get('/cancelled')->to('traveling#cancelled'); $authed_r->get('/account/password')->to('account#password_form'); @@ -2128,6 +2203,7 @@ sub startup { $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->post('/account/privacy')->to('account#privacy'); $authed_r->post('/account/hooks')->to('account#webhook'); + $authed_r->post('/account/insight')->to('account#insight'); $authed_r->post('/journey/add')->to('traveling#add_journey_form'); $authed_r->post('/journey/edit')->to('traveling#edit_journey'); $authed_r->post('/account/password')->to('account#change_password'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index a15390d..99fcc61 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -535,6 +535,17 @@ my @migrations = ( } ); }, + + # v12 -> v13 + sub { + my ($db) = @_; + $db->query( + qq{ + alter table users add column use_history smallint default 255; + update schema_version set version = 13; + } + ); + }, ); sub setup_db { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index e2bfd39..0275b96 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -232,6 +232,38 @@ sub privacy { } } +sub insight { + my ($self) = @_; + + my $user = $self->current_user; + my $use_history = $self->account_use_history( $user->{id} ); + + if ( $self->param('action') and $self->param('action') eq 'save' ) { + if ( $self->param('on_departure') ) { + $use_history |= 0x01; + } + else { + $use_history &= ~0x01; + } + + if ( $self->param('on_arrival') ) { + $use_history |= 0x02; + } + else { + $use_history &= ~0x02; + } + + $self->account_use_history( $user->{id}, $use_history ); + $self->flash( success => 'use_history' ); + $self->redirect_to('account'); + } + + $self->param( on_departure => $use_history & 0x01 ? 1 : 0 ); + $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 ); + $self->render('use_history'); + +} + sub webhook { my ($self) = @_; diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index d5b14e5..ebe84e8 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -79,6 +79,10 @@ % } % 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; % } % } diff --git a/templates/_checked_out.html.ep b/templates/_checked_out.html.ep index 18b613b..ca9373d 100644 --- a/templates/_checked_out.html.ep +++ b/templates/_checked_out.html.ep @@ -5,6 +5,8 @@ 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}; % } % } diff --git a/templates/_connections.html.ep b/templates/_connections.html.ep index 1c7f003..d421d4e 100644 --- a/templates/_connections.html.ep +++ b/templates/_connections.html.ep @@ -1,8 +1,4 @@ -<span class="card-title" style="margin-top: 2ex;">Verbindungen</span> -% if ($checkin_from) { - <p>Zug auswählen zum Einchecken mit Zielwahl.</p> -% } -<div class="hide-on-med-and-up"><table><tbody> +<div class="hide-on-med-and-up"><table class="striped"><tbody> % for my $res (@{$connections}) { % my ($train, $via) = @{$res}; <tr> @@ -30,7 +26,7 @@ </tr> % } </tbody></table></div> -<div class="hide-on-small-only"><table><tbody> +<div class="hide-on-small-only"><table class="striped"><tbody> % for my $res (@{$connections}) { % my ($train, $via) = @{$res}; <tr> diff --git a/templates/account.html.ep b/templates/account.html.ep index 2c58a7b..627be85 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -16,6 +16,9 @@ % elsif ($success eq 'privacy') { <span class="card-title">Einstellungen zu öffentliche Account-Daten geändert</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> % } @@ -28,6 +31,7 @@ <h1>Account</h1> % my $acc = current_user(); % my $hook = get_webhook(); +% my $use_history = account_use_history($acc->{id}); <div class="row"> <div class="col s12"> <table class="striped"> @@ -44,6 +48,18 @@ <td><a href="/account/password"><i class="material-icons">edit</i></a></td> </tr> <tr> + <th scope="row">Verbindungen</th> + <td> + <a href="/account/insight"><i class="material-icons">edit</i></a> + % if ($use_history & 0x03) { + Vorschläge aktiv + % } + % else { + <span style="color: #999999;">Vorschläge deaktiviert</span> + % } + </td> + </tr> + <tr> <th scope="row">Öffentliche Daten</th> <td> <a href="/account/privacy"><i class="material-icons">edit</i></a> diff --git a/templates/changelog.html.ep b/templates/changelog.html.ep index fec21a3..9c3d13f 100644 --- a/templates/changelog.html.ep +++ b/templates/changelog.html.ep @@ -2,6 +2,23 @@ <div class="row"> <div class="col s12 m1 l1"> + 1.6 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left">add</i> Anzeige von häufig genutzten + Verbindungen in der Abfahrtstafel. Wie bei den Anschlusszügen kann + darüber direkt (inkl. Vorauswahl des Ziels) eingecheckt werden. + </p> + <p> + <i class="material-icons left">add</i> Konfigurationsseite, um die + Heuristik für Anschlusszüge und häufige Verbindungen zu deaktivieren. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> 1.5 </div> <div class="col s12 m11 l11"> diff --git a/templates/departures.html.ep b/templates/departures.html.ep index 3c9638e..7e98d9c 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -1,7 +1,13 @@ <div class="row"> - <div class="col s12"> - % my $status = $self->get_user_status; - % if ($status->{checked_in}) { + <div class="col s12 center-align"><b> + %= $station + </b></div> +</div> +% my $status = $self->get_user_status; +% my $have_connections = 0; +% if ($status->{checked_in}) { + <div class="row"> + <div class="col s12"> <div class="card"> <div class="card-content"> <span class="card-title">Aktuell eingecheckt</span> @@ -14,31 +20,40 @@ </a> </div> </div> - % } - % elsif ($status->{timestamp_delta} < 180) { + </div> + </div> +% } +% elsif ($status->{timestamp_delta} < 180) { + <div class="row"> + <div class="col s12"> %= include '_checked_out', journey => $status; - % } + </div> </div> -</div> +% } +% elsif (not param('train') and my @connections = get_connecting_trains(ds100 => $ds100)) { + % $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; + </div> + </div> +% } <div class="row"> <div class="col s12"> - %= $station - % if (@{$results}) { - – Zug auswählen zum Einchecken. - % } - % else { - – Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor - und maximal 120 Minuten nach Abfahrt möglich. - % } - <br/> + <p> + % if ($have_connections) { + Alle Abfahrten – + % } + % if (@{$results}) { + Zug auswählen zum Einchecken. + % } + % else { + Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor + und maximal 120 Minuten nach Abfahrt möglich. + % } + </p> <table class="striped"> - <thead> - <tr> - <th>Zug</th> - <th></th> - <th>Abfahrt</th> - </tr> - </thead> <tbody> % for my $result (@{$results}) { % my $td_class = ''; diff --git a/templates/use_history.html.ep b/templates/use_history.html.ep new file mode 100644 index 0000000..e8e129f --- /dev/null +++ b/templates/use_history.html.ep @@ -0,0 +1,62 @@ +<h1>Bevorzugte Verbindungen</h1> +<div class="row"> + <div class="col s12"> + <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, + sondern auch direkt Essen Hbf als Ziel eingetragen. + <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, + kannst du dieses Feature hier + ausschalten. + </p> --> + </div> +</div> +<h2>Vorschläge aktiv für:</h2> +%= form_for '/account/insight' => (method => 'POST') => begin + %= csrf_field + <div class="row"> + <div class="input-field col s12"> + <label> + %= check_box on_departure => 1 + <span>Abfahrtstafel</span> + </label> + </div> + </div> + <div class="row"> + <div class="col s12"> + Zeige häufige Fahrten im Abfahrtsmonitor. + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <label> + %= check_box on_arrival => 1 + <span>Reisestatus</span> + </label> + </div> + </div> + <div class="row"> + <div class="col s12"> + Zeige Anschlussmöglichkeiten kurz vor Ankunft am Ziel der aktuellen + Reise. Sobald es erreicht wurde, ist über diese Liste auch ein Checkin + ohne Umweg über die Abfahrtstafel möglich. + </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 |