diff --git a/Website/src/app/components/base_layout.tsx b/Website/src/app/components/base_layout.tsx new file mode 100644 index 00000000..16620f41 --- /dev/null +++ b/Website/src/app/components/base_layout.tsx @@ -0,0 +1,32 @@ +import './globals.css' +import Header from "@/app/components/header"; +import Footer from "@/app/components/footer"; + +/** + * The general layout for this site to be used both for pages + * using the app-router and the legacy pages-router. + * + * This will ensure that each page has a header and a footer. + * + * Because this is a flex-layout, children can use the `grow` + * class to grow to take up the remaining space on the page. + * + * @param children The actual page this layout should wrap. In JSX, these are the + * children of this element. + * @constructor + */ +export default function BaseLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( + // inline styling using tailwind will keep styling information in the same + // file as the markup information. +
+
+ {children} +
+ ) + } \ No newline at end of file diff --git a/Website/src/components/dynmap.tsx b/Website/src/app/components/dynmap.tsx similarity index 92% rename from Website/src/components/dynmap.tsx rename to Website/src/app/components/dynmap.tsx index f11d9f0d..2d475078 100644 --- a/Website/src/components/dynmap.tsx +++ b/Website/src/app/components/dynmap.tsx @@ -1,11 +1,11 @@ "use client"; import dynamic from 'next/dynamic'; import LoadMapScreen from './loadmap'; -import {Vehicle} from "@/lib/api.website"; -import {IMapRefreshConfig, RevalidateError} from '@/lib/types'; +import {Vehicle} from "@/utils/api.website"; +import {IMapRefreshConfig, RevalidateError} from '@/utils/types'; import useSWR from "swr"; -const _internal_DynamicMap = dynamic(() => import('@/components/map'), { +const _internal_DynamicMap = dynamic(() => import('@/app/components/map'), { loading: LoadMapScreen, ssr: false }); diff --git a/Website/src/app/components/form.tsx b/Website/src/app/components/form.tsx new file mode 100644 index 00000000..a977975c --- /dev/null +++ b/Website/src/app/components/form.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +/** + * A component that wraps any Form, giving them a consistent appearance. + * + * @param children The actual page this layout should wrap. In JSX, these are the + * children of this element. + * @constructor + */ +export function FormWrapper({children}: { children: React.ReactNode }) { + return ( +
+
+ {children} +
+
+ ) +} + +// TODO: create a component for a form in a dialog to replace/refactor +// the LoginDialog and SelectionDialog components. \ No newline at end of file diff --git a/Website/src/components/globals.css b/Website/src/app/components/globals.css similarity index 100% rename from Website/src/components/globals.css rename to Website/src/app/components/globals.css diff --git a/Website/src/components/loadmap.tsx b/Website/src/app/components/loadmap.tsx similarity index 100% rename from Website/src/components/loadmap.tsx rename to Website/src/app/components/loadmap.tsx diff --git a/Website/src/components/login.tsx b/Website/src/app/components/login.tsx similarity index 100% rename from Website/src/components/login.tsx rename to Website/src/app/components/login.tsx diff --git a/Website/src/components/login_wrap.tsx b/Website/src/app/components/login_wrap.tsx similarity index 83% rename from Website/src/components/login_wrap.tsx rename to Website/src/app/components/login_wrap.tsx index 57f86a2e..f87968a2 100644 --- a/Website/src/components/login_wrap.tsx +++ b/Website/src/app/components/login_wrap.tsx @@ -1,8 +1,8 @@ "use client" -import {IMapRefreshConfig} from "@/lib/types"; +import {IMapRefreshConfig} from "@/utils/types"; import {useState} from "react"; -import {LoginDialog} from "@/components/login"; -import {SelectionDialog} from "@/components/track_selection"; +import {LoginDialog} from "@/app/components/login"; +import {SelectionDialog} from "@/app/components/track_selection"; const LoginWrapper = ({logged_in, track_selected, map_conf, child}: {logged_in: boolean, track_selected: boolean, map_conf: IMapRefreshConfig, child: (conf: IMapRefreshConfig) => JSX.Element}) => { const [loginState, setLogin] = useState(logged_in); diff --git a/Website/src/app/components/map.tsx b/Website/src/app/components/map.tsx new file mode 100644 index 00000000..064396ac --- /dev/null +++ b/Website/src/app/components/map.tsx @@ -0,0 +1,165 @@ +"use client"; +import L from "leaflet" +import "leaflet-rotatedmarker" +import 'leaflet/dist/leaflet.css' +import {useEffect, useMemo, useRef, useState} from "react"; +import {IMapConfig} from '@/utils/types' +import {batteryLevelFormatter, coordinateFormatter} from "@/utils/helpers"; +import assert from "assert"; +import {createPortal} from "react-dom"; + +function Map(props: IMapConfig) { + + // console.log('props', props); + + const {position: initial_position, zoom_level, server_vehicles: vehicles, init_data, focus: initial_focus} = props; + + const mapRef = useRef(undefined as L.Map | undefined); + const markerRef = useRef([] as L.Marker[]) + const mapContainerRef = useRef(null as HTMLDivElement | null) + const [position, setPosition] = useState(initial_position) + const [focus, setFocus] = useState(initial_focus); + const [popupContainer, setPopupContainer] = useState(undefined as undefined | HTMLDivElement); + const markerIcon = useMemo(() => new L.Icon({ + iconUrl: "generic_rail_bound_vehicle.svg", + iconSize: L.point(45, 45) + }), []); + + const vehicleInFocus = vehicles.find((v) => v.id == focus); + + // debugger; + + function insertMap() { + // debugger; + // console.log(mapRef, mapRef.current); + assert(mapContainerRef.current, "Error: Ref to Map Container not populated"); + assert(mapRef.current == undefined, "Error: Trying to insert map more than once"); + mapRef.current = L.map(mapContainerRef.current); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(mapRef.current); + + /*const openrailwaymap = L.tileLayer('http://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', + { + attribution: '© OpenStreetMap contributors, Style: CC-BY-SA 2.0 OpenRailwayMap and OpenStreetMap', + minZoom: 2, + maxZoom: 19, + tileSize: 256 + }).addTo(mapRef.current);*/ + + } + + function setMapZoom() { + assert(mapRef.current != undefined, "Error: Map not ready!"); + + mapRef.current.setZoom(zoom_level); + } + + function setMapPosition() { + assert(mapRef.current != undefined, "Error: Map not ready!"); + assert(!Number.isNaN(mapRef.current?.getZoom()), "Error: ZoomLevel MUST be set before position is set!") + + mapRef.current.setView(position); + } + + + function addTrackPath() { + assert(mapRef.current != undefined, "Error: Map not ready!"); + + const trackPath = L.geoJSON(init_data?.trackPath, {style: {color: 'red'}}) + trackPath.addTo(mapRef.current) + + // Add a callback to remove the track path to remove the track path in case of a re-render. + return () => { + trackPath.remove(); + } + } + + function updateMarkers() { + + assert(mapRef.current != undefined, "Error: Map not ready!"); + + while (markerRef.current.length > vehicles.length) { + const m = markerRef.current.pop() + if (m) { + m.remove() + } else { + break; + } + } + vehicles.forEach((v, i) => { + if (i >= markerRef.current.length) { + if (mapRef.current) { + const m = L.marker(vehicles[i].pos, { + icon: markerIcon, + rotationOrigin: "center" + }).addTo(mapRef.current); + markerRef.current.push(m); + } + } + const m = markerRef.current[i]; + m.setLatLng(vehicles[i].pos) + // m.setPopupContent(popupContent(vehicles[i])) + m.setRotationAngle(vehicles[i].heading || 0) + + if (v.id === focus) { + const current_popup = m.getPopup() + if (current_popup == undefined) { + // create a div element to contain the popup content. + // We can then use a React portal to place content in there. + const popupElement = document.createElement('div'); + popupElement.className = "w-64 flex p-1.5 flex-row flex-wrap"; + m.bindPopup(popupElement); + setPopupContainer(popupElement); + // unset the focussed element on popup closing. + m.on('popupclose', () => { + setFocus(undefined) + }) + } + m.openPopup(); + setPosition(vehicles[i].pos); + } else { + m.closePopup(); + m.unbindPopup(); + } + m.on('click', () => { + // set the vehicle as the focussed vehicle if it is clicked. + setFocus(v.id) + }) + + } + ) + } + + useEffect(insertMap, []); + useEffect(setMapZoom, [zoom_level]); + useEffect(setMapPosition, [position]); + useEffect(addTrackPath, [init_data?.trackPath]); + useEffect(updateMarkers, [focus, markerIcon, vehicles]); + + return ( + <> +
+ {popupContainer && createPortal(vehicleInFocus ? + <> +

Vehicle "{vehicleInFocus?.name}"

+
Tracker-Level:
+
{vehicleInFocus ? batteryLevelFormatter.format(vehicleInFocus.batteryLevel) : 'unbekannt'}
+
Position:
+
{ + vehicleInFocus + ? <>{coordinateFormatter.format(vehicleInFocus?.pos.lat)} N {coordinateFormatter.format(vehicleInFocus?.pos.lng)} E + : 'unbekannt'} +
+ :
, popupContainer + )} + + ); +} + + +export default Map \ No newline at end of file diff --git a/Website/src/components/track_selection.tsx b/Website/src/app/components/track_selection.tsx similarity index 70% rename from Website/src/components/track_selection.tsx rename to Website/src/app/components/track_selection.tsx index 50377954..f5de5fd6 100644 --- a/Website/src/components/track_selection.tsx +++ b/Website/src/app/components/track_selection.tsx @@ -2,13 +2,12 @@ import {FormEventHandler, useEffect, useRef} from "react"; -import { UrlObject } from 'url'; import Footer from "@/app/components/footer"; -import {RevalidateError} from "@/lib/types"; +import {RevalidateError} from "@/utils/types"; import useSWR from "swr"; -import {TrackList} from "@/lib/api.website"; +import {TrackList} from "@/utils/api.website"; import {setCookie} from "cookies-next"; -type Url = string | UrlObject; +import {inter} from "@/utils/common"; const selectTrack: FormEventHandler = (e) => { e.preventDefault() @@ -34,16 +33,16 @@ const fetcher = (url: string) => fetch(url).then( } ).then(res => res.json()); -export default function Selection({dst_url}: {dst_url?: Url}) { +export default function Selection() { // @type data TrackList const {data, error, isLoading}: {data: TrackList, error?: any, isLoading: boolean} = useSWR('/webapi/tracks/list', fetcher); return ( -
+ {isLoading ?

Lädt...

: (error ?

{error.toString()}

: (<> - - + {data.map(({id, name}) => ())} ))} @@ -51,20 +50,20 @@ export default function Selection({dst_url}: {dst_url?: Url}) { ) } -export function SelectionDialog({dst_url, login_callback, children}: React.PropsWithChildren<{dst_url?: Url, login_callback?: (success: boolean) => void}>) { +export function SelectionDialog({children}: React.PropsWithChildren<{}>) { const dialogRef = useRef(null as HTMLDialogElement | null) useEffect(() => { if (!dialogRef.current?.open) { dialogRef.current?.showModal(); } - }) + }, []); return ( { event.preventDefault(); - }} className="drop-shadow-xl shadow-black backdrop:bg-gray-200/30 backdrop:backdrop-blur" > + }} className="drop-shadow-xl shadow-black bg-white p-4 rounded max-w-2xl w-full dark:bg-slate-800 dark:text-white backdrop:bg-gray-200/30 backdrop:backdrop-blur" > {children} - +
) diff --git a/Website/src/app/layout.tsx b/Website/src/app/layout.tsx index 1ce4ebd6..fb43a9b5 100644 --- a/Website/src/app/layout.tsx +++ b/Website/src/app/layout.tsx @@ -1,10 +1,22 @@ -import RootLayout from "@/components/layout" +import BaseLayout from "@/app/components/base_layout" +import {inter, meta_info} from "@/utils/common"; -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} +export const metadata = meta_info; -export default RootLayout; +/** + * The Layout to use on all pages in the app-directory. + * Effectively defers to BaseLayout with minimal adjustments. + */ +export default function RootLayout({children,}: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) +} diff --git a/Website/src/app/login/page.tsx b/Website/src/app/login/page.tsx index ffa20625..f7d79ac7 100644 --- a/Website/src/app/login/page.tsx +++ b/Website/src/app/login/page.tsx @@ -1,15 +1,12 @@ -import Login from "@/components/login"; +import Login from "@/app/components/login"; +import {FormWrapper} from "@/app/components/form"; -export default function Home(x: any) { - +export default function LoginPage() { + return ( - //
-
-
- -
-
- //
+ + + ) - } \ No newline at end of file +} \ No newline at end of file diff --git a/Website/src/app/management/add_track/page.tsx b/Website/src/app/management/add_track/page.tsx index 5fd785dc..30e0b027 100644 --- a/Website/src/app/management/add_track/page.tsx +++ b/Website/src/app/management/add_track/page.tsx @@ -1,7 +1,7 @@ 'use client' import {FormEvent, useRef, useState} from "react"; -import {TrackPath} from "@/lib/api.website"; +import {TrackPath} from "@/utils/api.website"; export default function Home() { const formRef = useRef(null as (null | HTMLFormElement)) diff --git a/Website/src/app/management/layout.tsx b/Website/src/app/management/layout.tsx index 89dbdd6a..b0643554 100644 --- a/Website/src/app/management/layout.tsx +++ b/Website/src/app/management/layout.tsx @@ -1,9 +1,9 @@ +import {FormWrapper} from "@/app/components/form"; + export default function Layout({children,}: { children: React.ReactNode }) { return ( -
-
- {children} -
-
+ + {children} + ); } \ No newline at end of file diff --git a/Website/src/app/management/vehicleTypes/client.tsx b/Website/src/app/management/vehicleTypes/client.tsx index 90727fe8..6c9dabdb 100644 --- a/Website/src/app/management/vehicleTypes/client.tsx +++ b/Website/src/app/management/vehicleTypes/client.tsx @@ -7,9 +7,9 @@ else, but also not in ´page.tsx` as we need to obtain the currently selected tr import {ChangeEventHandler, FormEventHandler, useRef, useState} from "react"; import useSWR from "swr"; -import {RevalidateError} from "@/lib/types"; -import {VehicleTypeCrU, VehicleTypeList} from "@/lib/api.website"; -import {nanToUndefined} from "@/lib/helpers"; +import {RevalidateError} from "@/utils/types"; +import {VehicleTypeCrU, VehicleTypeList} from "@/utils/api.website"; +import {nanToUndefined} from "@/utils/helpers"; const fetcher = async (url: string) => { const res = await fetch(url, {method: 'GET',}); diff --git a/Website/src/app/management/vehicles/client.tsx b/Website/src/app/management/vehicles/client.tsx index e59d74fd..41ce0d2d 100644 --- a/Website/src/app/management/vehicles/client.tsx +++ b/Website/src/app/management/vehicles/client.tsx @@ -7,10 +7,10 @@ else, but also not in ´page.tsx` as we need to obtain the currently selected tr import {ChangeEventHandler, FormEventHandler, MouseEventHandler, useRef, useState} from "react"; import useSWR from "swr"; -import {RevalidateError} from "@/lib/types"; -import {VehicleCrU, VehicleList, VehicleTypeList} from "@/lib/api.website"; -import {SelectionDialog} from "@/components/track_selection"; -import {nanToUndefined} from "@/lib/helpers"; +import {RevalidateError} from "@/utils/types"; +import {VehicleCrU, VehicleList, VehicleTypeList} from "@/utils/api.website"; +import {SelectionDialog} from "@/app/components/track_selection"; +import {nanToUndefined} from "@/utils/helpers"; const fetcher = async ([url, track_id]: [url: string, track_id: number]) => { const res = await fetch(`${url}${track_id}`, {method: 'GET',}); diff --git a/Website/src/app/management/vehicles/page.tsx b/Website/src/app/management/vehicles/page.tsx index af95f653..9d1433db 100644 --- a/Website/src/app/management/vehicles/page.tsx +++ b/Website/src/app/management/vehicles/page.tsx @@ -1,7 +1,7 @@ import {getCookie} from "cookies-next"; import {cookies} from "next/headers"; import VehicleManagement from "./client"; -import {getVehicleTypeList} from "@/lib/data"; +import {getVehicleTypeList} from "@/utils/data"; export default async function Page() { const trackID = cookies().get('track_id')?.value; diff --git a/Website/src/app/map/page.tsx b/Website/src/app/map/page.tsx index 2bfa0a58..65b52814 100644 --- a/Website/src/app/map/page.tsx +++ b/Website/src/app/map/page.tsx @@ -1,38 +1,40 @@ //import Map from '@/components/map' -import DynamicMap from '@/components/dynmap'; +import DynamicMap from '@/app/components/dynmap'; import {cookies} from 'next/headers'; -import {getInitData, getVehicleData} from '@/lib/data'; -import {LoginDialog} from "@/components/login"; -import LoginWrapper from "@/components/login_wrap"; -import {InitResponse, Vehicle} from "@/lib/api.website"; -import {nanToUndefined} from "@/lib/helpers"; +import {getInitData, getVehicleData} from '@/utils/data'; +import LoginWrapper from "@/app/components/login_wrap"; +import {InitResponse, Vehicle} from "@/utils/api.website"; +import {nanToUndefined} from "@/utils/helpers"; -export default async function Home({searchParams}: { searchParams: { focus?: string, success?: string } }) { +export default async function MapPage({searchParams}: { searchParams: { focus?: string, success?: string } }) { console.log('params', searchParams); + // get the login token and the ID of the selected track const token = cookies().get("token")?.value; const track_id = parseInt(cookies().get("track_id")?.value ?? '', 10) const track_selected = !isNaN(track_id); + + // try to fetch initial data from the backend, but only if the user has a token, and has a track selected. let server_vehicles: Vehicle[]; let init_data: InitResponse | undefined; try { init_data = (token && track_selected) ? await getInitData(token, track_id) : undefined; server_vehicles = (token && track_selected) ? await getVehicleData(token, track_id) : []; } catch (e) { - console.log('Catched e:', e); + console.error('Error fetching Map Data from the Backend:', e); init_data = undefined; server_vehicles = [] } + // also process the parameter allowing to specify the initial focussed vehicle const focus = nanToUndefined(parseInt(searchParams.focus ?? '', 10)); - console.log("server vehicles", server_vehicles) return ( -
-
- -
-
+ + + ); } \ No newline at end of file diff --git a/Website/src/app/signup/page.tsx b/Website/src/app/signup/page.tsx index c93a3159..b72ecad8 100644 --- a/Website/src/app/signup/page.tsx +++ b/Website/src/app/signup/page.tsx @@ -1,15 +1,15 @@ -import Login from "@/components/login"; +import Login from "@/app/components/login"; +import {FormWrapper} from "@/app/components/form"; +/** + * DO NOT USE. Will probably stop working, as progress on the backend continues. + */ +export default function SignupPage(x: any) { -export default function Home(x: any) { - return ( - //
-
-
- -
-
- //
+ +

Please enter the username and password for the account you want to create.

+ +
) - } \ No newline at end of file +} \ No newline at end of file diff --git a/Website/src/app/webapi/auth/route.ts b/Website/src/app/webapi/auth/route.ts index b8f1773a..2b10e9fd 100644 --- a/Website/src/app/webapi/auth/route.ts +++ b/Website/src/app/webapi/auth/route.ts @@ -1,6 +1,6 @@ import {cookies} from "next/headers"; import {NextRequest, NextResponse} from "next/server"; -import {authenticate} from "@/lib/data"; +import {authenticate} from "@/utils/data"; import {NextURL} from "next/dist/server/web/next-url"; import {hostname} from "os"; diff --git a/Website/src/app/webapi/tracks/list/route.ts b/Website/src/app/webapi/tracks/list/route.ts index 276f586a..7615737c 100644 --- a/Website/src/app/webapi/tracks/list/route.ts +++ b/Website/src/app/webapi/tracks/list/route.ts @@ -3,10 +3,10 @@ * read the auth token from the cookie, so we don't need to access it on the client */ -import {getTrackList, getVehicleData} from "@/lib/data"; +import {getTrackList, getVehicleData} from "@/utils/data"; import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; -import {UnauthorizedError} from "@/lib/types"; +import {UnauthorizedError} from "@/utils/types"; export async function GET(request: NextRequest) { // console.log("foobar", request) diff --git a/Website/src/app/webapi/tracks/new/route.ts b/Website/src/app/webapi/tracks/new/route.ts index 04babf4d..9b904008 100644 --- a/Website/src/app/webapi/tracks/new/route.ts +++ b/Website/src/app/webapi/tracks/new/route.ts @@ -1,7 +1,7 @@ import {NextRequest, NextResponse} from "next/server"; -import {sendTrack} from "@/lib/data"; +import {sendTrack} from "@/utils/data"; import {cookies} from "next/headers"; -import {UnauthorizedError} from "@/lib/types"; +import {UnauthorizedError} from "@/utils/types"; export async function PUT(request: NextRequest, x: any, y: any, z: any) { const payload = await request.json() diff --git a/Website/src/app/webapi/update/route.ts b/Website/src/app/webapi/update/route.ts index c17e50b7..e0c91cf5 100644 --- a/Website/src/app/webapi/update/route.ts +++ b/Website/src/app/webapi/update/route.ts @@ -3,10 +3,10 @@ * read the auth token from the cookie, so we don't need to access it on the client */ -import { getVehicleData } from "@/lib/data"; +import { getVehicleData } from "@/utils/data"; import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; -import {UnauthorizedError} from "@/lib/types"; +import {UnauthorizedError} from "@/utils/types"; export async function POST(request: NextRequest) { diff --git a/Website/src/components/form.tsx b/Website/src/components/form.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/Website/src/components/layout.tsx b/Website/src/components/layout.tsx deleted file mode 100644 index 5d1ebd4f..00000000 --- a/Website/src/components/layout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import './globals.css' -import { Inter } from 'next/font/google' -import Header from "@/app/components/header"; -import Footer from "@/app/components/footer"; - -const inter = Inter({ subsets: ['latin'] }) - -export default function RootLayout({ - children, - }: { - children: React.ReactNode - }) { - return ( - - -
-
- {children} -
-
- - - ) - } \ No newline at end of file diff --git a/Website/src/components/map.tsx b/Website/src/components/map.tsx deleted file mode 100644 index 7c075cc3..00000000 --- a/Website/src/components/map.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; -import L from "leaflet" -import "leaflet-rotatedmarker" -import 'leaflet/dist/leaflet.css' -import {useEffect, useRef} from "react"; -import {createRoot} from "react-dom/client"; -import {IMapConfig} from '@/lib/types' -import {Vehicle} from "@/lib/api.website"; -import {batteryLevelFormatter, coordinateFormatter} from "@/lib/helpers"; - -function popupContent({batteryLevel, name, pos}: Vehicle): L.Content { - const baseContainer = document.createElement('div') - baseContainer.className = "w-64 flex p-1.5 flex-row flex-wrap" - - const contents = ( - <> -

Vehicle "{name}"

-
Tracker-Level:
-
{batteryLevelFormatter.format(batteryLevel)}
-
Position:
-
{coordinateFormatter.format(pos.lat)} N {coordinateFormatter.format(pos.lng)} E
- - ) - const root = createRoot(baseContainer); - root.render(contents); - - return baseContainer; -} - -function Map(props: IMapConfig) { - - // console.log('props', props); - - const {position, zoom_level, server_vehicles, init_data, focus} = props; - - const mapRef = useRef(undefined as L.Map | undefined); - const markerRef = useRef([] as L.Marker[]) - //const [vehicles, setVehicles] = useState(server_vehicles) - const vehicles = server_vehicles; - const mapContainerRef = useRef(null as HTMLDivElement | null) - const markerIcon = new L.Icon({ - iconUrl: "generic_rail_bound_vehicle.svg", - iconSize: L.point(45, 45) - }) - - // debugger; - - function renderMap() { - // debugger; - // console.log(mapRef, mapRef.current); - if (!mapContainerRef.current) { - throw new Error("Ref to Map Container not populated") - } else if (mapRef.current == undefined) { - mapRef.current = L.map(mapContainerRef.current); - - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 19, - attribution: '© OpenStreetMap' - }).addTo(mapRef.current); - - /*const openrailwaymap = L.tileLayer('http://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', - { - attribution: '© OpenStreetMap contributors, Style: CC-BY-SA 2.0 OpenRailwayMap and OpenStreetMap', - minZoom: 2, - maxZoom: 19, - tileSize: 256 - }).addTo(mapRef.current);*/ - - // console.log(vehicles); - // L.circle({lat: 54.2, lng: 10.56}, {radius: 5500}).addTo(mapRef.current); - - for (const v of vehicles) { - const m = L.marker(v.pos, { - icon: markerIcon, - rotationOrigin: "center" - }).addTo(mapRef.current); - m.bindPopup(popupContent(v)) - m.setRotationAngle(v.heading || 0) - if (v.id === focus) { - m.openPopup(); - mapRef.current?.setView(m.getLatLng()); - } - markerRef.current.push(m); - } - mapRef.current.setView(position, zoom_level); - - // render track path - console.log('track path',init_data, init_data?.trackPath); - const trackPath = L.geoJSON(init_data?.trackPath, {style: {color: 'red'}}) - trackPath.addTo(mapRef.current) - - } else { - while (markerRef.current.length > vehicles.length) { - const m = markerRef.current.pop() - if (m) { - m.remove() - } else { - break; - } - } - const max_i = vehicles.length - for (let i = 0; i < max_i; i++) { - if (i < markerRef.current.length) { - const m = markerRef.current[i] - m.setLatLng(vehicles[i].pos) - m.setPopupContent(popupContent(vehicles[i])) - m.setRotationAngle(vehicles[i].heading || 0) - if (vehicles[i].id === focus) { - m.openPopup(); - mapRef.current?.setView(m.getLatLng()); - } - // L.circle(vehicles[i].pos, {radius: 0.5, color: '#009988'}).addTo(mapRef.current); - } else { - const m = L.marker(vehicles[i].pos, { - icon: markerIcon, - rotationOrigin: "center" - }).addTo(mapRef.current); - markerRef.current.push(m); - m.bindPopup(popupContent(vehicles[i])) - m.setRotationAngle(vehicles[i].heading || 0) - if (vehicles[i].id === focus) { - m.openPopup(); - mapRef.current?.setView(m.getLatLng()); - } - } - - } - } - } - - useEffect(renderMap) - - return ( -
- ); -} - - -export default Map \ No newline at end of file diff --git a/Website/src/pages/_app.tsx b/Website/src/pages/_app.tsx new file mode 100644 index 00000000..649bd2fc --- /dev/null +++ b/Website/src/pages/_app.tsx @@ -0,0 +1,14 @@ +import BaseLayout from '@/app/components/base_layout' +import type { AppProps } from 'next/app' + +/** + * Together with _document.tsx the equivalent of magic things to get the base layout + * to apply for pages in the pages-directory. + */ +export default function MyApp({ Component, pageProps }: AppProps) { + return ( + + + + ) +} \ No newline at end of file diff --git a/Website/src/pages/_document.tsx b/Website/src/pages/_document.tsx new file mode 100644 index 00000000..9e02a4ef --- /dev/null +++ b/Website/src/pages/_document.tsx @@ -0,0 +1,15 @@ +import {Html, Head, Main, NextScript} from 'next/document' +import {inter} from "@/utils/common"; + +export default function Document() { + return ( + + + + +
+ + + + ) +} \ No newline at end of file diff --git a/Website/src/pages/logout.tsx b/Website/src/pages/logout.tsx index 4fd63249..c6b87bf8 100644 --- a/Website/src/pages/logout.tsx +++ b/Website/src/pages/logout.tsx @@ -1,10 +1,18 @@ -import Login from "@/components/login"; +import Login from "@/app/components/login"; +import "@/app/components/globals.css"; import {GetServerSideProps, InferGetServerSidePropsType} from "next"; import {deleteCookie, hasCookie} from "cookies-next"; -import RootLayout from "@/components/layout" -import {ReactElement} from "react"; +import Head from "next/head"; +import {meta_info} from "@/utils/common"; +import {FormWrapper} from "@/app/components/form"; +/** + * Executed on the server side on page load. See: https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props + * + * Will check if the token-cookie exists in the request, and if it + * exists will add a header in the response to delete the token cookie. + */ export const getServerSideProps: GetServerSideProps<{ success: boolean }> = async ({req, res,}) => { @@ -12,13 +20,13 @@ export const getServerSideProps: GetServerSideProps<{ if (hasCookie('token', { httpOnly: true, - sameSite: true, + sameSite: 'lax', req, res })) { deleteCookie('token', { httpOnly: true, - sameSite: true, + sameSite: 'lax', req, res }) success = true; @@ -29,23 +37,21 @@ export const getServerSideProps: GetServerSideProps<{ export default function Page({success}: InferGetServerSidePropsType) { return ( -
-
- {success ? (

You are logged out!

) : (

You are not logged in

)} - - - -
-
- ); -} + <> + + {/* Include the generic metadata for this page */} + {meta_info.title} + + + + {success ? (

Sie wurden ausgeloggt.

) : (

Sie waren nicht eingeloggt.

)} +

Möchten Sie sich wieder einloggen?

+ {/* Include a login-form directed at the main page to allow someone to login again. */} + +
-Page.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - + ); } diff --git a/Website/src/lib/api.website.ts b/Website/src/utils/api.website.ts similarity index 100% rename from Website/src/lib/api.website.ts rename to Website/src/utils/api.website.ts diff --git a/Website/src/utils/common.ts b/Website/src/utils/common.ts new file mode 100644 index 00000000..9ba19840 --- /dev/null +++ b/Website/src/utils/common.ts @@ -0,0 +1,8 @@ +import {Inter} from "next/font/google"; + +export const inter = Inter({ subsets: ['latin'] }) + +export const meta_info = { + title: 'RailTrail Admin Interface', + description: 'An administrative interface for the RailTrail rail vehicle management system.', +} \ No newline at end of file diff --git a/Website/src/lib/data.ts b/Website/src/utils/data.ts similarity index 96% rename from Website/src/lib/data.ts rename to Website/src/utils/data.ts index 815edf40..a0d072b8 100644 --- a/Website/src/lib/data.ts +++ b/Website/src/utils/data.ts @@ -1,3 +1,7 @@ +/** + * A collection on functions that relate to fetching data from the backend. + */ + import { AuthenticationRequest, AuthenticationResponse, @@ -5,10 +9,10 @@ import { TrackPath, Vehicle, VehicleCrU, VehicleList, VehicleListItem, VehicleTypeCrU, VehicleTypeList, VehicleTypeListItem } from "./api.website"; -import {UnauthorizedError} from "@/lib/types"; +import {UnauthorizedError} from "@/utils/types"; import 'server-only' - +/** The base path from which the webserver process can reach the backend server. */ const BACKEND_BASE_PATH = process.env['BACKEND_URI'] export const getVehicleData = async (token: string, track_id: number) => { diff --git a/Website/src/lib/helpers.ts b/Website/src/utils/helpers.ts similarity index 100% rename from Website/src/lib/helpers.ts rename to Website/src/utils/helpers.ts diff --git a/Website/src/lib/types.ts b/Website/src/utils/types.ts similarity index 100% rename from Website/src/lib/types.ts rename to Website/src/utils/types.ts