Compare commits
15 Commits
44b991f7c1
...
cd362fb811
| Author | SHA1 | Date | |
|---|---|---|---|
| cd362fb811 | |||
| 4a72142555 | |||
| 823adde1fa | |||
| f55f409158 | |||
| 250865893e | |||
| 07b6317b94 | |||
| 07f17e880e | |||
| baa4b9aae4 | |||
| 61e01c127f | |||
| 887a6bda3e | |||
| 774ac893ec | |||
| 1284be9c42 | |||
| 325f95487f | |||
| 1825b7d9fe | |||
| 4b9214dbaf |
@@ -54,7 +54,7 @@
|
|||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<span class="text-xl font-semibold text-stein-800">{area.gebiets_nr}</span>
|
<span class="text-xl font-semibold text-stein-800">{area.gebiets_nr}</span>
|
||||||
{#if area.status}
|
{#if area.status && area.status !== 'rechtskraeftig'}
|
||||||
<span class="rounded-full px-2 py-0.5 text-xs font-medium {STATUS_CLASS[area.status] ?? 'bg-stein-100 text-stein-600'}">
|
<span class="rounded-full px-2 py-0.5 text-xs font-medium {STATUS_CLASS[area.status] ?? 'bg-stein-100 text-stein-600'}">
|
||||||
{t(STATUS_KEY[area.status] ?? area.status)}
|
{t(STATUS_KEY[area.status] ?? area.status)}
|
||||||
</span>
|
</span>
|
||||||
@@ -76,29 +76,29 @@
|
|||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="flex-1 space-y-5 px-5 py-4 text-sm">
|
<div class="flex-1 space-y-5 px-5 py-4 text-sm">
|
||||||
<!-- Kenndaten -->
|
<!-- Kenndaten -->
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="flex flex-col divide-y divide-stein-100 border border-stein-100 rounded-lg overflow-hidden text-sm">
|
||||||
{#if area.flaeche_ha != null}
|
{#if area.flaeche_ha != null}
|
||||||
<div class="rounded-lg bg-stein-50 p-3">
|
<div class="flex items-center justify-between px-3 py-1.5">
|
||||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_flaeche)}</p>
|
<span class="text-xs text-stein-500">{t(T.windkarte_label_flaeche)}</span>
|
||||||
<p class="mt-0.5 font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</p>
|
<span class="font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if area.anlagen_geplant != null}
|
{#if area.anlagen_geplant != null}
|
||||||
<div class="rounded-lg bg-stein-50 p-3">
|
<div class="flex items-center justify-between px-3 py-1.5">
|
||||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_anlagen_geplant)}</p>
|
<span class="text-xs text-stein-500">{t(T.windkarte_label_anlagen_geplant)}</span>
|
||||||
<p class="mt-0.5 font-semibold text-stein-800">{area.anlagen_geplant}</p>
|
<span class="font-semibold text-stein-800">{area.anlagen_geplant}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if area.hoehe_max_m != null}
|
{#if area.hoehe_max_m != null}
|
||||||
<div class="rounded-lg bg-stein-50 p-3">
|
<div class="flex items-center justify-between px-3 py-1.5">
|
||||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_hoehe_max)}</p>
|
<span class="text-xs text-stein-500">{t(T.windkarte_label_hoehe_max)}</span>
|
||||||
<p class="mt-0.5 font-semibold text-stein-800">{area.hoehe_max_m} m</p>
|
<span class="font-semibold text-stein-800">{area.hoehe_max_m} m</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if area.investor}
|
{#if area.investor}
|
||||||
<div class="rounded-lg bg-stein-50 p-3 col-span-2">
|
<div class="flex items-center justify-between px-3 py-1.5">
|
||||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_investor)}</p>
|
<span class="text-xs text-stein-500">{t(T.windkarte_label_investor)}</span>
|
||||||
<p class="mt-0.5 font-semibold text-stein-800">{area.investor}</p>
|
<span class="font-semibold text-stein-800">{area.investor}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
{#if area.notizen}
|
{#if area.notizen}
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-1.5 text-xs font-medium uppercase tracking-wide text-stein-500">{t(T.windkarte_label_notizen)}</p>
|
<p class="mb-1.5 text-xs font-medium uppercase tracking-wide text-stein-500">{t(T.windkarte_label_notizen)}</p>
|
||||||
<p class="text-stein-700 leading-relaxed">{area.notizen}</p>
|
<div class="text-stein-700 leading-relaxed markdown prose prose-sm prose-zinc max-w-none">{@html area.notizen}</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
onready = null,
|
onready = null,
|
||||||
initialGebietsNr = null,
|
initialGebietsNr = null,
|
||||||
hiddenStatuses = [],
|
hiddenStatuses = [],
|
||||||
hiddenRings = ["2km", "5km"],
|
hiddenRings = [],
|
||||||
}: {
|
}: {
|
||||||
areas: WindArea[];
|
areas: WindArea[];
|
||||||
onselect: (area: WindArea | null, center: [number, number] | null) => void;
|
onselect: (area: WindArea | null, center: [number, number] | null) => void;
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
let selectedSlug = $state<string | null>(null);
|
let selectedSlug = $state<string | null>(null);
|
||||||
let geojsonLayer: import("leaflet").GeoJSON | null = null;
|
let geojsonLayer: import("leaflet").GeoJSON | null = null;
|
||||||
let buffering = $state(false);
|
let buffering = $state(false);
|
||||||
let _hiddenRings: string[] = ["2km", "5km"];
|
let _hiddenRings: string[] = [];
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
_hiddenRings = hiddenRings;
|
_hiddenRings = hiddenRings;
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
map!.fitBounds((layer as import("leaflet").Polygon).getBounds(), {
|
map!.fitBounds((layer as import("leaflet").Polygon).getBounds(), {
|
||||||
paddingTopLeft: [40, 40],
|
paddingTopLeft: [40, 40],
|
||||||
paddingBottomRight: [desktop ? 320 : 40, desktop ? 40 : 300],
|
paddingBottomRight: [desktop ? 320 : 40, desktop ? 40 : 300],
|
||||||
maxZoom: 12,
|
maxZoom: 11,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +278,8 @@
|
|||||||
},
|
},
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
|
restyleAll(selectedSlug);
|
||||||
|
|
||||||
// Labels: show from zoom 13, scale font proportionally with zoom
|
// Labels: show from zoom 13, scale font proportionally with zoom
|
||||||
const syncLabels = () => {
|
const syncLabels = () => {
|
||||||
if (!labelLayer || !map) return;
|
if (!labelLayer || !map) return;
|
||||||
|
|||||||
@@ -116,26 +116,26 @@
|
|||||||
{#if resolved.length === 0}
|
{#if resolved.length === 0}
|
||||||
<p class="text-sm text-stein-500">Keine Dateien verfügbar.</p>
|
<p class="text-sm text-stein-500">Keine Dateien verfügbar.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ul class="flex flex-col gap-2 list-none! p-0 m-0! ml-0">
|
<ul class="flex flex-col gap-1 list-none! p-0 m-0! ml-0">
|
||||||
{#each resolved as f}
|
{#each resolved as f}
|
||||||
<li class="list-none! ml-0">
|
<li class="list-none! ml-0">
|
||||||
<a
|
<a
|
||||||
href={f.href}
|
href={f.href}
|
||||||
class="group flex items-start gap-3 rounded-xs border border-stein-200 bg-white px-3 py-2 hover:border-stein-400 hover:bg-stein-50 transition-colors"
|
class="group flex items-start gap-2 rounded-xs border border-stein-200 bg-white px-2.5 py-1.5 hover:border-stein-400 hover:bg-stein-50 transition-colors no-underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Icon icon={f.iconName} class="size-6 shrink-0 text-stein-600" aria-hidden="true" />
|
<Icon icon={f.iconName} class="size-5 shrink-0 text-stein-500 mt-0.5" aria-hidden="true" />
|
||||||
<span class="flex flex-col min-w-0">
|
<span class="flex-1 min-w-0 flex flex-col">
|
||||||
<span class="font-medium text-stein-900 group-hover:underline truncate">{f.label}</span>
|
<span class="flex items-center gap-1.5 min-w-0">
|
||||||
{#if f.description}
|
<span class="text-sm text-stein-900 group-hover:underline truncate">{f.label}</span>
|
||||||
<span class="text-sm text-stein-600">{f.description}</span>
|
{#if f.sizeLabel}
|
||||||
|
<span class="shrink-0 text-[.6rem] tabular-nums whitespace-nowrap bg-stein-100 text-stein-500 px-1 py-px rounded-xs">{f.sizeLabel}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-xs text-stein-500">
|
|
||||||
{#if f.ext}<span class="uppercase">{f.ext}</span>{/if}
|
|
||||||
{#if f.ext && f.sizeLabel} · {/if}
|
|
||||||
{#if f.sizeLabel}{f.sizeLabel}{/if}
|
|
||||||
</span>
|
</span>
|
||||||
|
{#if f.description}
|
||||||
|
<span class="text-xs text-stein-400">{f.description}</span>
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ let areas = $state<WindArea[]>([]);
|
|||||||
let mapReady = $state(false);
|
let mapReady = $state(false);
|
||||||
let initialGebietsNr = $state<string | null>(null);
|
let initialGebietsNr = $state<string | null>(null);
|
||||||
let resetMap: (() => void) | null = null;
|
let resetMap: (() => void) | null = null;
|
||||||
let hiddenStatuses = $state<string[]>([]);
|
let hiddenStatuses = $state<string[]>(["rechtskraeftig"]);
|
||||||
let hiddenRings = $state<string[]>(["2km", "5km"]);
|
let hiddenRings = $state<string[]>([]);
|
||||||
let mapActive = $state(false);
|
let mapActive = $state(false);
|
||||||
|
|
||||||
function toggleStatus(status: string) {
|
function toggleStatus(status: string) {
|
||||||
@@ -173,7 +173,6 @@ let areas = $state<WindArea[]>([]);
|
|||||||
{#each [
|
{#each [
|
||||||
{ status: "rechtskraeftig", color: "#2d7a45", label: t(T.windkarte_legend_rechtskraeftig) },
|
{ status: "rechtskraeftig", color: "#2d7a45", label: t(T.windkarte_legend_rechtskraeftig) },
|
||||||
{ status: "entwurf_2", color: "#b08a52", label: t(T.windkarte_legend_entwurf_2) },
|
{ status: "entwurf_2", color: "#b08a52", label: t(T.windkarte_legend_entwurf_2) },
|
||||||
{ status: "entwurf_2_voraussichtlich", color: "#d4752a", label: t(T.windkarte_legend_entwurf_2_voraussichtlich) },
|
|
||||||
] as item}
|
] as item}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -62,8 +62,13 @@ export interface CmsFileUrlOptions {
|
|||||||
/**
|
/**
|
||||||
* Lokale Datei-URL: /cms-files?src=…&name=…&dl=…
|
* Lokale Datei-URL: /cms-files?src=…&name=…&dl=…
|
||||||
* `src` = volle Datei-URL oder CMS-Asset-Pfad (`/api/assets/...`).
|
* `src` = volle Datei-URL oder CMS-Asset-Pfad (`/api/assets/...`).
|
||||||
|
* Externe http(s)-URLs werden direkt zurückgegeben — der /cms-files-Proxy
|
||||||
|
* würde sie wegen SSRF-Schutz sowieso ablehnen.
|
||||||
*/
|
*/
|
||||||
export function getCmsFileUrl(src: string, opts: CmsFileUrlOptions = {}): string {
|
export function getCmsFileUrl(src: string, opts: CmsFileUrlOptions = {}): string {
|
||||||
|
if (src.startsWith('http://') || src.startsWith('https://')) {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set('src', src);
|
params.set('src', src);
|
||||||
if (opts.filename) params.set('name', opts.filename);
|
if (opts.filename) params.set('name', opts.filename);
|
||||||
|
|||||||
Reference in New Issue
Block a user