Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor frontend: Merge duplicated layouts #80

Merged
merged 3 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions frontend/src/components/Loader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<span class="loader"></span>

<style>
/* https://cssloaders.github.io/ */
.loader {
animation: rotate 1s infinite;
height: 50px;
width: 50px;
}

.loader:before,
.loader:after {
border-radius: 50%;
content: "";
display: block;
height: 20px;
width: 20px;
}
.loader:before {
animation: ball1 1s infinite;
background-color: var(--text);
box-shadow: 30px 0 0 var(--accent);
margin-bottom: 10px;
}
.loader:after {
animation: ball2 1s infinite;
background-color: var(--accent);
box-shadow: 30px 0 0 var(--text);
}

@keyframes rotate {
0% {
transform: rotate(0deg) scale(0.8);
}
50% {
transform: rotate(360deg) scale(1.2);
}
100% {
transform: rotate(720deg) scale(0.8);
}
}

@keyframes ball1 {
0% {
box-shadow: 30px 0 0 var(--accent);
}
50% {
box-shadow: 0 0 0 var(--accent);
margin-bottom: 0;
transform: translate(15px, 15px);
}
100% {
box-shadow: 30px 0 0 var(--accent);
margin-bottom: 10px;
}
}

@keyframes ball2 {
0% {
box-shadow: 30px 0 0 var(--text);
}
50% {
box-shadow: 0 0 0 var(--text);
margin-top: -20px;
transform: translate(15px, 15px);
}
100% {
box-shadow: 30px 0 0 var(--text);
margin-top: 0;
}
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/ui/controls/StationSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Station } from "$models/station";
import { onMount } from "svelte";
import { env } from "$env/dynamic/public";
import { Search } from "lucide-svelte";
import Search from "lucide-svelte/icons/search";

let { station = $bindable(undefined) }: { station?: Station } = $props();
let open = $state<boolean>(false);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ui/controls/TimePicker.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from "svelte";
import { DateTime } from "luxon";
import { CalendarDays } from "lucide-svelte";
import CalendarDays from "lucide-svelte/icons/calendar-days";

let { selectedDate = $bindable(DateTime.now().set({ second: 0, millisecond: 0 })) } = $props();

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/params/int.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ParamMatcher } from "@sveltejs/kit";

export const match: ParamMatcher = (param) => {
return /^\d+$/.test(param);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
<p class="text-7xl font-bold">
{page.status}
</p>
<p class="text-2xl font-medium">This is not what you are looking for...</p>
<p class="text-2xl font-medium">{page.error?.message ?? "This is not what you are looking for..."}</p>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import "$src/app.css";
import Navbar from "$components/Navbar.svelte";
import type { LayoutProps } from "./$types";

let { children }: LayoutProps = $props();
</script>

<div class="flex h-screen w-screen flex-col overflow-hidden">
<div class="flex min-h-screen w-full flex-col">
<Navbar />
{@render children()}
</div>
7 changes: 7 additions & 0 deletions frontend/src/routes/(root)/[...missing]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";

// This is to catch all unfound pages and redirect them to the error page with the layout
export const load = (async () => {
throw error(404, "No page found...");
}) satisfies PageServerLoad;
6 changes: 6 additions & 0 deletions frontend/src/routes/(root)/[evaNumber=int]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";

export const load = (async ({ url }) => {
return redirect(308, `${url.pathname}/departures`);
}) satisfies PageServerLoad;
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ const loadStation = async (evaNumber: string): Promise<Station> => {
const response = await fetch(`${env.BACKEND_DOCKER_BASE_URL}/api/v1/stations/${evaNumber}`, {
method: "GET"
});

if (!response.ok) {
throw error(404, `No station found for ${evaNumber}`);
throw error(404, `No station found for ${evaNumber}. Maybe try a different one?`);
}

const station = (await response.json()) as Station;
if (station.evaNumber === undefined) {
throw error(404, `No station found for ${evaNumber}`);
throw error(404, `No station found for ${evaNumber}. Maybe try a different one?`);
}
return station;
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import "$src/app.css";
import Navbar from "$components/Navbar.svelte";
import type { LayoutProps } from "./$types";
import { MetaTags } from "svelte-meta-tags";
import Clock from "$components/timetable/Clock.svelte";
import { CornerDownRight, Star } from "lucide-svelte";
import CornerDownRight from "lucide-svelte/icons/corner-down-right";
import Star from "lucide-svelte/icons/star";
import { authClient } from "$lib/auth-client";
import { page } from "$app/state";
import { getContext, setContext } from "svelte";
Expand Down Expand Up @@ -75,37 +75,33 @@
}}
/>

<div class="flex min-h-screen flex-col">
<Navbar />

<div class="bg-background sticky top-20 z-10 container mx-auto w-full flex-col px-6 pt-4">
<div class="flex justify-between">
{#snippet favoriteStation()}
<div class="flex flex-row items-baseline gap-x-2 break-all">
{#if $session.data}
<Star
class="shrink-0 cursor-pointer transition-all duration-300 {favor
? 'fill-accent stroke-yellow-400'
: 'hover:stroke-accent fill-transparent'}"
onclick={toggleFavour}
/>
{/if}
<span class="text-xl font-semibold break-words whitespace-normal md:text-4xl">{station.name}</span>
</div>
{/snippet}

{@render favoriteStation()}
<Clock />
</div>

<button class="group relative flex cursor-pointer gap-x-2 text-base md:text-xl" onclick={navigate}>
<CornerDownRight />
Show {isDeparture ? "Arrivals" : "Departures"}
<span
class="bg-accent absolute bottom-0 left-0 h-0.5 w-full scale-x-0 transform transition-transform duration-300 group-hover:scale-x-100"
></span>
</button>
<div class="bg-background sticky top-20 z-10 container mx-auto w-full flex-col px-6 pt-4">
<div class="flex justify-between">
{#snippet favoriteStation()}
<div class="flex flex-row items-baseline gap-x-2 break-all">
{#if $session.data}
<Star
class="shrink-0 cursor-pointer transition-all duration-300 {favor
? 'fill-accent stroke-yellow-400'
: 'hover:stroke-accent fill-transparent'}"
onclick={toggleFavour}
/>
{/if}
<span class="text-xl font-semibold break-words whitespace-normal md:text-4xl">{station.name}</span>
</div>
{/snippet}

{@render favoriteStation()}
<Clock />
</div>

{@render children()}
<button class="group relative flex cursor-pointer gap-x-2 text-base md:text-xl" onclick={navigate}>
<CornerDownRight />
Show {isDeparture ? "Arrivals" : "Departures"}
<span
class="bg-accent absolute bottom-0 left-0 h-0.5 w-full scale-x-0 transform transition-transform duration-300 group-hover:scale-x-100"
></span>
</button>
</div>

{@render children()}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { Journey } from "$models/connection";
import { DateTime } from "luxon";
import { env } from "$env/dynamic/private";

export const load: PageServerLoad = async ({ params, url }): Promise<{ journeys: Journey[] }> => {
const journeys = await loadJourneys(
export const load: PageServerLoad = async ({ params, url }): Promise<{ journeys: Promise<Journey[]> }> => {
const journeys = loadJourneys(
params.evaNumber,
params.type,
url.searchParams.get("startDate") ?? DateTime.now().set({ second: 0, millisecond: 0 }).toISO()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import { onMount } from "svelte";
import type { PageProps } from "./$types";
import Filter from "$components/timetable/filter/Filter.svelte";
import type { Connection, Journey } from "$models/connection";
import ConnectionComponent from "$components/timetable/ConnectionComponent.svelte";
import WingTrain from "$components/timetable/WingTrain.svelte";
import Loader from "$components/Loader.svelte";

let { data }: PageProps = $props();

let currentFilter = $state(["*"]);

const matchesFilter = (journey: Journey) => {
if (currentFilter.length === 0) return true;
if (currentFilter.includes("*")) return true;

const firstConnection: Connection = journey?.connections[0];
if (firstConnection === undefined) return false;

return currentFilter.includes(firstConnection?.lineInformation?.type ?? "");
};

const allowedProducts = (journeys: Journey[] | undefined): string[] => {
const products = data.station?.products || [];

const types = journeys!
.flatMap((journey) => journey.connections.map((conn) => conn.lineInformation?.type))
.filter(Boolean);

return products.filter((product) => types.includes(product));
};
</script>

{#await data.journeys}
<div class="mx-auto flex-1">
<Loader />
</div>
{:then journeys}
<div class="scrollbar-track-sky-300 container mx-auto flex flex-1 flex-col divide-y-2 md:divide-y-0">
{#each journeys as journey}
{#if !matchesFilter(journey)}{:else if journey.connections.length > 1}
<WingTrain {journey} />
{:else}
<ConnectionComponent connection={journey.connections[0]} renderInformation={true} renderBorder={true} />
{/if}
{/each}
</div>

<div class="sticky bottom-0 mt-auto w-full">
<Filter allowedProducts={allowedProducts(journeys)} bind:selected={currentFilter} />
</div>
{/await}
8 changes: 8 additions & 0 deletions frontend/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import "$src/app.css";
import type { LayoutProps } from "./$types";

let { children }: LayoutProps = $props();
</script>

{@render children()}
12 changes: 0 additions & 12 deletions frontend/src/routes/[evaNumber]/[type=type]/+error.svelte

This file was deleted.

43 changes: 0 additions & 43 deletions frontend/src/routes/[evaNumber]/[type=type]/+page.svelte

This file was deleted.

12 changes: 0 additions & 12 deletions frontend/src/routes/journey/coach-sequence/+layout.svelte

This file was deleted.

Loading