104 lines
4.8 KiB
JavaScript
104 lines
4.8 KiB
JavaScript
import express from 'express'
|
|
import http from 'http'
|
|
import { WebSocketServer } from 'ws'
|
|
import path from 'path'
|
|
import fs from 'fs'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const app = express()
|
|
const server = http.createServer(app)
|
|
const PORT = process.env.PORT || 3001
|
|
|
|
const MAPS_DIR = path.join(__dirname, '..', 'maps')
|
|
const DOORS_FILE = path.join(__dirname, '..', 'src', 'data', 'door-positions.json')
|
|
const BASEMAP_FILE = path.join(__dirname, '..', 'src', 'data', 'basemap.json')
|
|
const SIZES_FILE = path.join(__dirname, '..', 'src', 'data', 'room-sizes.json')
|
|
|
|
// ── WebSocket (admin collab) ────────────────────────────────────────────────
|
|
const wss = new WebSocketServer({ server, path: '/ws/admin' })
|
|
const adminClients = new Set()
|
|
|
|
function readJSON(file, fallback) {
|
|
try { return JSON.parse(fs.readFileSync(file, 'utf8')) } catch { return fallback }
|
|
}
|
|
|
|
function broadcast(sender, msg) {
|
|
const str = JSON.stringify(msg)
|
|
for (const client of adminClients) {
|
|
if (client !== sender && client.readyState === 1) client.send(str)
|
|
}
|
|
}
|
|
|
|
wss.on('connection', (ws) => {
|
|
adminClients.add(ws)
|
|
ws.send(JSON.stringify({
|
|
type: 'init',
|
|
doors: readJSON(DOORS_FILE, {}),
|
|
basemap: readJSON(BASEMAP_FILE, []),
|
|
sizes: readJSON(SIZES_FILE, {}),
|
|
}))
|
|
|
|
ws.on('message', (raw) => {
|
|
try {
|
|
const msg = JSON.parse(raw)
|
|
if (msg.type === 'doors-save') {
|
|
fs.writeFileSync(DOORS_FILE, JSON.stringify(msg.data, null, 2))
|
|
broadcast(ws, { type: 'doors-update', data: msg.data })
|
|
}
|
|
if (msg.type === 'basemap-save') {
|
|
fs.writeFileSync(BASEMAP_FILE, JSON.stringify(msg.data, null, 2))
|
|
broadcast(ws, { type: 'basemap-update', data: msg.data })
|
|
}
|
|
if (msg.type === 'sizes-save') {
|
|
fs.writeFileSync(SIZES_FILE, JSON.stringify(msg.data, null, 2))
|
|
broadcast(ws, { type: 'sizes-update', data: msg.data })
|
|
}
|
|
if (msg.type === 'basemap-position-save') {
|
|
const current = readJSON(BASEMAP_FILE, [])
|
|
const next = current.map(entry => entry.id === msg.id ? { ...entry, position: msg.position } : entry)
|
|
fs.writeFileSync(BASEMAP_FILE, JSON.stringify(next, null, 2))
|
|
broadcast(ws, { type: 'basemap-update', data: next })
|
|
}
|
|
} catch {}
|
|
})
|
|
|
|
ws.on('close', () => adminClients.delete(ws))
|
|
})
|
|
|
|
// ── REST ────────────────────────────────────────────────────────────────────
|
|
function formatName(f) {
|
|
return f.replace(/_/g,' ').replace(/\b\w/g,c=>c.toUpperCase())
|
|
.replace(/\b1f\b/gi,'1F').replace(/\b2f\b/gi,'2F')
|
|
.replace(/\bB1f\b/gi,'B1F').replace(/\bB2f\b/gi,'B2F')
|
|
.replace(/\bRt\b/g,'Route').replace(/\bPokecenter\b/g,'Pokémon Center')
|
|
}
|
|
|
|
function scanRooms(dir, rooms = []) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name)
|
|
if (entry.isDirectory()) scanRooms(full, rooms)
|
|
else if (entry.name.toLowerCase().endsWith('.png')) {
|
|
const base = path.basename(entry.name, path.extname(entry.name)).toLowerCase()
|
|
const rel = path.relative(MAPS_DIR, full).replace(/\\/g, '/')
|
|
const cat = rel.split('/').slice(-2,-1)[0] ?? 'other'
|
|
rooms.push({ id: base, name: formatName(base), image: `/maps/${rel}`, category: cat })
|
|
}
|
|
}
|
|
return rooms
|
|
}
|
|
|
|
app.get('/api/rooms', (_, res) => { try { res.json(scanRooms(MAPS_DIR)) } catch(e) { res.status(500).json({error:e.message}) } })
|
|
app.get('/api/doors', (_, res) => res.json(readJSON(DOORS_FILE, {})))
|
|
app.get('/api/basemap', (_, res) => res.json(readJSON(BASEMAP_FILE, [])))
|
|
app.get('/api/room-sizes', (_, res) => res.json(readJSON(SIZES_FILE, {})))
|
|
|
|
app.post('/api/doors', express.json(), (req, res) => { try { fs.writeFileSync(DOORS_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} })
|
|
app.post('/api/basemap', express.json(), (req, res) => { try { fs.writeFileSync(BASEMAP_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} })
|
|
app.post('/api/room-sizes', express.json(), (req, res) => { try { fs.writeFileSync(SIZES_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} })
|
|
|
|
app.use('/maps', express.static(MAPS_DIR))
|
|
app.use(express.static(path.join(__dirname, '..', 'dist')))
|
|
app.get('/{*path}', (_, res) => res.sendFile(path.join(__dirname, '..', 'dist', 'index.html')))
|
|
|
|
server.listen(PORT, () => console.log(`Pokemon Doors running on http://localhost:${PORT}`)) |