Compare commits
15 Commits
| 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="flex items-center gap-2 flex-wrap">
|
||||
<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'}">
|
||||
{t(STATUS_KEY[area.status] ?? area.status)}
|
||||
</span>
|
||||
@@ -76,29 +76,29 @@
|
||||
<!-- Body -->
|
||||
<div class="flex-1 space-y-5 px-5 py-4 text-sm">
|
||||
<!-- 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}
|
||||
<div class="rounded-lg bg-stein-50 p-3">
|
||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_flaeche)}</p>
|
||||
<p class="mt-0.5 font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</p>
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_flaeche)}</span>
|
||||
<span class="font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if area.anlagen_geplant != null}
|
||||
<div class="rounded-lg bg-stein-50 p-3">
|
||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_anlagen_geplant)}</p>
|
||||
<p class="mt-0.5 font-semibold text-stein-800">{area.anlagen_geplant}</p>
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_anlagen_geplant)}</span>
|
||||
<span class="font-semibold text-stein-800">{area.anlagen_geplant}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if area.hoehe_max_m != null}
|
||||
<div class="rounded-lg bg-stein-50 p-3">
|
||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_hoehe_max)}</p>
|
||||
<p class="mt-0.5 font-semibold text-stein-800">{area.hoehe_max_m} m</p>
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_hoehe_max)}</span>
|
||||
<span class="font-semibold text-stein-800">{area.hoehe_max_m} m</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if area.investor}
|
||||
<div class="rounded-lg bg-stein-50 p-3 col-span-2">
|
||||
<p class="text-xs text-stein-500">{t(T.windkarte_label_investor)}</p>
|
||||
<p class="mt-0.5 font-semibold text-stein-800">{area.investor}</p>
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_investor)}</span>
|
||||
<span class="font-semibold text-stein-800">{area.investor}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -119,7 +119,7 @@
|
||||
{#if area.notizen}
|
||||
<div>
|
||||
<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>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
onready = null,
|
||||
initialGebietsNr = null,
|
||||
hiddenStatuses = [],
|
||||
hiddenRings = ["2km", "5km"],
|
||||
hiddenRings = [],
|
||||
}: {
|
||||
areas: WindArea[];
|
||||
onselect: (area: WindArea | null, center: [number, number] | null) => void;
|
||||
@@ -52,7 +52,7 @@
|
||||
let selectedSlug = $state<string | null>(null);
|
||||
let geojsonLayer: import("leaflet").GeoJSON | null = null;
|
||||
let buffering = $state(false);
|
||||
let _hiddenRings: string[] = ["2km", "5km"];
|
||||
let _hiddenRings: string[] = [];
|
||||
|
||||
$effect(() => {
|
||||
_hiddenRings = hiddenRings;
|
||||
@@ -164,7 +164,7 @@
|
||||
map!.fitBounds((layer as import("leaflet").Polygon).getBounds(), {
|
||||
paddingTopLeft: [40, 40],
|
||||
paddingBottomRight: [desktop ? 320 : 40, desktop ? 40 : 300],
|
||||
maxZoom: 12,
|
||||
maxZoom: 11,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -278,6 +278,8 @@
|
||||
},
|
||||
}).addTo(map);
|
||||
|
||||
restyleAll(selectedSlug);
|
||||
|
||||
// Labels: show from zoom 13, scale font proportionally with zoom
|
||||
const syncLabels = () => {
|
||||
if (!labelLayer || !map) return;
|
||||
|
||||
@@ -116,26 +116,26 @@
|
||||
{#if resolved.length === 0}
|
||||
<p class="text-sm text-stein-500">Keine Dateien verfügbar.</p>
|
||||
{: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}
|
||||
<li class="list-none! ml-0">
|
||||
<a
|
||||
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"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon={f.iconName} class="size-6 shrink-0 text-stein-600" aria-hidden="true" />
|
||||
<span class="flex flex-col min-w-0">
|
||||
<span class="font-medium text-stein-900 group-hover:underline truncate">{f.label}</span>
|
||||
{#if f.description}
|
||||
<span class="text-sm text-stein-600">{f.description}</span>
|
||||
<Icon icon={f.iconName} class="size-5 shrink-0 text-stein-500 mt-0.5" aria-hidden="true" />
|
||||
<span class="flex-1 min-w-0 flex flex-col">
|
||||
<span class="flex items-center gap-1.5 min-w-0">
|
||||
<span class="text-sm text-stein-900 group-hover:underline truncate">{f.label}</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}
|
||||
<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>
|
||||
{#if f.description}
|
||||
<span class="text-xs text-stein-400">{f.description}</span>
|
||||
{/if}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -24,8 +24,8 @@ let areas = $state<WindArea[]>([]);
|
||||
let mapReady = $state(false);
|
||||
let initialGebietsNr = $state<string | null>(null);
|
||||
let resetMap: (() => void) | null = null;
|
||||
let hiddenStatuses = $state<string[]>([]);
|
||||
let hiddenRings = $state<string[]>(["2km", "5km"]);
|
||||
let hiddenStatuses = $state<string[]>(["rechtskraeftig"]);
|
||||
let hiddenRings = $state<string[]>([]);
|
||||
let mapActive = $state(false);
|
||||
|
||||
function toggleStatus(status: string) {
|
||||
@@ -173,7 +173,6 @@ let areas = $state<WindArea[]>([]);
|
||||
{#each [
|
||||
{ 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_voraussichtlich", color: "#d4752a", label: t(T.windkarte_legend_entwurf_2_voraussichtlich) },
|
||||
] as item}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -62,8 +62,13 @@ export interface CmsFileUrlOptions {
|
||||
/**
|
||||
* Lokale Datei-URL: /cms-files?src=…&name=…&dl=…
|
||||
* `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 {
|
||||
if (src.startsWith('http://') || src.startsWith('https://')) {
|
||||
return src;
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
params.set('src', src);
|
||||
if (opts.filename) params.set('name', opts.filename);
|
||||
|
||||
Reference in New Issue
Block a user