restarted with less bloat
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -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
|
26
README.md
Normal file
26
README.md
Normal file
@ -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.
|
17
bun-env.d.ts
vendored
Normal file
17
bun-env.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
54
bun.lock
Normal file
54
bun.lock
Normal file
@ -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=="],
|
||||
}
|
||||
}
|
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[serve.static]
|
||||
env = "BUN_PUBLIC_*"
|
29
devscripts/generate_tiles.ts
Normal file
29
devscripts/generate_tiles.ts
Normal file
@ -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`
|
25
devscripts/optimise_assets.ts
Normal file
25
devscripts/optimise_assets.ts
Normal file
@ -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)}
|
24
package.json
Normal file
24
package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
46
src/App.tsx
Normal file
46
src/App.tsx
Normal file
@ -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 (<>
|
||||
<MapContainer id="map"
|
||||
center={[0,0]}
|
||||
maxBounds={[[-8192,-8192], [8192,8192]]}
|
||||
crs={L.CRS.Simple}
|
||||
zoom={0}
|
||||
ref={map}>
|
||||
<TileLayer
|
||||
url="/tiles/{z}/{x}/{y}"
|
||||
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]})}>
|
||||
<Tooltip>{it.label}</Tooltip>
|
||||
</Marker>
|
||||
))
|
||||
}
|
||||
</MapContainer>
|
||||
<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} />
|
||||
</>)
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
26
src/frontend.tsx
Normal file
26
src/frontend.tsx
Normal file
@ -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 = (
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
5
src/index.css
Normal file
5
src/index.css
Normal file
@ -0,0 +1,5 @@
|
||||
* {padding: 0; margin: 0;}
|
||||
#map {
|
||||
width: 100vw;
|
||||
height: 90vh;
|
||||
}
|
15
src/index.html
Normal file
15
src/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""/>
|
||||
<title>Nguhmap</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./frontend.tsx"></script>
|
||||
</body>
|
||||
</html>
|
31
src/index.tsx
Normal file
31
src/index.tsx
Normal file
@ -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}`);
|
11
static/legend/city.svg
Normal file
11
static/legend/city.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?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"
|
||||
version="1.1"
|
||||
>
|
||||
<circle style="fill: #000" r="8" transform="translate(8,8)" />
|
||||
<circle style="fill: #fff" r="6" transform="translate(8,8)" />
|
||||
</svg>
|
After Width: | Height: | Size: 311 B |
36
tsconfig.json
Normal file
36
tsconfig.json
Normal file
@ -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"]
|
||||
}
|
Reference in New Issue
Block a user