From 3ec8b28cf97a87b7d4f28897fbb5c6f75c2aea20 Mon Sep 17 00:00:00 2001 From: Arne Baeumler Date: Sun, 7 Jun 2026 17:12:05 +0200 Subject: [PATCH] feat: load nextcloud shares in browser --- src/server/request-handler.ts | 634 ++++++++++++++++++++++++++-------- 1 file changed, 496 insertions(+), 138 deletions(-) diff --git a/src/server/request-handler.ts b/src/server/request-handler.ts index 5300738..2cf83ce 100644 --- a/src/server/request-handler.ts +++ b/src/server/request-handler.ts @@ -1,38 +1,64 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -const demoPhotos = [ +type Photo = { + id: string; + name: string; + latitude: number | null; + longitude: number | null; + capturedAt: string | null; + thumbUrl: string; + fullUrl: string; + source: "demo" | "nextcloud"; +}; + +type ShareListingEntry = { + href: string; + name: string; + lastModified: string | null; + contentType: string; + isCollection: boolean; +}; + +const demoPhotos: Photo[] = [ { - id: "1", + id: "demo-1", name: "berlin-brandenburg-gate.jpg", - lat: 52.516275, - lon: 13.377704, - capturedAt: "2026-06-07T08:20:00Z", - thumb: - "https://images.unsplash.com/photo-1467269204594-9661b134dd2b?auto=format&fit=crop&w=200&q=60" + latitude: 52.516275, + longitude: 13.377704, + capturedAt: "2026-06-07T08:20:00.000Z", + thumbUrl: + "https://images.unsplash.com/photo-1467269204594-9661b134dd2b?auto=format&fit=crop&w=240&q=60", + fullUrl: + "https://images.unsplash.com/photo-1467269204594-9661b134dd2b?auto=format&fit=crop&w=1600&q=80", + source: "demo" }, { - id: "2", + id: "demo-2", name: "museum-island.jpg", - lat: 52.5169, - lon: 13.4015, - capturedAt: "2026-06-07T08:42:00Z", - thumb: - "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=200&q=60" + latitude: 52.5169, + longitude: 13.4015, + capturedAt: "2026-06-07T08:42:00.000Z", + thumbUrl: + "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=240&q=60", + fullUrl: + "https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?auto=format&fit=crop&w=1600&q=80", + source: "demo" }, { - id: "3", + id: "demo-3", name: "alexanderplatz.jpg", - lat: 52.521918, - lon: 13.413215, - capturedAt: "2026-06-07T09:05:00Z", - thumb: - "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=200&q=60" + latitude: 52.521918, + longitude: 13.413215, + capturedAt: "2026-06-07T09:05:00.000Z", + thumbUrl: + "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=240&q=60", + fullUrl: + "https://images.unsplash.com/photo-1494526585095-c41746248156?auto=format&fit=crop&w=1600&q=80", + source: "demo" } -] as const; +]; function htmlPage(): string { - const points = JSON.stringify(demoPhotos); - return ` @@ -50,10 +76,11 @@ function htmlPage(): string { color-scheme: light; --bg: #f3efe6; --panel: rgba(255, 255, 255, 0.82); - --panel-border: rgba(33, 37, 41, 0.12); - --text: #1f2937; + --border: rgba(15, 23, 42, 0.12); + --text: #16202a; --muted: #5b6472; --accent: #2d6cdf; + --accent-strong: #1f56c2; --shadow: 0 18px 50px rgba(23, 31, 45, 0.14); } @@ -63,8 +90,13 @@ function htmlPage(): string { html, body { - height: 100%; margin: 0; + height: 100%; + } + + body { + display: grid; + grid-template-rows: auto 1fr; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--text); background: @@ -73,34 +105,34 @@ function htmlPage(): string { var(--bg); } - body { - display: grid; - grid-template-rows: auto 1fr; - } - header { - padding: 20px 24px 12px; display: flex; justify-content: space-between; - gap: 16px; align-items: end; + gap: 16px; + padding: 20px 24px 12px; + } + + h1, + h2, + p { + margin: 0; } .brand h1 { - margin: 0; - font-size: clamp(1.5rem, 2.4vw, 2.2rem); + font-size: clamp(1.6rem, 2.4vw, 2.2rem); } .brand p, - .meta { - margin: 6px 0 0; + .meta, + .muted { color: var(--muted); } .layout { min-height: 0; display: grid; - grid-template-columns: 340px 1fr; + grid-template-columns: 360px 1fr; gap: 16px; padding: 0 16px 16px; } @@ -110,44 +142,92 @@ function htmlPage(): string { .overlay-card { background: var(--panel); backdrop-filter: blur(14px); - border: 1px solid var(--panel-border); + border: 1px solid var(--border); border-radius: 20px; box-shadow: var(--shadow); } aside { - padding: 18px; - display: flex; - flex-direction: column; - gap: 16px; min-height: 0; - } - - .dropzone { - border: 1.5px dashed rgba(45, 108, 223, 0.35); - border-radius: 16px; padding: 18px; - background: rgba(255, 255, 255, 0.7); + display: grid; + gap: 16px; } - .dropzone strong { - display: block; - margin-bottom: 6px; + .card { + padding: 16px; + border-radius: 16px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.72); } - .dropzone small, - .section small { + .card h2 { + font-size: 1rem; + margin-bottom: 8px; + } + + .card p, + .card small { color: var(--muted); } - .section { + label { display: grid; + gap: 8px; + margin-top: 12px; + font-size: 0.92rem; + } + + input { + width: 100%; + border: 1px solid rgba(15, 23, 42, 0.18); + border-radius: 12px; + padding: 12px 14px; + font: inherit; + background: white; + } + + .button-row { + display: flex; gap: 10px; + margin-top: 12px; + flex-wrap: wrap; + } + + button { + border: 0; + border-radius: 999px; + padding: 10px 14px; + font: inherit; + cursor: pointer; + } + + .primary { + background: var(--accent); + color: white; + } + + .primary:hover { + background: var(--accent-strong); + } + + .secondary { + background: rgba(15, 23, 42, 0.08); + color: var(--text); + } + + .status { + margin-top: 10px; + font-size: 0.9rem; + color: var(--muted); } .list { display: grid; gap: 10px; + max-height: min(44vh, 380px); + overflow: auto; + padding-right: 2px; } .photo { @@ -158,6 +238,7 @@ function htmlPage(): string { padding: 10px; border-radius: 14px; background: rgba(255, 255, 255, 0.72); + border: 1px solid rgba(15, 23, 42, 0.06); } .photo img { @@ -189,15 +270,16 @@ function htmlPage(): string { min-height: 560px; } - .status { + .map-label { position: absolute; - left: 16px; - top: 16px; z-index: 500; + top: 16px; + left: 16px; padding: 10px 12px; border-radius: 999px; - background: rgba(255, 255, 255, 0.9); - border: 1px solid rgba(0, 0, 0, 0.08); + background: rgba(255, 255, 255, 0.92); + border: 1px solid rgba(15, 23, 42, 0.08); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08); font-size: 0.88rem; } @@ -206,7 +288,7 @@ function htmlPage(): string { inset: 0; display: none; place-items: center; - background: rgba(8, 12, 18, 0.78); + background: rgba(8, 12, 18, 0.82); z-index: 1000; padding: 24px; } @@ -216,41 +298,29 @@ function htmlPage(): string { } .overlay-card { - max-width: min(1100px, 100%); - width: 100%; + width: min(1120px, 100%); overflow: hidden; } .overlay-card header { padding: 16px 18px; - border-bottom: 1px solid var(--panel-border); + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; } .overlay-card img { + display: block; width: 100%; max-height: 78vh; object-fit: contain; - display: block; background: #111; } .close { - appearance: none; - border: 0; background: #111827; color: white; - border-radius: 999px; - padding: 8px 12px; - cursor: pointer; - } - - .leaflet-popup-content-wrapper, - .leaflet-popup-tip { - background: rgba(255, 255, 255, 0.96); - } - - .leaflet-popup-content { - margin: 10px 12px; } .thumb { @@ -268,11 +338,19 @@ function htmlPage(): string { .thumb button { border: 0; + border-radius: 10px; + padding: 8px 10px; background: var(--accent); color: white; - padding: 8px 10px; - border-radius: 10px; - cursor: pointer; + } + + .leaflet-popup-content-wrapper, + .leaflet-popup-tip { + background: rgba(255, 255, 255, 0.97); + } + + .leaflet-popup-content { + margin: 10px 12px; } @media (max-width: 920px) { @@ -290,27 +368,42 @@ function htmlPage(): string {

mapy-mg

-

Fotos hochladen, Positionen sichtbar machen und Wege grob nachzeichnen.

+

Fotos laden, EXIF lokal im Browser auslesen und auf OpenStreetMap anzeigen.

-
OpenStreetMap + Leaflet · Prototyp
+
Client-only Import · kein Bildspeicher auf dem Server
-
Marker per Hover -> Thumbnail, Klick -> Vollbild
+
Hover: Thumbnail · Klick: Vollbild · Route: zeitlich sortiert
@@ -326,87 +419,352 @@ function htmlPage(): string { - `;