diff --git a/src/data.ts b/src/data.ts index 078e077..4ac4db7 100644 --- a/src/data.ts +++ b/src/data.ts @@ -1,4 +1,4 @@ -type Line2 = { +export type Line = { type: "STR" | "Bus" | "RE" | "RB"; color: string; number: string; @@ -7,23 +7,34 @@ export type Changes = { /** Optional because this could be the last connection and destination is set. */ changes?: Connection[]; }; + +type Time = { + label: string; + time: Date; +}; + +export function getTime(time: Time): string { + if (typeof time === "string") return time; + return time.label; +} + export type Transport = { type: "transport"; /** Start and at the same time destination of the parent connection before this if this is not the first connection */ start: string; - line: Line2; + line: Line; headsign: string; /** Only set if this is the last connection/leaf in the connection tree. If this is the last connection, then changes should not exist */ destination: string; time: { /** Time to change from the connection before this (parent connection). Optional because this could be the first connection */ change?: string; - arrive: string; - depart: string; + arrive: Time; + depart: Time; duration: string; delay?: { - arrive: string; - depart: string; + arrive: Time; + depart: Time; }; }; platform?: string; @@ -36,13 +47,10 @@ export type Walk = { time: string; start: string; destination: string; - - /** Optional because this could be the last connection and destination is set. */ - changes?: Connection[]; }; export type Connection = (Transport | Walk) & Changes; -const WALK_BONN_HBF: Walk = { +export const WALK_BONN_HBF: Walk = { type: "walk", timeAvailable: "8min", distance: "84m", @@ -51,7 +59,7 @@ const WALK_BONN_HBF: Walk = { destination: "Bonn Hbf", }; -const WALK_BARBAROSSAPLATZ: Walk = { +export const WALK_BARBAROSSAPLATZ: Walk = { type: "walk", timeAvailable: "9min", distance: "334m", @@ -60,7 +68,17 @@ const WALK_BARBAROSSAPLATZ: Walk = { destination: "Barbarossaplatz, Köln", }; -const RE_WESEL_1628: Connection = { +export function createTime(hour: number, minute: number): Time { + const date = new Date(0, 0, 0, hour, minute); + return { + label: date.toLocaleTimeString(undefined, { + hourCycle: "h23", + timeStyle: "short", + }), + time: date, + }; +} +export const RE_WESEL_1628: Connection = { type: "transport", start: "Bonn Hbf", line: { @@ -70,12 +88,16 @@ const RE_WESEL_1628: Connection = { }, headsign: "Wesel", time: { - arrive: "16:21", - depart: "16:04", + // arrive: "16:21", + arrive: createTime(16, 21), + depart: createTime(16, 4), + // depart: "16:04", duration: "17min", delay: { - arrive: "16:45", - depart: "16:28", + // arrive: "16:45", + arrive: createTime(16, 45), + // depart: "16:28", + depart: createTime(16, 28), }, }, platform: "1", @@ -96,12 +118,16 @@ const RE_WESEL_1628: Connection = { headsign: "Riehl Slabystr., Köln", destination: "Neumarkt, Köln", time: { - arrive: "16:57", - depart: "16:54", + // arrive: "16:57", + arrive: createTime(16, 57), + // depart: "16:54", + depart: createTime(16, 54), duration: "3min", delay: { - arrive: "16:57", - depart: "16:54", + // arrive: "16:57", + arrive: createTime(16, 57), + // depart: "16:54", + depart: createTime(16, 54), }, }, }, @@ -110,7 +136,7 @@ const RE_WESEL_1628: Connection = { ], }; -const STR_NIEHL_1622: Connection = { +export const STR_NIEHL_1622: Connection = { type: "transport", start: "Hauptbahnhof, Bonn", // line: "STR 16", @@ -122,17 +148,21 @@ const STR_NIEHL_1622: Connection = { headsign: "Niehl Sebastianstr., Köln", destination: "Neumarkt, Köln", time: { - arrive: "17:15", - depart: "16:22", + // arrive: "17:15", + arrive: createTime(17, 15), + // depart: "16:22", + depart: createTime(16, 22), duration: "53min", delay: { - arrive: "16:22", - depart: "17:15", + // arrive: "16:22", + arrive: createTime(16, 22), + // depart: "17:15", + depart: createTime(17, 15), }, }, }; -const RB_MESSE_1633: Connection = { +export const RB_MESSE_1633: Connection = { type: "transport", start: "Bonn Hbf", line: { @@ -143,12 +173,16 @@ const RB_MESSE_1633: Connection = { headsign: "Köln Messe/Deutz", destination: "Köln Süd", time: { - arrive: "16:53", - depart: "16:33", + // arrive: "16:53", + arrive: createTime(16, 53), + // depart: "16:33", + depart: createTime(16, 33), duration: "20min", delay: { - arrive: "16:54", - depart: "16:34", + // arrive: "16:54", + arrive: createTime(16, 54), + // depart: "16:34", + depart: createTime(16, 34), }, }, changes: [ @@ -167,12 +201,16 @@ const RB_MESSE_1633: Connection = { headsign: "Niehl Sebastianstr., Köln", destination: "Neumarkt, Köln", time: { - arrive: "17:05", - depart: "17:02", + // arrive: "17:05", + arrive: createTime(17, 5), + // depart: "17:02", + depart: createTime(17, 2), duration: "3min", delay: { - arrive: "17:05", - depart: "17:02", + // arrive: "17:05", + arrive: createTime(17, 5), + // depart: "17:02", + depart: createTime(17, 2), }, }, }, @@ -181,7 +219,7 @@ const RB_MESSE_1633: Connection = { ], }; -function withChange( +export function withChange( connection: Transport, change: string | undefined, ): Connection { @@ -206,8 +244,10 @@ export const connections: Connection[] = [ }, headsign: "Dottendorf Quirinusplatz, Bonn", time: { - arrive: "16:07", - depart: "15:58", + // arrive: "16:07", + arrive: createTime(16, 7), + // depart: "15:58", + depart: createTime(15, 58), duration: "9min", }, destination: "Hauptbahnhof, Bonn", @@ -225,12 +265,16 @@ export const connections: Connection[] = [ destination: "Neumarkt, Köln", time: { change: "5min", - arrive: "17:05", - depart: "16:12", + // arrive: "17:05", + arrive: createTime(17, 5), + // depart: "16:12", + depart: createTime(16, 12), duration: "53min", delay: { - arrive: "16:12", - depart: "17:05", + // arrive: "16:12", + arrive: createTime(16, 12), + // depart: "17:05", + depart: createTime(17, 5), }, }, }, @@ -254,12 +298,16 @@ export const connections: Connection[] = [ headsign: "Dottendorf Quirinusplatz, Bonn", destination: "Hauptbahnhof, Bonn", time: { - arrive: "16:17", - depart: "16:08", + // arrive: "16:17", + arrive: createTime(16, 17), + // depart: "16:08", + depart: createTime(16, 8), duration: "9min", delay: { - arrive: "16:08", - depart: "16:17", + // arrive: "16:08", + arrive: createTime(16, 8), + // depart: "16:17", + depart: createTime(16, 17), }, }, changes: [ @@ -278,8 +326,10 @@ export const connections: Connection[] = [ headsign: "Hauptbahnhof, Bonn", destination: "Hauptbahnhof, Bonn", time: { - arrive: "16:15", - depart: "16:05", + // arrive: "16:15", + arrive: createTime(16, 15), + // depart: "16:05", + depart: createTime(16, 5), duration: "10min", }, changes: [ @@ -300,8 +350,10 @@ export const connections: Connection[] = [ headsign: "Hauptbahnhof, Bonn", destination: "Hauptbahnhof, Bonn", time: { - arrive: "16:20", - depart: "16:09", + // arrive: "16:20", + arrive: createTime(16, 20), + // depart: "16:09", + depart: createTime(16, 9), duration: "10min", }, changes: [ @@ -323,8 +375,10 @@ export const connections: Connection[] = [ headsign: "Dottendorf Quirinusplatz, Bonn", destination: "Hauptbahnhof, Bonn", time: { - arrive: "16:27", - depart: "16:18", + // arrive: "16:27", + arrive: createTime(16, 27), + // depart: "16:18", + depart: createTime(16, 18), duration: "9min", }, changes: [ @@ -347,12 +401,16 @@ export const connections: Connection[] = [ headsign: "Hauptbahnhof, Bonn", destination: "Hauptbahnhof, Bonn", time: { - arrive: "16:23", - depart: "16:13", + // arrive: "16:23", + arrive: createTime(16, 23), + // depart: "16:13", + depart: createTime(16, 13), duration: "10min", delay: { - arrive: "16:28", - depart: "16:18", + // arrive: "16:28", + arrive: createTime(16, 28), + // depart: "16:18", + depart: createTime(16, 18), }, }, changes: [ diff --git a/src/data2.ts b/src/data2.ts new file mode 100644 index 0000000..2d67250 --- /dev/null +++ b/src/data2.ts @@ -0,0 +1,191 @@ +import { + Connection, + RE_WESEL_1628, + STR_NIEHL_1622, + WALK_BONN_HBF, + createTime, + withChange, +} from "./data"; + +export const CONNECTIONS_TO_HAUPTBAHNHOF: Connection[] = [ + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + // line: "STR 62", + line: { + type: "STR", + color: "bg-[#9f3d53]", + number: "62", + }, + headsign: "Dottendorf Quirinusplatz, Bonn", + time: { + // arrive: "16:07", + arrive: createTime(16, 7), + // depart: "15:58", + depart: createTime(15, 58), + duration: "9min", + }, + destination: "Hauptbahnhof, Bonn", + }, + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + // line: "STR 62", + line: { + type: "STR", + color: "bg-[#9f3d53]", + number: "62", + }, + headsign: "Dottendorf Quirinusplatz, Bonn", + destination: "Hauptbahnhof, Bonn", + time: { + // arrive: "16:17", + arrive: createTime(16, 17), + // depart: "16:08", + depart: createTime(16, 8), + duration: "9min", + delay: { + // arrive: "16:08", + arrive: createTime(16, 8), + // depart: "16:17", + depart: createTime(16, 17), + }, + }, + }, + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + line: { + type: "Bus", + color: "bg-[#76408C]", + number: "117", + }, + headsign: "Hauptbahnhof, Bonn", + destination: "Hauptbahnhof, Bonn", + time: { + // arrive: "16:15", + arrive: createTime(16, 15), + // depart: "16:05", + depart: createTime(16, 5), + duration: "10min", + }, + }, + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + line: { + type: "Bus", + color: "bg-[#76408C]", + number: "603", + }, + headsign: "Hauptbahnhof, Bonn", + destination: "Hauptbahnhof, Bonn", + time: { + // arrive: "16:20", + arrive: createTime(16, 20), + // depart: "16:09", + depart: createTime(16, 9), + duration: "10min", + }, + }, + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + line: { + type: "STR", + color: "bg-[#9f3d53]", + number: "62", + }, + headsign: "Dottendorf Quirinusplatz, Bonn", + destination: "Hauptbahnhof, Bonn", + time: { + // arrive: "16:27", + arrive: createTime(16, 27), + // depart: "16:18", + depart: createTime(16, 18), + duration: "9min", + }, + }, + { + type: "transport", + start: "Beuel Konrad-Adenauer-Platz, Bonn", + line: { + type: "Bus", + color: "bg-[#76408C]", + number: "640", + }, + headsign: "Hauptbahnhof, Bonn", + destination: "Hauptbahnhof, Bonn", + time: { + // arrive: "16:23", + arrive: createTime(16, 23), + // depart: "16:13", + depart: createTime(16, 13), + duration: "10min", + delay: { + // arrive: "16:28", + arrive: createTime(16, 28), + // depart: "16:18", + depart: createTime(16, 18), + }, + }, + }, +]; + +export const CONNECTIONS_FROM_HAUPTBAHNHOF: Connection[] = [ + { + type: "transport", + start: "Hauptbahnhof, Bonn", + // line: "STR 16", + line: { + type: "STR", + color: "bg-[#9f3d53]", + number: "16", + }, + headsign: "Niehl Sebastianstr., Köln", + destination: "Neumarkt, Köln", + time: { + change: "5min", + // arrive: "17:05", + arrive: createTime(17, 5), + // depart: "16:12", + depart: createTime(16, 12), + duration: "53min", + delay: { + // arrive: "16:12", + arrive: createTime(16, 12), + // depart: "17:05", + depart: createTime(17, 5), + }, + }, + }, + { + type: "transport", + start: "Hauptbahnhof, Bonn", + // line: "STR 16", + line: { + type: "STR", + color: "bg-[#9f3d53]", + number: "16", + }, + headsign: "Niehl Sebastianstr., Köln", + destination: "Neumarkt, Köln", + time: { + // arrive: "17:15", + arrive: createTime(17, 15), + // depart: "16:22", + depart: createTime(16, 22), + duration: "53min", + delay: { + // arrive: "16:22", + arrive: createTime(16, 22), + // depart: "17:15", + depart: createTime(17, 15), + }, + }, + }, + { + ...WALK_BONN_HBF, + timeAvailable: "21min", + }, +]; diff --git a/src/routes/journey.tsx b/src/routes/journey.tsx index 60b5aae..d9e1401 100644 --- a/src/routes/journey.tsx +++ b/src/routes/journey.tsx @@ -10,8 +10,93 @@ import { createSignal, type JSX, } from "solid-js"; -import { Changes, Connection, Transport, Walk, connections } from "~/data"; +import { + Changes, + Connection, + Transport, + Walk, + connections, + getTime, +} from "~/data"; +import { + CONNECTIONS_FROM_HAUPTBAHNHOF, + CONNECTIONS_TO_HAUPTBAHNHOF, +} from "~/data2"; +function TransportConnectionInterim({ connection }: { connection: Transport }) { + return ( + <> +
+ +
+
+
+
+ +
+
+
+

+ to {connection.headsign} +

+ +
+
+
+
+
+ + + + + + + + + + + + + + + {connection.line.type} {connection.line.number} + +
+
+ + ); +} function TransportConnection({ connection, }: { @@ -24,10 +109,10 @@ function TransportConnection({ class="grid h-56 grid-cols-[3rem_auto_1fr] grid-rows-[auto_1fr_auto] rounded-large bg-light-surface p-2" >
@@ -43,10 +128,10 @@ function TransportConnection({ to {connection.headsign}

@@ -122,6 +207,12 @@ function TransportConnection({ function WalkConnection({ connection }: { connection: Walk }): JSX.Element { return ( <> +

+ {/* Setting pl-20 to align with grid. TODO find better solution */} + + {connection.destination} + +

-

- {/* Setting pl-20 to align with grid. TODO find better solution */} - - {connection.destination} - -

); } @@ -286,20 +371,11 @@ function Connection2(properties: Connection2Properties): JSX.Element { ); } -function useObserver() { +function useObserver(callback: IntersectionObserverCallback) { let observer: IntersectionObserver | undefined; - function observeCallback(entries: IntersectionObserverEntry[]) { - console.debug("Intersection", entries.length); - for (const entry of entries) { - if (entry.isIntersecting) { - console.debug("Intersecting", entry.target); - } - } - } - function setScroller(element: Element) { - observer = new IntersectionObserver(observeCallback, { + observer = new IntersectionObserver(callback, { root: element, // This ensures that the element is only triggered once it is past the snap point threshold: 0.5, @@ -329,7 +405,9 @@ type RowProperties = { function Row({ scrollLeft, setScrollLeft }: VoidProps) { const debugIndex = debugOnlyRowIndex; debugOnlyRowIndex++; - const { setScroller, observe } = useObserver(); + const { setScroller, observe } = useObserver(() => { + throw new Error("todo"); + }); const [row, setRow] = createSignal(); @@ -390,37 +468,37 @@ function Row({ scrollLeft, setScrollLeft }: VoidProps) { } export default function Journeys() { - let timeoutId: ReturnType | undefined; - // function handleScroll(event: Event) { - // if (!(event.target instanceof HTMLDivElement)) - // throw new Error("Expected div"); - - // // References for later when we don't have the type - // const scrollLeft = event.target.scrollLeft; - // const offsetWidth = event.target.offsetWidth; - // const children = event.target.children; - - // const isAtSnappingPoint = scrollLeft % offsetWidth === 0; - // const debounce = isAtSnappingPoint ? 0 : 100; - - // if (timeoutId !== undefined) clearTimeout(timeoutId); + const [isWalkSnapped, setIsWalkSnapped] = createSignal(false); + + // To not trigger animation on initial render + let dirtyFlag = false; + const wasWalkSnapped = () => { + if (dirtyFlag) { + dirtyFlag = false; + return true; + } - // timeoutId = setTimeout(() => { - // if (!debounce) { - // console.debug("Snap"); - // // const index = Math.floor(scrollLeft / offsetWidth); - // // const child = children[index]; - // // console.debug("Snap at", index, child.id); + return false; + }; + createEffect(() => { + if (isWalkSnapped()) { + dirtyFlag = true; + } + }); - // return; - // } + function observeCallback([first]: IntersectionObserverEntry[]) { + //TODO avoid initial call with all elements + const target = first.target; - // console.debug("User stopped scrolling"); - // }, debounce); + const isWalkFocused = + target instanceof HTMLElement && + first.isIntersecting && + target.dataset.type === "walk"; - // console.debug("Scroll"); - // // console.debug("Scroll", event.target.scrollLeft, event.target.offsetWidth); - // } + console.debug("Intersection", isWalkFocused); + setIsWalkSnapped(isWalkFocused); + } + const { setScroller, observe } = useObserver(observeCallback); const [row1, setRow1] = createSignal(); @@ -436,14 +514,16 @@ export default function Journeys() { } return ( -
-

Starting station

{" "} +
+

+ Beuel Konrad-Adenauer-Platz, Bonn +

setIsScrolling(false)} > - + {/* {(_, index) => (
)} + */} + + {(connection) => ( +
+ + + + + + + + +
+ )}
-
Important Station
+

+ Hauptbahnhof, Bonn +

+ {/* Can't detect intersection/snap with CSS. Therefore we need to use JS */}
{ + setRow1(element); + setScroller(element); + }} + class="grid snap-mandatory grid-flow-col content-end gap-4 overflow-y-scroll" classList={{ "snap-x": !isScrolling(), + "animate-grow-walk": !isWalkSnapped() && wasWalkSnapped(), + "h-24 animate-shrink-walk": isWalkSnapped(), }} > - + {/* {(_, index) => (
)} + */} + + {(connection) => ( +
+ + + + + + + + +
+ )}
diff --git a/tailwind.config.js b/tailwind.config.js index c222495..fd1fda7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,9 +10,23 @@ export default { from: { transform: "translateY(100%)" }, to: { transform: "translateY(0)" }, }, + "shrink-walk": { + //TODO how to access Tailwind CSS config? + // h-56 to h-24 + from: { height: "14rem" }, + to: { height: "6rem" }, + }, + "grow-walk": { + //TODO how to access Tailwind CSS config? + // h-56 to h-24 + from: { height: "6rem" }, + to: { height: "14rem" }, + }, }, animation: { "fly-in": "fly-in 500ms ease-in-out", + "shrink-walk": "shrink-walk 500ms ease-in-out", + "grow-walk": "grow-walk 500ms ease-in-out", }, }, },