108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
//
|
|
// Copyright 2026 James Pace
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
// defined by the Mozilla Public License, v. 2.0.
|
|
//
|
|
import { AppNav, Footer } from "./AppNav.tsx";
|
|
import { Container, Row, Col, Image, Card } from "react-bootstrap";
|
|
import { atom, useAtomValue } from "jotai";
|
|
import { atomWithQuery } from "jotai-tanstack-query";
|
|
|
|
const costmapImageQuery = async () => {
|
|
const resp = await fetch("api/costmap_image");
|
|
if (!resp.ok) {
|
|
throw new Error("Network response was not ok");
|
|
}
|
|
const blob = await resp.blob();
|
|
return URL.createObjectURL(blob);
|
|
};
|
|
const costmapImageQueryAtom = atomWithQuery(() => ({
|
|
queryKey: ["costmap_image"],
|
|
queryFn: costmapImageQuery,
|
|
refetchInterval: 1000, // 1s
|
|
}));
|
|
const costmapImageAtom = atom((get) => {
|
|
const response = get(costmapImageQueryAtom);
|
|
if (response.isPending) {
|
|
return <p>"Loading..."</p>;
|
|
}
|
|
if (response.isError) {
|
|
return <p>"Error loading!"</p>;
|
|
}
|
|
const costmapStyle = {
|
|
minHeight: "65svb",
|
|
};
|
|
return <Image src={response.data} fluid style={costmapStyle} />;
|
|
});
|
|
|
|
const statusStringQuery = async () => {
|
|
const resp = await fetch("api/status");
|
|
if (!resp.ok) {
|
|
throw new Error("Network response was not ok");
|
|
}
|
|
return resp.json();
|
|
};
|
|
const statusStringQueryAtom = atomWithQuery(() => ({
|
|
queryKey: ["status_string"],
|
|
queryFn: statusStringQuery,
|
|
refetchInterval: 500, // 0.5s
|
|
}));
|
|
const statusStringAtom = atom((get) => {
|
|
const response = get(statusStringQueryAtom);
|
|
if (response.isPending || response.isError || !response.data.status) {
|
|
return "N/A";
|
|
}
|
|
return response.data.message;
|
|
});
|
|
|
|
const latLongQuery = async () => {
|
|
const resp = await fetch("api/position");
|
|
if (!resp.ok) {
|
|
throw new Error("Network response was not ok");
|
|
}
|
|
return resp.json();
|
|
};
|
|
const latLongQueryAtom = atomWithQuery(() => ({
|
|
queryKey: ["latlong"],
|
|
queryFn: latLongQuery,
|
|
refetchInterval: 500, // 0.5s
|
|
}));
|
|
const latLongAtom = atom((get) => {
|
|
const response = get(latLongQueryAtom);
|
|
if (response.isPending || response.isError || !response.data.status) {
|
|
return "N/A";
|
|
}
|
|
return `(${response.data.latitude}, ${response.data.longitude})`;
|
|
});
|
|
|
|
export function Autonomy() {
|
|
const costmapImage = useAtomValue(costmapImageAtom);
|
|
const statusString = useAtomValue(statusStringAtom);
|
|
const latLong = useAtomValue(latLongAtom);
|
|
|
|
return (
|
|
<div>
|
|
<AppNav />
|
|
<Container className="vert-padded">
|
|
<Card className="padded">
|
|
<Row>
|
|
<Col lg={8}>{costmapImage}</Col>
|
|
<Col lg={4}>
|
|
<p>Status:</p>
|
|
<pre>{statusString}</pre>
|
|
<p>Latitude, Longitude:</p>
|
|
<pre>{latLong}</pre>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
</Container>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|