Added UI for log

This commit is contained in:
2025-09-17 12:15:13 +02:00
parent 6e58fc2a55
commit aa5ac7f412
6 changed files with 135 additions and 55 deletions

View File

@ -22,5 +22,3 @@ bun devscripts/optimise_assets.ts threadcount
bun build
bun start
```
This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

View File

@ -6,10 +6,12 @@
"dependencies": {
"@types/leaflet": "^1.9.20",
"@types/react-leaflet": "^3.0.0",
"@types/react-modal": "^3.16.3",
"leaflet": "^1.9.4",
"react": "^19",
"react-dom": "^19",
"react-leaflet": "^5.0.0",
"react-modal": "^3.16.3",
},
"devDependencies": {
"@types/bun": "latest",
@ -35,20 +37,40 @@
"@types/react-leaflet": ["@types/react-leaflet@3.0.0", "", { "dependencies": { "react-leaflet": "*" } }, "sha512-p8R9mVKbCDDqOdW+M6GyJJuFn6q+IgDFYavFiOIvaWHuOe5kIHZEtCy1pfM43JIA6JiB3D/aDoby7C51eO+XSg=="],
"@types/react-modal": ["@types/react-modal@3.16.3", "", { "dependencies": { "@types/react": "*" } }, "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg=="],
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"exenv": ["exenv@1.2.2", "", {}, "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-leaflet": ["react-leaflet@5.0.0", "", { "dependencies": { "@react-leaflet/core": "^3.0.0" }, "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw=="],
"react-lifecycles-compat": ["react-lifecycles-compat@3.0.4", "", {}, "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="],
"react-modal": ["react-modal@3.16.3", "", { "dependencies": { "exenv": "^1.2.0", "prop-types": "^15.7.2", "react-lifecycles-compat": "^3.0.0", "warning": "^4.0.3" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" } }, "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="],
"warning": ["warning@4.0.3", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w=="],
}
}

View File

@ -11,10 +11,12 @@
"dependencies": {
"@types/leaflet": "^1.9.20",
"@types/react-leaflet": "^3.0.0",
"@types/react-modal": "^3.16.3",
"leaflet": "^1.9.4",
"react": "^19",
"react-dom": "^19",
"react-leaflet": "^5.0.0"
"react-leaflet": "^5.0.0",
"react-modal": "^3.16.3"
},
"devDependencies": {
"@types/react": "^19",

View File

@ -1,46 +1,108 @@
import { MapContainer, Marker, TileLayer, Tooltip } from "react-leaflet";
import L, { marker } from "leaflet";
import { MapContainer, Marker, Rectangle, TileLayer, Tooltip } from "react-leaflet";
import L from "leaflet";
import { useCallback, useState } from "react";
import Modal from 'react-modal';
import "./index.css";
type SetState<T> = React.Dispatch<React.SetStateAction<T>>;
type StateInit<T> = [T, SetState<T>];
function Login(params: { modal: StateInit<boolean>, login: StateInit<string | undefined>; }) {
const {modal, login} = params;
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
const openModal = () => modal[1](true)
const afterOpenModal = () => { }
const closeModal = () => modal[1](false)
return (<>
<button type="button" onClick={openModal}>Log In</button>
<Modal
isOpen={modal[0]}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Log In"
ariaHideApp={false}
>
<h2>Authentification</h2>
<form action={(e : FormData) => {
// TODO Check Auth
const l = e.get("login")
if (l == null) return;
closeModal()
login[1](l.toString())
}}>
<input name="login" type="text" placeholder="Login" required={true} /><br />
<input name="password" type="password" placeholder="Password" required={true} /><br />
<button id="auth!submit" type="submit">Log In</button>
</form>
</Modal>
</>)
}
function Toolbar(params: { logged_in: [boolean,] }) {
}
export function App() {
const [coords, set_coords] = useState({x:0, y:0})
const [next_label, set_next_label] = useState("")
const [markers, set_markers] = useState([
const [modalIsOpen, setModalIsOpen]: StateInit<boolean> = useState(false);
const [coords, setCoords]: StateInit<{ x: number; y: number }> = useState({ x: 0, y: 0 })
const [nextLabel, setNextLabel]: StateInit<string> = useState("")
const [currentZoom, setCurrentZoom]: StateInit<number> = useState(0)
const [markers, setMarkers]: StateInit<{ label: string; x: number; y: number }[]> = useState([
{ label: "Spawn", x: 0, y: 0 }
])
const [login, setLogin]: StateInit<string | undefined> = useState()
let map = useCallback((it: L.Map | null) => {
if (it == null) return;
it.on('mousemove', (e) => {set_coords({x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)})})
it.on('click', (e) => set_markers([...markers, {label: next_label, x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)}]))
}, [coords, markers, next_label])
it.on('mousemove', (e) => { setCoords({ x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat) }) })
it.on('zoomend', (_) => setCurrentZoom(it.getZoom()))
it.on('click', (e) => setMarkers([...markers, { label: nextLabel, x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat) }]))
}, [coords, markers, nextLabel])
return (<>
<MapContainer id="map"
return (<>{ (!modalIsOpen) ?
<MapContainer
className={(login) ? "edit" : "fullscreen"}
id="map"
center={[0, 0]}
maxBounds={[[-8192, -8192], [8192, 8192]]}
crs={L.CRS.Simple}
zoom={0}
ref={map}>
<TileLayer
url="/tiles/{z}/{x}/{y}"
url="/static/tiles/{z}/{x}_{y}.png"
tileSize={512}
minZoom={-4}
maxNativeZoom={0} />
{
markers.map((it, i) => (
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({iconUrl: "/legend/city.svg", iconAnchor: [8,8]})}>
{(currentZoom > -2) ? markers.map((it, i) => (
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({ iconUrl: "/static/legend/city.svg", iconAnchor: [6, 6] })}>
<Tooltip>{it.label}</Tooltip>
</Marker>
))
)) : null
}
</MapContainer>
<Rectangle bounds={[[-8000, 8000], [8000, 8000]]} pathOptions={{ color: "#000", stroke: true, fill: false, weight: 1 }} />
</MapContainer> : <></> } {
(login) ?
<>
<p>Mouse is at {coords.x}, {-coords.y}</p>
<label htmlFor="nextmarker">Next Marker Label</label>
<input id="nextmarker" type="text" onChange={(e) => set_next_label(e.target.value)} value={next_label} />
</>)
<input id="nextmarker" type="text" onChange={(e) => setNextLabel(e.target.value)} value={nextLabel} />
<p>Hello {login}!</p>
<button type="button" onClick={() => setLogin(undefined)}>Log out</button>
</>
: <Login login={[login,setLogin]} modal={[modalIsOpen, setModalIsOpen]} />}
</>)
}
export default App;

View File

@ -1,21 +1,18 @@
import { serve, file, type BunRequest } from "bun";
import index from "./index.html";
import { statSync, } from "node:fs";
const server = serve({
routes: {
// Serve index.html for all unmatched routes.
"/": index,
"/tiles/:z/:x/:y": (req : BunRequest<"/tiles/:z/:x/:y">) => {
const { z, x, y} = req.params;
return new Response(file(`./static/tiles/${z}/${x}_${y}.png`), {
headers: {
"Content-Type": "image/png"
"/static/*": (req : BunRequest<"/static/*">) => {
const path = "." + new URL(req.url).pathname
if (statSync(path).isFile()) {
return new Response(file(path))
} else {
return new Response(`${path} not found`, {status: 404})
}
})
},
"/legend/:item": (req: BunRequest<"/legend/:item">) => {
const {item} = req.params
return new Response(file(`./static/legend/${item}`))
}
},

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16px"
height="16px"
viewBox="0 0 16 16"
width="12px"
height="12x"
viewBox="0 0 12 12"
version="1.1"
>
<circle style="fill: #000" r="8" transform="translate(8,8)" />
<circle style="fill: #fff" r="6" transform="translate(8,8)" />
<rect style="stroke-width:2px;stroke:#000;fill:#fff" x="1" y="1" width="10" height="10"/>
</svg>

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 274 B