From 6e58fc2a558f8caa1c80b127ad9ba13456791b3e Mon Sep 17 00:00:00 2001 From: Annwan Date: Mon, 15 Sep 2025 15:44:59 +0200 Subject: [PATCH] restarted with less bloat --- .gitignore | 38 ++++++++++++++++++++++++ README.md | 26 +++++++++++++++++ bun-env.d.ts | 17 +++++++++++ bun.lock | 54 +++++++++++++++++++++++++++++++++++ bunfig.toml | 2 ++ devscripts/generate_tiles.ts | 29 +++++++++++++++++++ devscripts/optimise_assets.ts | 25 ++++++++++++++++ package.json | 24 ++++++++++++++++ src/App.tsx | 46 +++++++++++++++++++++++++++++ src/frontend.tsx | 26 +++++++++++++++++ src/index.css | 5 ++++ src/index.html | 15 ++++++++++ src/index.tsx | 31 ++++++++++++++++++++ static/legend/city.svg | 11 +++++++ tsconfig.json | 36 +++++++++++++++++++++++ 15 files changed, 385 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bun-env.d.ts create mode 100644 bun.lock create mode 100644 bunfig.toml create mode 100644 devscripts/generate_tiles.ts create mode 100644 devscripts/optimise_assets.ts create mode 100644 package.json create mode 100644 src/App.tsx create mode 100644 src/frontend.tsx create mode 100644 src/index.css create mode 100644 src/index.html create mode 100644 src/index.tsx create mode 100644 static/legend/city.svg create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cecf7e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# dependencies (bun install) +node_modules + +# big data +/assets +/static/tiles + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab800a7 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Ŋəmap + +To setup dependencies: +```bash +# Grab the latest jxl base map, it’s path is hereafter `themap` +bun install +bun devscripts/generate_tiles.ts themap threadcount +# This will take a while and a lot ram of per thread. If threadcount is not set, default is 8 (uses ~20GB of ram in my tests) +``` + +To start a development server: + +```bash +bun dev +``` + +To run for production: + +```bash +bun devscripts/optimise_assets.ts threadcount +# Will take a bit once again, but not as much ram, set threadcount to around the number of CPU threads you have, if not set default is 16 threads +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. diff --git a/bun-env.d.ts b/bun-env.d.ts new file mode 100644 index 0000000..72f1c26 --- /dev/null +++ b/bun-env.d.ts @@ -0,0 +1,17 @@ +// Generated by `bun init` + +declare module "*.svg" { + /** + * A path to the SVG file + */ + const path: `${string}.svg`; + export = path; +} + +declare module "*.module.css" { + /** + * A record of class names to their corresponding CSS module classes + */ + const classes: { readonly [key: string]: string }; + export = classes; +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..ee0162f --- /dev/null +++ b/bun.lock @@ -0,0 +1,54 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "bun-react-template", + "dependencies": { + "@types/leaflet": "^1.9.20", + "@types/react-leaflet": "^3.0.0", + "leaflet": "^1.9.4", + "react": "^19", + "react-dom": "^19", + "react-leaflet": "^5.0.0", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^19", + "@types/react-dom": "^19", + }, + }, + }, + "packages": { + "@react-leaflet/core": ["@react-leaflet/core@3.0.0", "", { "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ=="], + + "@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/leaflet": ["@types/leaflet@1.9.20", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw=="], + + "@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="], + + "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="], + + "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], + + "@types/react-leaflet": ["@types/react-leaflet@3.0.0", "", { "dependencies": { "react-leaflet": "*" } }, "sha512-p8R9mVKbCDDqOdW+M6GyJJuFn6q+IgDFYavFiOIvaWHuOe5kIHZEtCy1pfM43JIA6JiB3D/aDoby7C51eO+XSg=="], + + "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=="], + + "leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="], + + "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-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=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="], + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..9819bf6 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[serve.static] +env = "BUN_PUBLIC_*" \ No newline at end of file diff --git a/devscripts/generate_tiles.ts b/devscripts/generate_tiles.ts new file mode 100644 index 0000000..db3a334 --- /dev/null +++ b/devscripts/generate_tiles.ts @@ -0,0 +1,29 @@ +import { $, sleep, spawn } from "bun"; + +let path = process.argv[2]; +let cur_procs : number = 0; +const max_procs = (process.argv.length >= 4 && process.argv[3] != undefined) ? +process.argv[3] : 8 + +await $`mkdir -p /tmp/nguhmap` +for (let zoomlevel = 0; zoomlevel <= 4; ++zoomlevel) { + let size = 1024 << zoomlevel + console.log(`Resizing image for zoomlevel ${zoomlevel}: ${size}x${size}`) + await $`magick ${path} -resize ${size}x${size} /tmp/nguhmap/${zoomlevel - 4}.png` + let file = `/tmp/nguhmap/${zoomlevel - 4}` + console.log(`Cutting tiles for zoomlevel ${zoomlevel - 4}`) + await $`mkdir -p src/tiles/${zoomlevel - 4}` + for (let rx = 0; rx < 1 << (zoomlevel + 1); rx++) { + for (let ry = 0; ry < 1 << (zoomlevel + 1) ; ry++) { + while (cur_procs >= max_procs) {await sleep(200)} + cur_procs++ + console.log(`region: ${rx - (1 << zoomlevel)}/${ry - (1 << zoomlevel)}`) + spawn( + [`magick`, `${file}.png`, `-crop`, `512x512+${rx*512}+${ry*512}`, `src/tiles/${zoomlevel - 4}/${rx - (1 << zoomlevel)}_${ry - (1 << zoomlevel)}.png`], + { + onExit: () => {cur_procs--} + }) + } + } +} +while (cur_procs > 0) {await sleep(1000)} +await $`rm -rf /tmp/nguhmap` diff --git a/devscripts/optimise_assets.ts b/devscripts/optimise_assets.ts new file mode 100644 index 0000000..54ba03e --- /dev/null +++ b/devscripts/optimise_assets.ts @@ -0,0 +1,25 @@ +import { sleep, spawn } from "bun"; +import { readdir } from "node:fs/promises" +let cur_subprocs = 0; +const max_subprocs = (process.argv.length >= 3 && process.argv[2] != undefined) ? +process.argv[2] : 8 +const assets_dir = "./static/" +let assets_files = (await readdir(assets_dir, {recursive: true})).map(it => assets_dir + it) +console.log(assets_files) +for (const index in assets_files) +{ + const file = assets_files[index] + if (file == undefined) continue; + if (file.match(/\.png$/)) { + while (cur_subprocs >= max_subprocs) {await sleep(200)} + cur_subprocs++ + console.log(`optimizing ${file} (${index} / ${assets_files.length}): ${cur_subprocs} processes running`) + spawn(["optipng", "-o7", file], { + onExit: () => {cur_subprocs--}, + stdout: "ignore", + stderr: "ignore" + }) + } else { + console.log(`Ignoring file ${file}: not an optimisable filetype`) + } +} +while (cur_subprocs > 0) {await sleep(1000)} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7da543 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "bun-react-template", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/index.tsx", + "build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'", + "start": "NODE_ENV=production bun src/index.tsx" + }, + "dependencies": { + "@types/leaflet": "^1.9.20", + "@types/react-leaflet": "^3.0.0", + "leaflet": "^1.9.4", + "react": "^19", + "react-dom": "^19", + "react-leaflet": "^5.0.0" + }, + "devDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/bun": "latest" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..847a12d --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,46 @@ +import { MapContainer, Marker, TileLayer, Tooltip } from "react-leaflet"; +import L, { marker } from "leaflet"; +import { useCallback, useState } from "react"; +import "./index.css"; + +export function App() { + const [coords, set_coords] = useState({x:0, y:0}) + const [next_label, set_next_label] = useState("") + const [markers, set_markers] = useState([ + {label: "Spawn", x: 0, y: 0} + ]) + + 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]) + + return (<> + + + { + markers.map((it, i) => ( + + {it.label} + + )) + } + +

Mouse is at {coords.x}, {-coords.y}

+ + set_next_label(e.target.value)} value={next_label} /> + ) + +} + +export default App; diff --git a/src/frontend.tsx b/src/frontend.tsx new file mode 100644 index 0000000..446e60e --- /dev/null +++ b/src/frontend.tsx @@ -0,0 +1,26 @@ +/** + * This file is the entry point for the React app, it sets up the root + * element and renders the App component to the DOM. + * + * It is included in `src/index.html`. + */ + +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; + +const elem = document.getElementById("root")!; +const app = ( + + + +); + +if (import.meta.hot) { + // With hot module reloading, `import.meta.hot.data` is persisted. + const root = (import.meta.hot.data.root ??= createRoot(elem)); + root.render(app); +} else { + // The hot module reloading API is not available in production. + createRoot(elem).render(app); +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..f9a9859 --- /dev/null +++ b/src/index.css @@ -0,0 +1,5 @@ +* {padding: 0; margin: 0;} +#map { + width: 100vw; + height: 90vh; +} \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..fbbef92 --- /dev/null +++ b/src/index.html @@ -0,0 +1,15 @@ + + + + + + + Nguhmap + + +
+ + + diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..c6db1d0 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,31 @@ +import { serve, file, type BunRequest } from "bun"; +import index from "./index.html"; + +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" + } + }) + }, + "/legend/:item": (req: BunRequest<"/legend/:item">) => { + const {item} = req.params + return new Response(file(`./static/legend/${item}`)) + } + }, + + development: process.env.NODE_ENV !== "production" && { + // Enable browser hot reloading in development + hmr: true, + + // Echo console logs from the browser to the server + console: true, + }, +}); + +console.log(`🚀 Server running at ${server.url}`); diff --git a/static/legend/city.svg b/static/legend/city.svg new file mode 100644 index 0000000..6d4765c --- /dev/null +++ b/static/legend/city.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..632a36f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + + "exclude": ["dist", "node_modules"] +}