From 94711bb78d5c7164edfd1a4c75dc2e9dfdc40a7e Mon Sep 17 00:00:00 2001 From: Eugene Fox Date: Fri, 23 Feb 2024 13:06:16 +0000 Subject: [PATCH] - Implemented actual API for frontend - Reorganized and refactored frontend project --- frontend/.env | 1 + frontend/.eslintrc.cjs | 2 +- frontend/Dockerfile | 4 + frontend/package.json | 1 + frontend/src/App.tsx | 93 ++++++++++++---- frontend/src/Components/TrackChart.tsx | 15 +-- frontend/src/Components/TrackLinePlot.tsx | 2 +- frontend/src/Data/Api/ApiEndpoints.ts | 16 +++ .../Api/Contracts/Point/IGetPointsResponse.ts | 9 ++ .../Api/Contracts/Point/UpsertPointRequest.ts | 17 +++ .../Api/Contracts/Track/UpsertTrackRequest.ts | 29 +++++ frontend/src/Data/{ => Api/Models}/IPoint.ts | 0 frontend/src/Data/{ => Api/Models}/ITrack.ts | 0 .../src/Data/{ => Api/Models}/MaxSpeed.ts | 0 frontend/src/Data/{ => Api/Models}/Surface.ts | 0 frontend/src/Data/Api/Points.ts | 105 ++++++++++++++++++ frontend/src/Data/Api/Tracks.ts | 87 +++++++++++++++ frontend/src/Data/GaussianGenerator.ts | 48 ++++++++ frontend/src/Data/IChartPoint.ts | 4 +- frontend/src/Data/LoadMockData.ts | 51 --------- frontend/src/Data/MockDataGenerator.ts | 49 ++++++++ frontend/src/Data/TrackChartDataProps.ts | 4 +- frontend/yarn.lock | 12 ++ 23 files changed, 459 insertions(+), 90 deletions(-) create mode 100644 frontend/.env create mode 100644 frontend/src/Data/Api/ApiEndpoints.ts create mode 100644 frontend/src/Data/Api/Contracts/Point/IGetPointsResponse.ts create mode 100644 frontend/src/Data/Api/Contracts/Point/UpsertPointRequest.ts create mode 100644 frontend/src/Data/Api/Contracts/Track/UpsertTrackRequest.ts rename frontend/src/Data/{ => Api/Models}/IPoint.ts (100%) rename frontend/src/Data/{ => Api/Models}/ITrack.ts (100%) rename frontend/src/Data/{ => Api/Models}/MaxSpeed.ts (100%) rename frontend/src/Data/{ => Api/Models}/Surface.ts (100%) create mode 100644 frontend/src/Data/Api/Points.ts create mode 100644 frontend/src/Data/Api/Tracks.ts create mode 100644 frontend/src/Data/GaussianGenerator.ts delete mode 100644 frontend/src/Data/LoadMockData.ts create mode 100644 frontend/src/Data/MockDataGenerator.ts diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..640f2bd --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:5152 diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 6f3226f..f52dc17 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -32,7 +32,7 @@ module.exports = { "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unnecessary-type-assertion": "warn", "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unused-vars": "warn" }, parserOptions: { ecmaVersion: "latest", diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b6f3733..4b16f66 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,6 +1,8 @@ # Use the official Node.js 20 image as the base image FROM node:20 as builder +ARG API_URL=http://localhost:5152 + # Set the working directory inside the container WORKDIR /app @@ -13,6 +15,8 @@ RUN yarn install # Copy the app source code to the working directory COPY . . +RUN echo "VITE_API_URL=${API_URL}" > .env.production + # Build the app RUN yarn build diff --git a/frontend/package.json b/frontend/package.json index 1426f20..105acd5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@types/d3-scale": "^4.0.8", + "@types/node": "^20.11.20", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 25c4114..b8fd832 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,9 +4,10 @@ import { useEffect, useState } from "react"; import useStyles from "./App.styles"; import ChartSkeleton from "./Components/ChartSkeleton"; import TrackChart from "./Components/TrackChart"; -import IPoint from "./Data/IPoint"; -import ITrack from "./Data/ITrack"; -import LoadMockData from "./Data/LoadMockData"; +import ApiEndpoints from "./Data/Api/ApiEndpoints"; +import IPoint from "./Data/Api/Models/IPoint"; +import ITrack from "./Data/Api/Models/ITrack"; +import { GeneratePoints, GenerateTracks } from "./Data/MockDataGenerator"; const theme: Theme = createTheme(); @@ -20,25 +21,68 @@ function App(): JSX.Element const [zoom, setZoom] = useState([0, 1]); const sx = useStyles(); - const loadData = () => - { - setLoading(true); - console.log("Loading data..."); - const newData = LoadMockData(true); - setData(newData); - setZoom([0, Math.min(newData.tracks.length + 1, 20)]); - - new Promise(resolve => setTimeout(resolve, 1000)) - .then(() => setLoading(false)) - .catch(console.error); - }; - useEffect(() => { - loadData(); + void LoadDataAsync(); }, []); - const handleZoomChange = (_: unknown, newValue: number | number[]) => + async function LoadDataAsync(): Promise + { + setLoading(true); + const { Points, Tracks } = new ApiEndpoints(); + + try + { + const tracks: ITrack[] = await Tracks.GetAllTracksAsync(); + let points: IPoint[] = []; + + if (tracks.length > 0) + points = await Points.GetPointsArrayAsync([ + ...tracks.map(t => t.firstId), + tracks[tracks.length - 1].secondId + ]); + + setData({ tracks, points }); + setZoom([0, Math.min(tracks.length + 1, 20)]); + } + catch (error) + { + console.error("Failed to load data:", error); + } + finally + { + console.log("Data loaded") + setLoading(false); + } + } + + async function RecreateDataAsync(): Promise + { + setLoading(true); + const { Points, Tracks } = new ApiEndpoints(); + + try + { + const points: IPoint[] = GeneratePoints(120); + const tracks: ITrack[] = GenerateTracks(points); + + await Points.ImportPointsAsync(points); + await Tracks.ImportTracksAsync(tracks); + + await LoadDataAsync(); + } + catch (error) + { + console.error("Failed to recreate data:", error); + } + finally + { + console.log("Data recreated") + setLoading(false); + } + } + + function OnZoomChange(_: unknown, newValue: number | number[]): void { const value: number[] = newValue as number[]; @@ -46,10 +90,13 @@ function App(): JSX.Element return; if (value[1] - value[0] > 50) + { setLoading(true); + setTimeout(() => setLoading(false), 500); + } setZoom(value); - }; + } return ( @@ -58,21 +105,21 @@ function App(): JSX.Element setLoading(false) } /> + zoom={ zoom } /> { !isLoading && } diff --git a/frontend/src/Components/TrackChart.tsx b/frontend/src/Components/TrackChart.tsx index 19c0536..61ba1c7 100644 --- a/frontend/src/Components/TrackChart.tsx +++ b/frontend/src/Components/TrackChart.tsx @@ -1,10 +1,10 @@ import { SxProps } from "@mui/system"; import * as xc from "@mui/x-charts"; import { useEffect, useState } from "react"; +import IPoint from "../Data/Api/Models/IPoint"; +import MaxSpeed from "../Data/Api/Models/MaxSpeed"; import IChartPoint from "../Data/IChartPoint"; -import IPoint from "../Data/IPoint"; -import ITrack from "../Data/ITrack"; -import MaxSpeed from "../Data/MaxSpeed"; +import ITrack from "../Data/Api/Models/ITrack"; import { TooltipProps, TracklineSeries } from "../Data/TrackChartDataProps"; import CartesianGrid from "./CartesianGrid"; import TrackLinePlot from "./TrackLinePlot"; @@ -18,14 +18,12 @@ interface IProps points: IPoint[]; /** The zoom levels (start, end). */ zoom: number[]; - /** A callback for when the processing is complete. */ - onProcessingComplete?: () => void; } /** * A chart of the track. */ -function TrackChart({ tracks, points, zoom, ...props }: IProps): JSX.Element +function TrackChart({ tracks, points, zoom }: IProps): JSX.Element { const [dataset, setDataset] = useState([]); const [xTicks, setXTicks] = useState([]); @@ -84,10 +82,7 @@ function TrackChart({ tracks, points, zoom, ...props }: IProps): JSX.Element setXTicks(data.map(i => i.distance)); console.warn("Reflow!"); - - setTimeout(() => props.onProcessingComplete?.(), 500); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tracks, points, props.onProcessingComplete]); + }, [tracks, points]); useEffect(() => { diff --git a/frontend/src/Components/TrackLinePlot.tsx b/frontend/src/Components/TrackLinePlot.tsx index 5618aa6..3b5b9fe 100644 --- a/frontend/src/Components/TrackLinePlot.tsx +++ b/frontend/src/Components/TrackLinePlot.tsx @@ -2,7 +2,7 @@ import { SxProps } from "@mui/system"; import { LinePlot, useDrawingArea, useXScale } from "@mui/x-charts"; import { ScaleLinear } from "d3-scale"; import { useEffect } from "react"; -import MaxSpeed from "../Data/MaxSpeed"; +import MaxSpeed from "../Data/Api/Models/MaxSpeed"; import IChartPoint from "../Data/IChartPoint"; // Remarks: diff --git a/frontend/src/Data/Api/ApiEndpoints.ts b/frontend/src/Data/Api/ApiEndpoints.ts new file mode 100644 index 0000000..4e85af6 --- /dev/null +++ b/frontend/src/Data/Api/ApiEndpoints.ts @@ -0,0 +1,16 @@ +import Points from "./Points"; +import Tracks from "./Tracks"; + +const apiUrl: string = import.meta.env.VITE_API_URL as string; + +export default class ApiEndpoints +{ + public Points: Points; + public Tracks: Tracks; + + constructor() + { + this.Points = new Points(apiUrl); + this.Tracks = new Tracks(apiUrl); + } +} diff --git a/frontend/src/Data/Api/Contracts/Point/IGetPointsResponse.ts b/frontend/src/Data/Api/Contracts/Point/IGetPointsResponse.ts new file mode 100644 index 0000000..0a2fbca --- /dev/null +++ b/frontend/src/Data/Api/Contracts/Point/IGetPointsResponse.ts @@ -0,0 +1,9 @@ +import IPoint from "../../Models/IPoint"; + +export default interface IGetPointsResponse +{ + points: IPoint[], + totalCount: number, + count: number, + page: number +} diff --git a/frontend/src/Data/Api/Contracts/Point/UpsertPointRequest.ts b/frontend/src/Data/Api/Contracts/Point/UpsertPointRequest.ts new file mode 100644 index 0000000..d931306 --- /dev/null +++ b/frontend/src/Data/Api/Contracts/Point/UpsertPointRequest.ts @@ -0,0 +1,17 @@ +export default class UpsertPointRequest +{ + public name: string; + public height: number; + + constructor(name: string, height: number) + { + if (name.length < 1) + throw new Error("Name must be at least 1 character long"); + + if (height !== Math.floor(height)) + throw new Error("Height must be an integer"); + + this.name = name; + this.height = height; + } +} diff --git a/frontend/src/Data/Api/Contracts/Track/UpsertTrackRequest.ts b/frontend/src/Data/Api/Contracts/Track/UpsertTrackRequest.ts new file mode 100644 index 0000000..dd81805 --- /dev/null +++ b/frontend/src/Data/Api/Contracts/Track/UpsertTrackRequest.ts @@ -0,0 +1,29 @@ +import MaxSpeed from "../../Models/MaxSpeed"; +import Surface from "../../Models/Surface"; + +export default class UpsertTrackRequest +{ + public firstId: number; + public secondId: number; + public distance: number; + public surface: Surface; + public maxSpeed: MaxSpeed; + + constructor(firstId: number, secondId: number, distance: number, surface: number, maxSpeed: number) + { + if (firstId < 0) + throw new Error("First ID must be at least 0"); + + if (secondId < 0) + throw new Error("Second ID must be at least 0"); + + if (distance < 1) + throw new Error("Distance must be greater than 0"); + + this.firstId = firstId; + this.secondId = secondId; + this.distance = distance; + this.surface = surface; + this.maxSpeed = maxSpeed; + } +} diff --git a/frontend/src/Data/IPoint.ts b/frontend/src/Data/Api/Models/IPoint.ts similarity index 100% rename from frontend/src/Data/IPoint.ts rename to frontend/src/Data/Api/Models/IPoint.ts diff --git a/frontend/src/Data/ITrack.ts b/frontend/src/Data/Api/Models/ITrack.ts similarity index 100% rename from frontend/src/Data/ITrack.ts rename to frontend/src/Data/Api/Models/ITrack.ts diff --git a/frontend/src/Data/MaxSpeed.ts b/frontend/src/Data/Api/Models/MaxSpeed.ts similarity index 100% rename from frontend/src/Data/MaxSpeed.ts rename to frontend/src/Data/Api/Models/MaxSpeed.ts diff --git a/frontend/src/Data/Surface.ts b/frontend/src/Data/Api/Models/Surface.ts similarity index 100% rename from frontend/src/Data/Surface.ts rename to frontend/src/Data/Api/Models/Surface.ts diff --git a/frontend/src/Data/Api/Points.ts b/frontend/src/Data/Api/Points.ts new file mode 100644 index 0000000..3dba70b --- /dev/null +++ b/frontend/src/Data/Api/Points.ts @@ -0,0 +1,105 @@ +import IGetPointsResponse from "./Contracts/Point/IGetPointsResponse"; +import UpsertPointRequest from "./Contracts/Point/UpsertPointRequest"; +import IPoint from "./Models/IPoint"; + +export default class Points +{ + private apiUrl: string; + + constructor(apiUrl: string) + { + this.apiUrl = apiUrl; + } + + public async CreatePointAsync(request: UpsertPointRequest): Promise + { + const response: Response = await fetch(this.apiUrl + "/points", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(request) + }); + + const data: IPoint = await response.json() as IPoint; + + return data; + } + + public async GetPointsArrayAsync(ids: number[]): Promise + { + const response: Response = await fetch(this.apiUrl + "/points/array", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(ids) + }); + + const data: IPoint[] = await response.json() as IPoint[]; + + return data; + } + + public async GetPointsAsync(page?: number, count?: number): Promise + { + const params = new URLSearchParams(); + + if (page) + params.append("page", page.toString()); + + if (count) + params.append("count", count.toString()); + + const response: Response = await fetch(this.apiUrl + `/points?${params.toString()}`); + const data: IGetPointsResponse = await response.json() as IGetPointsResponse; + + return data; + } + + public async GetPointAsync(id: number): Promise + { + const response: Response = await fetch(this.apiUrl + `/points/${id}`); + const data: IPoint = await response.json() as IPoint; + + return data; + } + + public async UpsertPointAsync(id: number, request: UpsertPointRequest): Promise + { + const response: Response = await fetch(this.apiUrl + `/points/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(request) + }); + + if (response.status === 204) + return null; + + const data: IPoint = await response.json() as IPoint; + + return data; + } + + public async DeletePointAsync(id: number): Promise + { + await fetch(this.apiUrl + `/points/${id}`, { method: "DELETE" }); + } + + public async ImportPointsAsync(points: IPoint[]): Promise + { + const response: Response = await fetch(this.apiUrl + "/points/import", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(points) + }); + + const data: IPoint[] = await response.json() as IPoint[]; + + return data; + } +} diff --git a/frontend/src/Data/Api/Tracks.ts b/frontend/src/Data/Api/Tracks.ts new file mode 100644 index 0000000..f7bb5d3 --- /dev/null +++ b/frontend/src/Data/Api/Tracks.ts @@ -0,0 +1,87 @@ +import UpsertTrackRequest from "./Contracts/Track/UpsertTrackRequest"; +import ITrack from "./Models/ITrack"; + +export default class Tracks +{ + private apiUrl: string; + + constructor(apiUrl: string) + { + this.apiUrl = apiUrl; + } + + public async CreateTrackAsync(request: UpsertTrackRequest): Promise + { + const response: Response = await fetch(this.apiUrl + "/tracks", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(request) + }); + + const data: ITrack = await response.json() as ITrack; + + return data; + } + + public async GetTrackAsync(firstId: number, secondId: number): Promise + { + const response: Response = await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`); + const data: ITrack = await response.json() as ITrack; + + return data; + } + + public async GetAllTracksAsync(): Promise + { + const response: Response = await fetch(this.apiUrl + "/tracks"); + const data: ITrack[] = await response.json() as ITrack[]; + + return data; + } + + public async UpsertTrackAsync( + firstId: number, + secondId: number, + request: UpsertTrackRequest + ): Promise + { + const response: Response = await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(request) + }); + + if (response.status === 204) + return null; + + const data: ITrack = await response.json() as ITrack; + + return data; + } + + public async DeleteTrackAsync(firstId: number, secondId: number): Promise + { + await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`, { + method: "DELETE" + }); + } + + public async ImportTracksAsync(tracks: ITrack[]): Promise + { + const response: Response = await fetch(this.apiUrl + "/tracks/import", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(tracks) + }); + + const data: ITrack[] = await response.json() as ITrack[]; + + return data; + } +} diff --git a/frontend/src/Data/GaussianGenerator.ts b/frontend/src/Data/GaussianGenerator.ts new file mode 100644 index 0000000..55e7178 --- /dev/null +++ b/frontend/src/Data/GaussianGenerator.ts @@ -0,0 +1,48 @@ +/** + * A class that generates random numbers from a Gaussian distribution. + */ +export default class GaussianGenerator +{ + private mean: number; + private stdDev: number; + + /** + * Constructs a GaussianGenerator object with the specified mean and standard deviation. + * @param mean The mean of the Gaussian distribution. + * @param stdDev The standard deviation of the Gaussian distribution. + */ + constructor(mean: number, stdDev: number) + { + this.mean = mean; + this.stdDev = stdDev; + } + + /** + * Generates the next random number from the Gaussian distribution. + * @returns The next random number from the Gaussian distribution. + */ + NextGaussian(): number + { + let u1 = 0, u2 = 0; + while (u1 === 0) u1 = Math.random(); // Converting [0,1) to (0,1) + while (u2 === 0) u2 = Math.random(); + const randStdNormal = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); + const randNormal = this.mean + this.stdDev * randStdNormal; // random normal(mean,stdDev^2) + return Math.floor(randNormal); + } + + /** + * Generates an array of random numbers from the Gaussian distribution. + * @param n The number of random numbers to generate. + * @returns An array of random numbers from the Gaussian distribution. + */ + Generate(n: number): number[] + { + const arr = []; + for (let i = 0; i < n; i++) + { + arr.push(this.NextGaussian()); + } + return arr; + } +} diff --git a/frontend/src/Data/IChartPoint.ts b/frontend/src/Data/IChartPoint.ts index f7c86b1..ed071f4 100644 --- a/frontend/src/Data/IChartPoint.ts +++ b/frontend/src/Data/IChartPoint.ts @@ -1,5 +1,5 @@ -import MaxSpeed from "./MaxSpeed"; -import Surface from "./Surface"; +import MaxSpeed from "./Api/Models/MaxSpeed"; +import Surface from "./Api/Models/Surface"; /** Represents an aggregated point on the chart. */ export default interface IChartPoint diff --git a/frontend/src/Data/LoadMockData.ts b/frontend/src/Data/LoadMockData.ts deleted file mode 100644 index fb8190c..0000000 --- a/frontend/src/Data/LoadMockData.ts +++ /dev/null @@ -1,51 +0,0 @@ -import IPoint from "./IPoint"; -import ITrack from "./ITrack"; - -/** - * Returns a random number between min and max - * @param min The minimum value (inclusive) - * @param max The maximum value (exclusive) - * @returns A random number between min and max - */ -const getRandom = (min: number, max: number): number => - Math.floor(Math.random() * (max - min)) + min; - -const pointNames: string[] = - [ - "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", - "Juliett", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", - "Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu" - ]; - -function LoadMockData(largeCount?: boolean): { tracks: ITrack[], points: IPoint[]; } -{ - const count: number = getRandom(10, 20) * (largeCount ? 10 : 1); - const points: IPoint[] = []; - const tracks: ITrack[] = []; - - // Generate random data - - for (let i = 0; i < count; i++) - { - points.push({ - id: i, - name: `${pointNames[getRandom(0, pointNames.length)]}-${i}`, - height: getRandom(50, 200) - }); - } - - for (let i = 0; i < count - 1; i++) - { - tracks.push({ - firstId: points[i].id, - secondId: points[i + 1].id, - distance: getRandom(1000, 2000), - surface: getRandom(0, 3), - maxSpeed: getRandom(0, 3) - }); - } - - return { tracks, points }; -} - -export default LoadMockData; diff --git a/frontend/src/Data/MockDataGenerator.ts b/frontend/src/Data/MockDataGenerator.ts new file mode 100644 index 0000000..7363c6f --- /dev/null +++ b/frontend/src/Data/MockDataGenerator.ts @@ -0,0 +1,49 @@ +import IPoint from "./Api/Models/IPoint"; +import ITrack from "./Api/Models/ITrack"; +import MaxSpeed from "./Api/Models/MaxSpeed"; +import Surface from "./Api/Models/Surface"; +import GaussianGenerator from "./GaussianGenerator"; + +export function GeneratePoints(count: number): IPoint[] +{ + const generator = new GaussianGenerator(160, 10); + + const points: IPoint[] = generator.Generate(count).map((height, index) => ({ + id: index + 1, + name: `${pointNames[index % pointNames.length]}-${index + 1}`, + height + })); + + return points; +} + +export function GenerateTracks(points: IPoint[]): ITrack[] +{ + const generator = new GaussianGenerator(2000, 500); + + const tracks: ITrack[] = generator.Generate(points.length - 1).map((distance, index) => ({ + firstId: points[index].id, + secondId: points[index + 1].id, + distance, + surface: getRandom(0, 3) as Surface, + maxSpeed: getRandom(0, 3) as MaxSpeed + })); + + return tracks; +} + +/** + * Returns a random number between min and max + * @param min The minimum value (inclusive) + * @param max The maximum value (exclusive) + * @returns A random number between min and max + */ +const getRandom = (min: number, max: number): number => + Math.floor(Math.random() * (max - min)) + min; + +const pointNames: string[] = + [ + "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", + "Juliett", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", + "Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu" + ]; diff --git a/frontend/src/Data/TrackChartDataProps.ts b/frontend/src/Data/TrackChartDataProps.ts index 71184a1..1abd0c5 100644 --- a/frontend/src/Data/TrackChartDataProps.ts +++ b/frontend/src/Data/TrackChartDataProps.ts @@ -1,8 +1,8 @@ import { green, grey, orange, red, yellow } from "@mui/material/colors"; import { AllSeriesType, ChartsAxisContentProps } from "@mui/x-charts"; +import MaxSpeed from "./Api/Models/MaxSpeed"; +import Surface from "./Api/Models/Surface"; import IChartPoint from "./IChartPoint"; -import MaxSpeed from "./MaxSpeed"; -import Surface from "./Surface"; /** Props for rendering trackline segments. */ export const TracklineSeries: AllSeriesType[] = [ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 3ff2b82..f09e4b0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -859,6 +859,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/node@^20.11.20": + version "20.11.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659" + integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg== + dependencies: + undici-types "~5.26.4" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -2906,6 +2913,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"