summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xlib/Travelynx.pm112
-rw-r--r--lib/Travelynx/Command/database.pm11
-rw-r--r--lib/Travelynx/Controller/Account.pm32
-rw-r--r--templates/_checked_in.html.ep4
-rw-r--r--templates/_checked_out.html.ep2
-rw-r--r--templates/_connections.html.ep8
-rw-r--r--templates/account.html.ep16
-rw-r--r--templates/changelog.html.ep17
-rw-r--r--templates/departures.html.ep61
-rw-r--r--templates/use_history.html.ep62
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