feat(hikes): route-builder, overview map + cards, SAC-coloured pipeline
Build pipeline (scripts/build-hikes.ts) parses per-hike GPX, encodes images via sharp, reverse-geocodes the centroid against Swisstopo and emits a typed manifest under src/lib/data/hikes.generated.ts (gitignored). Track JSON + image binaries live outside /static; served in dev by a small hike-images plugin in vite.config.ts, in prod by nginx (private/ images proxied through Node + X-Accel-Redirect for auth-gating). /hikes overview: full-bleed Swisstopo hero map (HikesOverviewMap) sits under the sticky nav, drawing one polyline per route coloured by SAC tier (T1 yellow Wegweiser, T2/T3 white-red-white, T4-T6 white-blue- white). Click navigates, hover thickens + tooltips. Layer toggle, recenter, GPS controls mirror the detail map (minus images toggle). Cards drop the trail SVG, gain a per-route icon + SAC marker pictogram on the cover, altitude range, season label, and "Neu" badge for recently-published hikes. Filter bar + totals strip recompute over the currently-visible set. /hikes/[slug]: hero map with elevation profile, photo strip with map sync, scroll-position pin, GPX download, SAC marker stats + min/max altitude + season. Route-builder (/hikes/route-builder): client-side draft persisted to localStorage, EXIF-driven image placement, snap-to-route via BRouter (OSRM + linear fallback) and Swisstopo profile.json elevation enrichment that handles degenerate same-coord segments via the height endpoint. Filter init switched from a script-time snapshot of data.hikes (which sporadically returned a one-hike subset during dev hydration and locked the page to that single hike) to a post-mount \$effect. Content under src/content/hikes/ intentionally not included (WIP).
This commit is contained in:
+49
-2
@@ -1,5 +1,49 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig, type Plugin } from 'vite';
|
||||
import { createReadStream, promises as fs } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
/** In `vite dev`, hike image binaries live in `hikes-assets/` (outside `/static`
|
||||
* so they aren't bundled into the Node build). In production nginx serves them
|
||||
* directly from `/var/www/static/hikes/`; the SvelteKit dev server has no
|
||||
* nginx in front, so we intercept `/hikes/<slug>/images/<file>` here and
|
||||
* stream the file off disk. Private images (`/hikes/<slug>/private/<file>`)
|
||||
* intentionally fall through to the SvelteKit endpoint, which enforces auth. */
|
||||
function hikeImagesDevPlugin(): Plugin {
|
||||
const ROOT = path.resolve(process.cwd(), 'hikes-assets');
|
||||
const MIME: Record<string, string> = {
|
||||
'.avif': 'image/avif',
|
||||
'.webp': 'image/webp',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml'
|
||||
};
|
||||
return {
|
||||
name: 'hike-images-dev',
|
||||
apply: 'serve',
|
||||
configureServer(server) {
|
||||
server.middlewares.use(async (req, res, next) => {
|
||||
const url = req.url ?? '';
|
||||
const m = url.match(/^\/hikes\/([^/]+)\/images\/([^/?#]+)(?:[?#].*)?$/);
|
||||
if (!m) return next();
|
||||
const [, slug, file] = m;
|
||||
if (slug.includes('..') || file.includes('..')) return next();
|
||||
const filePath = path.join(ROOT, slug, 'images', file);
|
||||
try {
|
||||
const stat = await fs.stat(filePath);
|
||||
const mime = MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream';
|
||||
res.setHeader('Content-Type', mime);
|
||||
res.setHeader('Content-Length', String(stat.size));
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||
createReadStream(filePath).pipe(res);
|
||||
} catch {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
@@ -14,7 +58,7 @@ export default defineConfig({
|
||||
server: {
|
||||
allowedHosts: ["bocken.org"]
|
||||
},
|
||||
plugins: [sveltekit()],
|
||||
plugins: [hikeImagesDevPlugin(), sveltekit()],
|
||||
optimizeDeps: {
|
||||
exclude: ['barcode-detector']
|
||||
},
|
||||
@@ -33,6 +77,9 @@ export default defineConfig({
|
||||
if (id.includes('barcode-detector') || id.includes('zxing-wasm')) {
|
||||
return 'barcode';
|
||||
}
|
||||
if (id.includes('/leaflet/')) {
|
||||
return 'leaflet';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user