Added UI for log
This commit is contained in:
@ -22,5 +22,3 @@ bun devscripts/optimise_assets.ts threadcount
|
|||||||
bun build
|
bun build
|
||||||
bun start
|
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.
|
|
||||||
|
22
bun.lock
22
bun.lock
@ -6,10 +6,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/leaflet": "^1.9.20",
|
"@types/leaflet": "^1.9.20",
|
||||||
"@types/react-leaflet": "^3.0.0",
|
"@types/react-leaflet": "^3.0.0",
|
||||||
|
"@types/react-modal": "^3.16.3",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
|
"react-modal": "^3.16.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@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-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=="],
|
"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=="],
|
"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=="],
|
"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": ["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-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-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=="],
|
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="],
|
"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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/leaflet": "^1.9.20",
|
"@types/leaflet": "^1.9.20",
|
||||||
"@types/react-leaflet": "^3.0.0",
|
"@types/react-leaflet": "^3.0.0",
|
||||||
|
"@types/react-modal": "^3.16.3",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19",
|
||||||
"react-leaflet": "^5.0.0"
|
"react-leaflet": "^5.0.0",
|
||||||
|
"react-modal": "^3.16.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
|
108
src/App.tsx
108
src/App.tsx
@ -1,46 +1,108 @@
|
|||||||
import { MapContainer, Marker, TileLayer, Tooltip } from "react-leaflet";
|
import { MapContainer, Marker, Rectangle, TileLayer, Tooltip } from "react-leaflet";
|
||||||
import L, { marker } from "leaflet";
|
import L from "leaflet";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
import Modal from 'react-modal';
|
||||||
import "./index.css";
|
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() {
|
export function App() {
|
||||||
const [coords, set_coords] = useState({x:0, y:0})
|
const [modalIsOpen, setModalIsOpen]: StateInit<boolean> = useState(false);
|
||||||
const [next_label, set_next_label] = useState("")
|
const [coords, setCoords]: StateInit<{ x: number; y: number }> = useState({ x: 0, y: 0 })
|
||||||
const [markers, set_markers] = useState([
|
const [nextLabel, setNextLabel]: StateInit<string> = useState("")
|
||||||
{label: "Spawn", x: 0, y: 0}
|
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) => {
|
let map = useCallback((it: L.Map | null) => {
|
||||||
if (it == null) return;
|
if (it == null) return;
|
||||||
it.on('mousemove', (e) => {set_coords({x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)})})
|
it.on('mousemove', (e) => { setCoords({ 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)}]))
|
it.on('zoomend', (_) => setCurrentZoom(it.getZoom()))
|
||||||
}, [coords, markers, next_label])
|
it.on('click', (e) => setMarkers([...markers, { label: nextLabel, x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat) }]))
|
||||||
|
}, [coords, markers, nextLabel])
|
||||||
|
|
||||||
return (<>
|
return (<>{ (!modalIsOpen) ?
|
||||||
<MapContainer id="map"
|
<MapContainer
|
||||||
center={[0,0]}
|
className={(login) ? "edit" : "fullscreen"}
|
||||||
maxBounds={[[-8192,-8192], [8192,8192]]}
|
id="map"
|
||||||
|
center={[0, 0]}
|
||||||
|
maxBounds={[[-8192, -8192], [8192, 8192]]}
|
||||||
crs={L.CRS.Simple}
|
crs={L.CRS.Simple}
|
||||||
zoom={0}
|
zoom={0}
|
||||||
ref={map}>
|
ref={map}>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
url="/tiles/{z}/{x}/{y}"
|
url="/static/tiles/{z}/{x}_{y}.png"
|
||||||
tileSize={512}
|
tileSize={512}
|
||||||
minZoom={-4}
|
minZoom={-4}
|
||||||
maxNativeZoom={0}/>
|
maxNativeZoom={0} />
|
||||||
{
|
|
||||||
markers.map((it, i) => (
|
{(currentZoom > -2) ? markers.map((it, i) => (
|
||||||
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({iconUrl: "/legend/city.svg", iconAnchor: [8,8]})}>
|
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({ iconUrl: "/static/legend/city.svg", iconAnchor: [6, 6] })}>
|
||||||
<Tooltip>{it.label}</Tooltip>
|
<Tooltip>{it.label}</Tooltip>
|
||||||
</Marker>
|
</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>
|
<p>Mouse is at {coords.x}, {-coords.y}</p>
|
||||||
<label htmlFor="nextmarker">Next Marker Label</label>
|
<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;
|
export default App;
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
import { serve, file, type BunRequest } from "bun";
|
import { serve, file, type BunRequest } from "bun";
|
||||||
import index from "./index.html";
|
import index from "./index.html";
|
||||||
|
import { statSync, } from "node:fs";
|
||||||
|
|
||||||
const server = serve({
|
const server = serve({
|
||||||
routes: {
|
routes: {
|
||||||
// Serve index.html for all unmatched routes.
|
// Serve index.html for all unmatched routes.
|
||||||
"/": index,
|
"/": index,
|
||||||
"/tiles/:z/:x/:y": (req : BunRequest<"/tiles/:z/:x/:y">) => {
|
"/static/*": (req : BunRequest<"/static/*">) => {
|
||||||
const { z, x, y} = req.params;
|
const path = "." + new URL(req.url).pathname
|
||||||
return new Response(file(`./static/tiles/${z}/${x}_${y}.png`), {
|
if (statSync(path).isFile()) {
|
||||||
headers: {
|
return new Response(file(path))
|
||||||
"Content-Type": "image/png"
|
} 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}`))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16px"
|
width="12px"
|
||||||
height="16px"
|
height="12x"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 12 12"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
>
|
>
|
||||||
<circle style="fill: #000" r="8" transform="translate(8,8)" />
|
<rect style="stroke-width:2px;stroke:#000;fill:#fff" x="1" y="1" width="10" height="10"/>
|
||||||
<circle style="fill: #fff" r="6" transform="translate(8,8)" />
|
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 274 B |
Reference in New Issue
Block a user