Skip to content
Snippets Groups Projects
Commit 940b4293 authored by Niklas Schrötler's avatar Niklas Schrötler
Browse files

Tailwind: Scaled type for target screen

parent 41350d29
No related branches found
No related tags found
No related merge requests found
import React from 'react';
import React, {useEffect, useState} from 'react';
import PanelWrapper from "../../meta/PanelWrapper";
import PlanElement from "./components/PlanElement";
import PanelTitle from "../../meta/PanelTitle";
import PanelContent from "../../meta/PanelContent";
import {StationResponse} from "./types/vrrfAPI";
import {Warning} from "phosphor-react";
export type FahrplanPanelDefinition = {
stops: string[],
filter: {
types?: string[],
destinations?: string[]
}
}
type Route = {
uid: string,
identifier: string,
heading: string,
stops: {
name: string,
arrival: Date,
delay?: number
}[]
}
const FahrplanPanel = (props: {definition: FahrplanPanelDefinition}) => {
const [routes, setRoutes] = useState<Route[]>([]);
useEffect(() => {
const update = async () => {
const departures = (await Promise.all(props.definition.stops.map(getStopData)))
.map(d => d.raw)
.flat();
let newRoutes: Route[] = [];
// Determine stop data from the departures
for(let departure of departures) {
// First throw away all data that belongs to a filtered category
if((props.definition.filter.types ?? []).includes(departure.type)) {
continue;
}
// Find existing route with same uid
const existing_ind = newRoutes.findIndex(r => r.uid === departure.lineref.identifier)
// Pre-compute values that will be needed regardless
const delay = stringToDelay(departure.delay);
const arrival = processArrival(departure.sched_date, departure.time);
console.log(arrival)
if(existing_ind === -1) {
// If it does not exist, create a new route
newRoutes.push({
uid: departure.key,
heading: departure.lineref.direction,
identifier: departure.line,
stops: [
{
name: departure.internal.stop,
arrival,
delay
}
]
})
} else {
// If it doesn't, just add a stop to the existing route
newRoutes[existing_ind].stops.push({
name: departure.internal.stop,
arrival,
delay: stringToDelay(departure.delay)
})
}
}
// Sort the output
// Write to the display
setRoutes(newRoutes);
}
update();
const interval = setInterval(update, 2 * 60 * 1000);
return () => {
clearInterval(interval);
}
}, []);
const FahrplanPanel = () => {
return (
<PanelWrapper>
<PanelTitle title={"ÖPNV Monitor"}/>
<PanelContent>
<div className={"flex flex-col gap-3"}>
<PlanElement trainIdentifier={"S1"} trainHeading={"Dortmund Hbf"} stops={[
{
name: "Essen",
time: "10:00",
delay: 20
},
{
name: "Nicht Essen",
time: "10:00",
delay: 20
}
]}/>
<PlanElement trainIdentifier={"S1"} trainHeading={"Dortmund Hbf"} stops={[
{
name: "Essen",
time: "10:00",
delay: 20
},
{
name: "Nicht Essen",
time: "10:00",
delay: 20
}
]}/>
<PanelContent className={"flex flex-col"}>
<div className={"flex-1 flex flex-col gap-3"}>
{routes.map((route) => (
<PlanElement
key={route.uid}
tripId={route.uid}
trainIdentifier={route.identifier}
trainHeading={route.heading}
stops={route.stops}
/>
))}
{routes.length === 0 && (
<div className={"flex-1 flex justify-center items-center"}>
<div className={"mb-10 flex flex-col items-center"}>
<Warning size={48} className={"mb-3"}/>
<p className={"max-w-xs text-center text-zinc-400"}>
Aktuell sind keine Abfahrtsdaten verfügbar.
</p>
</div>
</div>
)}
</div>
</PanelContent>
</PanelWrapper>
......@@ -44,3 +123,68 @@ const FahrplanPanel = () => {
};
export default FahrplanPanel;
async function getStopData(stop: string): Promise<StationResponse> {
const request = await fetch(`https://vrrf.finalrewind.org/${encodeURIComponent(stop)}.json`);
if(!request.ok) {
throw new Error("Converting stop did not work");
}
const data = await request.json();
if(data.error) {
console.warn("Stop data for", stop, "could not be fetched");
}
// Add internal reference data
data.raw = data.raw.map((r: any) => ({
...r,
internal: {
stop
}
}))
console.log(data);
return data as StationResponse;
}
function stringToDelay(input: string): number | undefined {
try {
const delay = parseInt(input);
if(delay === 0) {
return undefined;
}
return delay;
} catch (e) {
console.warn("While parsing delay, the string was not interpretable", input);
return undefined;
}
}
function processArrival(date: string, time: string): Date {
const d_parts = date.split(".");
console.log(date, time, "to", `${d_parts[2]}-${d_parts[1]}-${d_parts[0]} ${time}`);
return new Date(`${d_parts[2]}-${d_parts[1]}-${d_parts[0]} ${time}`);
}
// function sortData(data: any) {
// for (var i = 0; i < data.length; ++i) {
// data[i]['stops'] = data[i]['stops'].sort(sortFn);
// data[i]['timeValue'] = data[i]['stops'][0]['timeValue'];
// }
// return data.sort(sortFn);
// }
//
// function sortFn(a: any, b: any) {
// return a['timeValue'] - b['timeValue'];
// }
//
// function calcDateValue(_year: string, _month: string, _day: string, _hour: string, _minute: string): number {
// const year = parseInt(_year) * 12 * 31 * 24 * 60;
// const month = parseInt(_month) * 31 * 24 * 60;
// const day = parseInt(_day) * 24 * 60;
// const hour = parseInt(_hour) * 60;
// const minute = parseInt(_minute);
// return year+month+day+hour+minute;
// }
import { motion } from 'framer-motion';
import React from 'react';
import ProgressIndicator from "./ProgressIndicator";
import classNames from "../../../util/classNames";
const PlanElement = (props: {
tripId: string,
trainIdentifier: string,
trainHeading: string,
stops: {
time: string,
delay?: number,
name: string
name: string,
arrival: Date,
delay?: number
}[]
}) => {
return (
<div>
<div className={"flex flex-row gap-4 items-center font-semibold"}>
<div className={"flex justify-center items-center bg-green-700 text-lg h-8 w-16 leading-none"}>
<div className={classNames(
"flex justify-center items-center text-lg h-10 w-20 leading-none",
trainIdentifierToColor(props.trainIdentifier)
)}>
{props.trainIdentifier}
</div>
<h3 className={"text-xl"}>
......@@ -23,12 +28,38 @@ const PlanElement = (props: {
</div>
<motion.ol role="list" className="overflow-hidden" aria-hidden="true">
<ProgressIndicator first={true} id={"a"} name={"Meitnerweg"} arrival={new Date("Mon Jul 17 2023 23:56:56 GMT+0200 (Central European Summer Time)")}/>
<ProgressIndicator id={"b"} name={"Dortmund Universität S"} arrival={new Date("Mon Jul 17 2023 23:57:07 GMT+0200 (Central European Summer Time)")}/>
<motion.ol role="list" className="overflow-hidden" aria-hidden="true">
{
props.stops.map((stop, index) => (
<ProgressIndicator
first={index === 0}
id={stop.name}
name={stop.name}
arrival={stop.arrival}
/>
))
}
</motion.ol>
</div>
);
};
export default PlanElement;
const trainIdentifierToColor = (identifier: string): string => {
if(identifier.startsWith("S")) {
return "bg-green-700";
}
if(identifier.startsWith("X")) {
return "bg-pink-700";
}
// Fixme: This should just be "if first is number"
if(identifier.startsWith("4")) {
return "bg-sky-700";
}
return "bg-zinc-700";
}
import TimeAgo from 'javascript-time-ago'
import de from 'javascript-time-ago/locale/de'
import {useEffect, useState} from "react";
export default function ProgressIndicator(props: {first?: boolean, id: string, name: string, arrival: Date}) {
const [timeUntil, setTimeUntil] = useState<string>("");
useEffect(() => {
// Setup TimeAgo
TimeAgo.addDefaultLocale(de);
const timeAgo = new TimeAgo('de-DE')
const update = () => {
setTimeUntil(timeAgo.format(new Date(), {future: true}));
}
setInterval(update, 1000);
update();
}, []);
// import TimeAgo from 'javascript-time-ago'
// import de from 'javascript-time-ago/locale/de'
// import {useEffect, useState} from "react";
export default function ProgressIndicator(props: {first?: boolean, id: string, name: string, arrival: Date, delay?: number}) {
// const [timeUntil, setTimeUntil] = useState<string>("");
//
// useEffect(() => {
// // Setup TimeAgo
// TimeAgo.addDefaultLocale(de);
// const timeAgo = new TimeAgo('de-DE')
//
// const update = () => {
// setTimeUntil(timeAgo.format(new Date(), {future: true}));
// }
//
// setInterval(update, 1000);
// update();
// }, []);
return (
<li className={`relative ${!(props.first ?? false) ? "-mt-3.5" : "-mt-1"} text-sm text-zinc-300`} key={props.id}>
<li className={`relative ${!(props.first ?? false) ? "-mt-3.5" : "-mt-2"} text-sm text-zinc-300`} key={props.id}>
<div className={"flex flex-row gap-4"}>
<div className={"w-16 flex flex-row justify-center"}>
<div className="w-[3px] h-6 -mb-5 bg-zinc-400" aria-hidden="true" />
<div className={"w-20 flex flex-row justify-center"}>
<div className="w-[.25rem] h-6 -mb-5 bg-zinc-400" aria-hidden="true" />
</div>
<div className={"flex-1"}></div>
</div>
<div className="relative flex flex-row items-center gap-4">
<div className={"w-16 flex flex-row justify-center"}>
<span className="h-9 flex items-center" aria-hidden="true">
<span className="relative z-10 w-[18px] h-[18px] flex items-center justify-center bg-zinc-400 rounded-full">
<span className="h-[12px] w-[12px] bg-zinc-900 rounded-full" />
<div className={"w-20 flex flex-row justify-center"}>
<span className="h-[2.6rem] flex items-center" aria-hidden="true">
<span className="relative z-10 w-[1.5rem] h-[1.5rem] flex items-center justify-center bg-zinc-400 rounded-full">
<span className="h-[1rem] w-[1rem] bg-zinc-900 rounded-full" />
</span>
</span>
</div>
......@@ -40,10 +40,19 @@ export default function ProgressIndicator(props: {first?: boolean, id: string, n
{props.name}
</div>
<div>
{timeUntil}
<div className={"tabular-nums"}>
{props.delay && (
<span className={"text-red-400"}>
(+2)&nbsp;&nbsp;
</span>
)}
{renderTime(props.arrival)}
</div>
</div>
</li>
)
}
const renderTime = (date: Date) => {
return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")} Uhr`
}
export type StationResponse = {
error: string | null,
preformatted: string[][],
raw: Departure[],
version: string
}
export type Departure = {
countdown: string,
date: string,
delay: string,
destination: string,
info: string,
is_cancelled: number,
key: string,
line: string,
lineref: {
direction: string,
identifier: string,
mot: string,
name: string,
operator: string,
route: string,
type: string,
valid: string
},
mot: string,
next_route: any[],
occupancy: null,
platform: string,
platform_db: number,
platform_name: string,
prev_route: any[],
sched_date: string,
sched_time: string,
time: string,
train_no: null,
type: string,
internal: {
stop: string
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment