add load time and autofocus
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Failing after 10m5s
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Failing after 10m5s
This commit is contained in:
parent
917428507d
commit
5539c8521d
10 changed files with 153 additions and 11 deletions
|
|
@ -29,6 +29,10 @@ body {
|
|||
max-width: 750px;
|
||||
}
|
||||
|
||||
.next-col {
|
||||
background-color: #55ff55 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #114488;
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ pub async fn get_schedule_by_route_id(
|
|||
AND
|
||||
septa_schedule_days.service_id = septa_stop_schedules.service_id
|
||||
WHERE
|
||||
septa_stop_schedules.route_id = $1
|
||||
septa_stop_schedules.route_id = $1 OR septa_stop_schedules.route_id = 'B2'
|
||||
;"#,
|
||||
id.clone(),
|
||||
schedule_day_str.clone()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use libseptastic::{direction::Direction, stop_schedule::{Trip, TripTracking}};
|
|||
use std::{cmp::Ordering, collections::BTreeMap};
|
||||
use serde::{Serialize};
|
||||
use libseptastic::stop_schedule::TripTracking::Tracked;
|
||||
use chrono::Timelike;
|
||||
|
||||
#[derive(askama::Template)]
|
||||
#[template(path = "layout.html")]
|
||||
|
|
@ -49,9 +50,11 @@ pub struct TimetableDirection {
|
|||
pub direction: Direction,
|
||||
pub trip_ids: Vec<String>,
|
||||
pub tracking_data: Vec<TripTracking>,
|
||||
pub rows: Vec<TimetableStopRow>
|
||||
pub rows: Vec<TimetableStopRow>,
|
||||
pub next_id: Option<String>
|
||||
}
|
||||
|
||||
|
||||
pub fn build_timetables(
|
||||
directions: Vec<Direction>,
|
||||
trips: Vec<Trip>,
|
||||
|
|
@ -59,6 +62,12 @@ pub fn build_timetables(
|
|||
let mut results = Vec::new();
|
||||
|
||||
for direction in directions {
|
||||
let now = chrono::Local::now();
|
||||
let naive_time = now.time();
|
||||
let seconds_since_midnight = naive_time.num_seconds_from_midnight();
|
||||
|
||||
let mut next_id: Option<String> = None;
|
||||
|
||||
let mut direction_trips: Vec<&Trip> = trips
|
||||
.iter()
|
||||
.filter(|trip| trip.direction_id == direction.direction_id)
|
||||
|
|
@ -72,6 +81,14 @@ pub fn build_timetables(
|
|||
.unwrap_or(i64::MAX)
|
||||
});
|
||||
|
||||
for trip in direction_trips.clone() {
|
||||
if let Some(last) = trip.schedule.last() {
|
||||
if next_id == None && i64::from(seconds_since_midnight) < last.arrival_time {
|
||||
next_id = Some(last.trip_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let trip_ids: Vec<String> = direction_trips
|
||||
.iter()
|
||||
.map(|t| t.trip_id.clone())
|
||||
|
|
@ -120,7 +137,8 @@ pub fn build_timetables(
|
|||
direction: direction.clone(),
|
||||
trip_ids,
|
||||
rows,
|
||||
tracking_data: live_trips
|
||||
tracking_data: live_trips ,
|
||||
next_id
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,17 @@
|
|||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
setTimeout(() => {
|
||||
const perfData = window.performance.timing;
|
||||
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
|
||||
|
||||
const loadTimeElement = document.getElementById('js_load_time');
|
||||
loadTimeElement.textContent += ` ${pageLoadTime}ms`;
|
||||
}, 0); // Minimal delay to wait for `loadEventEnd` to be populated
|
||||
};
|
||||
</script>
|
||||
<noscript>
|
||||
<style>
|
||||
.js-only {
|
||||
|
|
@ -67,6 +78,7 @@
|
|||
{% if let Some(load_time) = load_time_ms %}
|
||||
<p style="marin-top: 5px; color: #555555;"><small><i>Data loaded in {{ *load_time | format_load_time }}</i></small></p>
|
||||
{% endif %}
|
||||
<p class="js-only" style="marin-top: 5px; color: #555555;"><small><i id="js_load_time">Total load time</i></small></p>
|
||||
</div>
|
||||
<div>
|
||||
{% if widescreen %}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,22 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll("details[data-direction-id]").forEach(details => {
|
||||
details.addEventListener("toggle", () => {
|
||||
if (details.open) {
|
||||
// Delay scroll until DOM is expanded/rendered
|
||||
setTimeout(() => {
|
||||
const directionId = details.getAttribute("data-direction-id");
|
||||
const target = document.getElementById("next-col-" + directionId);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: "smooth", inline: "start", block: "nearest" });
|
||||
}
|
||||
}, 50); // Short delay to allow rendering
|
||||
}
|
||||
});
|
||||
});
|
||||
document.querySelectorAll(".train-direction-table").forEach((table) => {
|
||||
table.addEventListener("click", (e) => {
|
||||
const cell = e.target.closest("td, th");
|
||||
|
|
@ -74,7 +89,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
</div>
|
||||
|
||||
{% for timetable in timetables %}
|
||||
<details style="margin-top: 15px;">
|
||||
<details style="margin-top: 15px;" data-direction-id="{{ timetable.direction.direction_id }}">
|
||||
<summary>
|
||||
<div style="display: inline-block;">
|
||||
<h3>{{ timetable.direction.direction | capitalize }} to</h3>
|
||||
|
|
@ -87,7 +102,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
<tr>
|
||||
<th>Stop</th>
|
||||
{% for trip_id in timetable.trip_ids %}
|
||||
<th>{{ trip_id }}</th>
|
||||
{% if let Some(next_id_v) = timetable.next_id %}
|
||||
{% if next_id_v == trip_id %}
|
||||
<th class="next-col" id="next-col-{{ timetable.direction.direction_id }}">
|
||||
{% else %}
|
||||
<th>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<th>
|
||||
{% endif %}
|
||||
{{ trip_id }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ impl SeptaFetcher {
|
|||
let mut routes = Self::fetch_routes(&version).await?;
|
||||
info!("Discovered {} SEPTA routes", routes.len());
|
||||
|
||||
routes = routes.into_iter().filter(|x| x.release_name == "20250914").collect();
|
||||
routes = routes.into_iter().filter(|x| x.release_name == "20250928").collect();
|
||||
|
||||
let route_stops = Self::fetch_route_stops(&mut routes, &version).await?;
|
||||
info!("Stop data for {} stops on {} routes successfully downloaded", route_stops.len(), routes.len());
|
||||
|
|
@ -209,14 +209,14 @@ impl SeptaFetcher {
|
|||
let mut calendar = Self::fetch_calendar(&version).await?;
|
||||
info!("Calendar data successfully downloaded for {} days", calendar.len());
|
||||
for entry in calendar.clone() {
|
||||
if entry.1.release_name != "20250914" {
|
||||
if entry.1.release_name != "20250928" {
|
||||
calendar.remove(&entry.0.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut schedule_stops = Self::fetch_schedules(&route_stops, &version).await?;
|
||||
info!("Schedule data downloaded for {} scheduled stops", schedule_stops.len());
|
||||
schedule_stops = schedule_stops.into_iter().filter(|x| x.release_name == "20250914").collect();
|
||||
schedule_stops = schedule_stops.into_iter().filter(|x| x.release_name == "20250928").collect();
|
||||
|
||||
let mut stop_map: HashMap<String, i64> = HashMap::new();
|
||||
let mut stop_set: HashSet<i64> = HashSet::new();
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl DbObj<Direction> for Direction {
|
|||
impl FromSeptaJson<septa_json::direction::Direction> for Direction {
|
||||
fn from_septa_json(json_dir: septa_json::direction::Direction) -> ::anyhow::Result<Box<Direction>> {
|
||||
Ok(Box::new(Direction { route_id: json_dir.route, direction_id: json_dir.direction.parse::<i64>()?,
|
||||
direction: match json_dir.true_direction.as_str() {
|
||||
direction: match json_dir.true_direction.as_str().clone() {
|
||||
"Eastbound" => Ok(libseptastic::direction::CardinalDirection::Eastbound),
|
||||
"Westbound" => Ok(libseptastic::direction::CardinalDirection::Westbound),
|
||||
"Outbound" => Ok(libseptastic::direction::CardinalDirection::Outbound),
|
||||
|
|
@ -77,7 +77,8 @@ impl FromSeptaJson<septa_json::direction::Direction> for Direction {
|
|||
"Southbound" => Ok(libseptastic::direction::CardinalDirection::Southbound),
|
||||
"Northbound" => Ok(libseptastic::direction::CardinalDirection::Northbound),
|
||||
"LOOP" => Ok(libseptastic::direction::CardinalDirection::Loop),
|
||||
_ => Err(anyhow::anyhow!("Unable to find right direction"))
|
||||
"Loop" => Ok(libseptastic::direction::CardinalDirection::Loop),
|
||||
_ => Err(anyhow::anyhow!(format!("Unable to find right direction {}", json_dir.true_direction)))
|
||||
}?
|
||||
, direction_destination: json_dir.direction_destination }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use sqlx::{Postgres, Transaction};
|
||||
use crate::traits::{DbObj, FromSeptaJson};
|
||||
use crate::septa_json;
|
||||
use libseptastic::route::{Route, RouteType};
|
||||
use libseptastic::route::{Route, RouteType, InterlinedRoute};
|
||||
|
||||
impl DbObj<Route> for Route {
|
||||
|
||||
|
|
@ -44,6 +44,26 @@ impl DbObj<Route> for Route {
|
|||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query("
|
||||
CREATE TABLE IF NOT EXISTS interlined_routes (
|
||||
id VARCHAR(8) PRIMARY KEY,
|
||||
name VARCHAR(64) NOT NULL
|
||||
);
|
||||
")
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query("
|
||||
CREATE TABLE IF NOT EXISTS interlined_routes_subroutes (
|
||||
interline_id VARCHAR(8),
|
||||
route_id VARCHAR(8),
|
||||
FOREIGN KEY (interline_id) REFERENCES interlined_routes(id),
|
||||
FOREIGN KEY (route_id) REFERENCES septa_routes(id)
|
||||
);
|
||||
")
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -88,10 +108,55 @@ async fn insert_many(routes: Vec<Route>, tx: &mut Transaction<'_, Postgres>) ->
|
|||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
let ir: InterlinedRoute = InterlinedRoute {
|
||||
interline_id: String::from("B"),
|
||||
interline_name: String::from("Broad Street Line"),
|
||||
interlined_routes: vec![String::from("B1"), String::from("B2"), String::from("B3")]
|
||||
};
|
||||
|
||||
sqlx::query("
|
||||
INSERT INTO
|
||||
interlined_routes
|
||||
(
|
||||
id,
|
||||
name
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
$1,
|
||||
$2
|
||||
);
|
||||
")
|
||||
.bind(&ir.interline_id)
|
||||
.bind(&ir.interline_name)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
for route in ir.interlined_routes {
|
||||
sqlx::query("
|
||||
INSERT INTO
|
||||
interlined_routes_subroutes
|
||||
(
|
||||
interline_id,
|
||||
route_id
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
$1,
|
||||
$2
|
||||
);
|
||||
")
|
||||
.bind(&ir.interline_id)
|
||||
.bind(&route)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn insert(&self, tx: &mut Transaction<'_, Postgres>) -> anyhow::Result<()> {
|
||||
Self::insert_many(vec![self.clone()], tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@ impl DbObj<StopSchedule> for StopSchedule {
|
|||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query("
|
||||
CREATE INDEX septa_stop_schedule_trip_id_idx ON septa_stop_schedules (trip_id);
|
||||
").execute(&mut **tx).await?;
|
||||
|
||||
sqlx::query("
|
||||
CREATE INDEX septa_stop_schedule_service_id_idx ON septa_stop_schedules (service_id);
|
||||
").execute(&mut **tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn insert_many(scheds: Vec<StopSchedule>, tx: &mut Transaction<'_, Postgres>) -> anyhow::Result<()> {
|
||||
|
|
@ -107,6 +115,7 @@ impl FromSeptaJson<septa_json::stop_schedule::StopSchedule> for StopSchedule {
|
|||
septa_json::stop_schedule::TripId::RegionalRail(x) => x,
|
||||
septa_json::stop_schedule::TripId::Other(y) => y.to_string()
|
||||
},
|
||||
stop_name: String::from(""),
|
||||
service_id: json_sched.service_id,
|
||||
// FIXME: Actually get direction
|
||||
direction_id: json_sched.direction_id,
|
||||
|
|
|
|||
|
|
@ -18,3 +18,11 @@ pub struct Route {
|
|||
pub route_type: RouteType,
|
||||
pub id: String
|
||||
}
|
||||
|
||||
|
||||
#[derive(::sqlx::FromRow, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InterlinedRoute {
|
||||
pub interline_id: String,
|
||||
pub interline_name: String,
|
||||
pub interlined_routes: Vec<String>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue