add live updating table
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 9m20s
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 9m20s
This commit is contained in:
parent
2ca76548d6
commit
6773e6ae30
7 changed files with 210 additions and 124 deletions
|
|
@ -1,6 +1,7 @@
|
|||
use actix_web::{get, web::{self, Data}, HttpRequest, Responder};
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, get, web::{self, Data}};
|
||||
use anyhow::anyhow;
|
||||
use chrono::Timelike;
|
||||
use askama::Template;
|
||||
use chrono::{NaiveTime, Timelike};
|
||||
use chrono_tz::America::New_York;
|
||||
use libseptastic::stop_schedule::{self, LiveTrip, Trip, TripTracking};
|
||||
use log::info;
|
||||
|
|
@ -34,18 +35,10 @@ async fn get_stops_html(req: HttpRequest, state: Data<Arc<AppState>>) -> impl Re
|
|||
}
|
||||
}).await
|
||||
}
|
||||
#[get("/stop/{stop_id}")]
|
||||
async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::Path<String>) -> impl Responder {
|
||||
crate::perform_action(req, move || {
|
||||
let statex = state.clone();
|
||||
let pathx = path.clone();
|
||||
async move {
|
||||
let stop_id = pathx;
|
||||
let start_time = Instant::now();
|
||||
|
||||
if let Some(stop) = statex.gtfs_service.get_stop_by_id(&stop_id) {
|
||||
let routes: Vec<libseptastic::route::Route> = statex.gtfs_service.get_routes_at_stop(&stop.id).iter().filter_map(|route| {
|
||||
match statex.gtfs_service.get_route(route.clone()) {
|
||||
async fn get_trip_perspective_for_stop(state: &Data<Arc<AppState>>,stop: &libseptastic::stop::Stop) -> Vec<TripPerspective> {
|
||||
let routes: Vec<libseptastic::route::Route> = state.gtfs_service.get_routes_at_stop(&stop.id).iter().filter_map(|route| {
|
||||
match state.gtfs_service.get_route(route.clone()) {
|
||||
Ok(route) => Some(route),
|
||||
Err(_) => None
|
||||
}
|
||||
|
|
@ -53,7 +46,7 @@ async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::
|
|||
|
||||
let route_ids: HashSet<String> = routes.iter().map(|route| route.id.clone()).collect();
|
||||
|
||||
let mut trips = statex.gtfs_service.get_all_trips().iter().filter_map(|trip| {
|
||||
let mut trips = state.gtfs_service.get_all_trips().iter().filter_map(|trip| {
|
||||
if route_ids.contains(trip.0) {
|
||||
Some(trip.1.clone())
|
||||
} else {
|
||||
|
|
@ -62,7 +55,7 @@ async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::
|
|||
}).flatten().collect();
|
||||
|
||||
|
||||
statex.trip_tracking_service.annotate_trips(&mut trips).await;
|
||||
state.trip_tracking_service.annotate_trips(&mut trips).await;
|
||||
|
||||
let now_utc = chrono::Utc::now();
|
||||
let now = now_utc.with_timezone(&New_York);
|
||||
|
|
@ -73,7 +66,7 @@ async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::
|
|||
//if !trip.is_active_on(&now.naive_local()) {
|
||||
// return None;
|
||||
//}
|
||||
//
|
||||
|
||||
// poor midnight handling?
|
||||
if !trip.calendar_day.is_calendar_active_for_date(&now.naive_local().date()) {
|
||||
return None
|
||||
|
|
@ -81,7 +74,7 @@ async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::
|
|||
let mut est_arrival_time = 0;
|
||||
let mut is_tracked = false;
|
||||
let stop_sched : Vec<_> = trip.schedule.iter().filter(|stop_schedule| {
|
||||
if stop_schedule.stop.id != stop_id {
|
||||
if stop_schedule.stop.id != stop.id {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +121,57 @@ async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::
|
|||
_ => f.perspective_stop.arrival_time
|
||||
});
|
||||
|
||||
filtered_trips
|
||||
}
|
||||
|
||||
#[get("/stop/{stop_id}/table")]
|
||||
async fn get_stop_table_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::Path<String>) -> impl Responder {
|
||||
let stop_id = path;
|
||||
|
||||
if let Some(stop) = state.gtfs_service.get_stop_by_id(&stop_id) {
|
||||
let filtered_trips = get_trip_perspective_for_stop(&state, &stop).await;
|
||||
|
||||
let now_utc = chrono::Utc::now();
|
||||
let now = now_utc.with_timezone(&New_York);
|
||||
let naive_time = now.time();
|
||||
let cur_time = i64::from(naive_time.num_seconds_from_midnight());
|
||||
|
||||
|
||||
HttpResponse::Ok().body(crate::templates::StopTableTemplate {
|
||||
trips: filtered_trips,
|
||||
current_time: cur_time
|
||||
}.render().unwrap())
|
||||
}
|
||||
else {
|
||||
HttpResponse::InternalServerError().body("Error")
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/stop/{stop_id}")]
|
||||
async fn get_stop_html(req: HttpRequest, state: Data<Arc<AppState>>, path: web::Path<String>) -> impl Responder {
|
||||
crate::perform_action(req, move || {
|
||||
let statex = state.clone();
|
||||
let pathx = path.clone();
|
||||
async move {
|
||||
let stop_id = pathx;
|
||||
let start_time = Instant::now();
|
||||
|
||||
if let Some(stop) = statex.gtfs_service.get_stop_by_id(&stop_id) {
|
||||
|
||||
let routes: Vec<libseptastic::route::Route> = statex.gtfs_service.get_routes_at_stop(&stop.id).iter().filter_map(|route| {
|
||||
match statex.gtfs_service.get_route(route.clone()) {
|
||||
Ok(route) => Some(route),
|
||||
Err(_) => None
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let filtered_trips = get_trip_perspective_for_stop(&statex, &stop).await;
|
||||
|
||||
let now_utc = chrono::Utc::now();
|
||||
let now = now_utc.with_timezone(&New_York);
|
||||
let naive_time = now.time();
|
||||
let cur_time = i64::from(naive_time.num_seconds_from_midnight());
|
||||
|
||||
Ok(crate::templates::ContentTemplate {
|
||||
page_title: Some(stop.name.clone()),
|
||||
page_desc: Some(String::from("Stop information")),
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ async fn main() -> ::anyhow::Result<()> {
|
|||
// .service(controllers::route::get_directions)
|
||||
.service(controllers::stop::get_stops_html)
|
||||
.service(controllers::stop::get_stop_html)
|
||||
.service(controllers::stop::get_stop_table_html)
|
||||
.service(get_index)
|
||||
.service(actix_files::Files::new("/assets", "./assets"))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -77,6 +77,13 @@ pub struct StopTemplate {
|
|||
pub current_time: i64
|
||||
}
|
||||
|
||||
#[derive(askama::Template)]
|
||||
#[template(path = "stop_table_impl.html")]
|
||||
pub struct StopTableTemplate {
|
||||
pub trips: Vec<TripPerspective>,
|
||||
pub current_time: i64
|
||||
}
|
||||
|
||||
pub fn build_timetables(
|
||||
directions: Vec<Direction>,
|
||||
trips: Vec<Trip>,
|
||||
|
|
@ -183,6 +190,7 @@ mod filters {
|
|||
return Ok(format!("{}ns", nanos));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_time(
|
||||
seconds_since_midnight: &i64,
|
||||
_: &dyn askama::Values,
|
||||
|
|
@ -190,8 +198,10 @@ mod filters {
|
|||
let total_minutes = seconds_since_midnight / 60;
|
||||
let (hours, ampm) = {
|
||||
let hrs = total_minutes / 60;
|
||||
if hrs >= 12 {
|
||||
if hrs > 12 {
|
||||
(hrs - 12, "PM")
|
||||
} else if hrs == 12 {
|
||||
(12, "PM")
|
||||
} else if hrs > 0 {
|
||||
(hrs, "AM")
|
||||
} else {
|
||||
|
|
@ -201,4 +211,26 @@ mod filters {
|
|||
let minutes = total_minutes % 60;
|
||||
Ok(format!("{}:{:02} {}", hours, minutes, ampm))
|
||||
}
|
||||
|
||||
pub fn format_time_with_seconds(
|
||||
seconds_since_midnight: &i64,
|
||||
_: &dyn askama::Values,
|
||||
) -> askama::Result<String> {
|
||||
let total_minutes = seconds_since_midnight / 60;
|
||||
let (hours, ampm) = {
|
||||
let hrs = total_minutes / 60;
|
||||
if hrs > 12 {
|
||||
(hrs - 12, "PM")
|
||||
} else if hrs == 12 {
|
||||
(12, "PM")
|
||||
} else if hrs > 0 {
|
||||
(hrs, "AM")
|
||||
} else {
|
||||
(12, "AM")
|
||||
}
|
||||
};
|
||||
let minutes = total_minutes % 60;
|
||||
let seconds = seconds_since_midnight % 60;
|
||||
Ok(format!("{}:{:02}:{:02} {}", hours, minutes, seconds, ampm))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
{%- import "route_symbol.html" as scope -%}
|
||||
|
||||
<style>
|
||||
.trip-desc {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
</style>
|
||||
{%- import "stop_table.html" as stop_table -%}
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<h1>{{ stop.name }}</h1>
|
||||
|
|
@ -20,7 +14,6 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{#{% if let libseptastic::stop::StopType::MultiPlatform(platforms) = stop.platforms %}
|
||||
<div>
|
||||
<p>Platforms at this station:</p>
|
||||
|
|
@ -30,45 +23,6 @@
|
|||
</div>
|
||||
{% endif %}#}
|
||||
|
||||
<table class="train-direction-table">
|
||||
<tr>
|
||||
<th>ROUTE</th>
|
||||
<th>DESTINATION</th>
|
||||
<th>BOARDING AREA</th>
|
||||
<th>TIME</th>
|
||||
<th>VEHICLE</th>
|
||||
</tr>
|
||||
{% for trip in trips %}
|
||||
<tr>
|
||||
<td>
|
||||
{% call scope::route_symbol(trip.trip.route) %}
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ trip.trip.direction.direction_destination }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ trip.perspective_stop.platform.name }}</p>
|
||||
</td>
|
||||
{% if let Tracked(tracked_trip) = trip.trip.tracking_data %}
|
||||
<td style="color: #008800">
|
||||
<p style="font-size: small;">{{ &trip.perspective_stop.get_arrival_time(&tracked_trip) | format_time }}</p>
|
||||
<p style="font-size: x-small; font-style: italic;">{{ ( trip.perspective_stop.get_arrival_time(&tracked_trip) - current_time) / 60 }} mins</p>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<p style="font-size: small;">{{ trip.perspective_stop.arrival_time | format_time }}</p>
|
||||
<p style="font-size: x-small; font-style: italic;">{{ (trip.perspective_stop.arrival_time - current_time) / 60 }} mins</p>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if let Tracked(tracked_trip) = trip.trip.tracking_data %}
|
||||
<td>
|
||||
{{ tracked_trip.vehicle_ids.join(", ") }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
-
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div hx-get="/stop/{{ stop.id }}/table" hx-trigger="every 5s">
|
||||
{% call stop_table::stop_table(trips, current_time) %}
|
||||
</div>
|
||||
|
|
|
|||
51
api/templates/stop_table.html
Normal file
51
api/templates/stop_table.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{%- import "route_symbol.html" as scope -%}
|
||||
|
||||
{% macro stop_table(trips, current_time) %}
|
||||
<table class="train-direction-table">
|
||||
<tr>
|
||||
<th>ROUTE</th>
|
||||
<th>DESTINATION</th>
|
||||
<th>BOARDING AREA</th>
|
||||
<th>TIME</th>
|
||||
<th>VEHICLE</th>
|
||||
</tr>
|
||||
{% for trip in trips %}
|
||||
<tr>
|
||||
<td>
|
||||
{% call scope::route_symbol(trip.trip.route) %}
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ trip.trip.direction.direction_destination }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ trip.perspective_stop.platform.name }}</p>
|
||||
</td>
|
||||
{% if let Tracked(tracked_trip) = trip.trip.tracking_data %}
|
||||
<td style="color: #008800">
|
||||
<p style="font-size: small;">{{ &trip.perspective_stop.get_arrival_time(&tracked_trip) | format_time }}</p>
|
||||
<p style="font-size: x-small; font-style: italic;">{{ ( trip.perspective_stop.get_arrival_time(&tracked_trip) - current_time) / 60 }} mins</p>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<p style="font-size: small;">{{ trip.perspective_stop.arrival_time | format_time }}</p>
|
||||
<p style="font-size: x-small; font-style: italic;">{{ (trip.perspective_stop.arrival_time - current_time) / 60 }} mins</p>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if let Tracked(tracked_trip) = trip.trip.tracking_data %}
|
||||
<td>
|
||||
{{ tracked_trip.vehicle_ids.join(", ") }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
-
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<p>Updated at: {{ current_time | format_time_with_seconds }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
3
api/templates/stop_table_impl.html
Normal file
3
api/templates/stop_table_impl.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{%- import "stop_table.html" as stop_table -%}
|
||||
|
||||
{% call stop_table::stop_table(trips, current_time) %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue