From 5927e5dbe9ec4fc1596dfd5f789eead3c4015bed Mon Sep 17 00:00:00 2001 From: Steve Crow Date: Mon, 3 Feb 2025 17:00:30 -0500 Subject: [PATCH] Add overlays and some UI clean up --- drizzle/0005_wise_starfox.sql | 21 + drizzle/meta/0005_snapshot.json | 831 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/components/form/MultiSelect.svelte | 97 ++ src/lib/components/map/AirspaceMap.svelte | 254 ++++-- src/lib/db/schema.ts | 18 + .../(protected)/admin/AdminMenuButton.svelte | 35 +- .../admin/airspace/+page.server.ts | 190 +++- .../(protected)/admin/airspace/+page.svelte | 366 +++++--- .../airspace/AddUpdateOverlayForm.svelte | 371 ++++++++ .../AddUpdateStaticElementForm.svelte | 19 +- .../(protected)/admin/areas/+page.svelte | 197 +++-- .../admin/restrictions/+page.svelte | 130 +-- .../(protected)/admin/splits/+page.svelte | 56 +- src/routes/airspace/+page.server.ts | 57 +- src/routes/airspace/+page.svelte | 6 +- 16 files changed, 2268 insertions(+), 387 deletions(-) create mode 100644 drizzle/0005_wise_starfox.sql create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 src/lib/components/form/MultiSelect.svelte create mode 100644 src/routes/(protected)/admin/airspace/AddUpdateOverlayForm.svelte diff --git a/drizzle/0005_wise_starfox.sql b/drizzle/0005_wise_starfox.sql new file mode 100644 index 0000000..0241f2a --- /dev/null +++ b/drizzle/0005_wise_starfox.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS "airspace_overlay_components" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "group_id" uuid NOT NULL, + "name" text, + "color" text, + "geojson" jsonb, + "settings" jsonb +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "airspace_overlay_groups" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text, + "is_published" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "airspace_overlay_components" ADD CONSTRAINT "airspace_overlay_components_group_id_airspace_overlay_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."airspace_overlay_groups"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..829dfbc --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,831 @@ +{ + "id": "45817721-3126-4dcc-b602-64fe3d7e3004", + "prevId": "78b4ec70-ab59-48e0-8cf9-c4d3d570b810", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.adar_records": { + "name": "adar_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "adar_id": { + "name": "adar_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "upper_altitude": { + "name": "upper_altitude", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "lower_altitude": { + "name": "lower_altitude", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "auto_route_limit": { + "name": "auto_route_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "route_string": { + "name": "route_string", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protected_area_overwrite": { + "name": "protected_area_overwrite", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "star_id": { + "name": "star_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dp_id": { + "name": "dp_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "route_fixes": { + "name": "route_fixes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "arrival_airports": { + "name": "arrival_airports", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "departure_airports": { + "name": "departure_airports", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_comment": { + "name": "user_comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "adar_records_adar_id_unique": { + "name": "adar_records_adar_id_unique", + "nullsNotDistinct": false, + "columns": [ + "adar_id" + ] + } + } + }, + "public.aircraft": { + "name": "aircraft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "class": { + "name": "class", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number_of_engines": { + "name": "number_of_engines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "engine_type": { + "name": "engine_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manufacturer": { + "name": "manufacturer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "aircraft_code_index": { + "name": "aircraft_code_index", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.airlines": { + "name": "airlines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "telephony": { + "name": "telephony", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "airline_code_index": { + "name": "airline_code_index", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.airspace_overlay_components": { + "name": "airspace_overlay_components", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "geojson": { + "name": "geojson", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "airspace_overlay_components_group_id_airspace_overlay_groups_id_fk": { + "name": "airspace_overlay_components_group_id_airspace_overlay_groups_id_fk", + "tableFrom": "airspace_overlay_components", + "tableTo": "airspace_overlay_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.airspace_overlay_groups": { + "name": "airspace_overlay_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.airspace_static_element_components": { + "name": "airspace_static_element_components", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "geojson": { + "name": "geojson", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "airspace_static_element_components_group_id_airspace_static_element_groups_id_fk": { + "name": "airspace_static_element_components_group_id_airspace_static_element_groups_id_fk", + "tableFrom": "airspace_static_element_components", + "tableTo": "airspace_static_element_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.airspace_static_element_groups": { + "name": "airspace_static_element_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.area_metadata": { + "name": "area_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "short": { + "name": "short", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "long": { + "name": "long", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "geojson": { + "name": "geojson", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "frequency": { + "name": "frequency", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth_user": { + "name": "auth_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.restrictions": { + "name": "restrictions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "airport": { + "name": "airport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route": { + "name": "route", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to": { + "name": "to", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "restriction": { + "name": "restriction", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "valid_at": { + "name": "valid_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "valid_until": { + "name": "valid_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.split_group_areas": { + "name": "split_group_areas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "area_id": { + "name": "area_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "split_group_areas_group_id_split_groups_id_fk": { + "name": "split_group_areas_group_id_split_groups_id_fk", + "tableFrom": "split_group_areas", + "tableTo": "split_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "split_group_areas_area_id_area_metadata_id_fk": { + "name": "split_group_areas_area_id_area_metadata_id_fk", + "tableFrom": "split_group_areas", + "tableTo": "area_metadata", + "columnsFrom": [ + "area_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.split_groups": { + "name": "split_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "split_id": { + "name": "split_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "split_groups_split_id_splits_id_fk": { + "name": "split_groups_split_id_splits_id_fk", + "tableFrom": "split_groups", + "tableTo": "splits", + "columnsFrom": [ + "split_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.splits": { + "name": "splits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_session": { + "name": "user_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 5ceec70..9daca86 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1738556826419, "tag": "0004_zippy_zarek", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1738620021149, + "tag": "0005_wise_starfox", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/components/form/MultiSelect.svelte b/src/lib/components/form/MultiSelect.svelte new file mode 100644 index 0000000..162d997 --- /dev/null +++ b/src/lib/components/form/MultiSelect.svelte @@ -0,0 +1,97 @@ + + +
+ + + {#if isOpen} +
+
+ +
+
+ {#each filteredOptions as option} + + {/each} +
+
+ {/if} +
diff --git a/src/lib/components/map/AirspaceMap.svelte b/src/lib/components/map/AirspaceMap.svelte index 4036e7c..5ecb031 100644 --- a/src/lib/components/map/AirspaceMap.svelte +++ b/src/lib/components/map/AirspaceMap.svelte @@ -14,6 +14,7 @@ import MdiIcon from '../MdiIcon.svelte'; import Legend from './Legend.svelte'; import SplitName from '$lib/components/splits/SplitName.svelte'; + import MultiSelect from '$lib/components/form/MultiSelect.svelte'; interface Area { id: string; @@ -65,9 +66,26 @@ }[]; } - let { splits = [], staticElementGroups = [] } = $props<{ + interface OverlayGroup { + id: string; + name: string; + isPublished: boolean; + components: { + id: string; + name: string; + geojson: object; + color: string; + }[]; + } + + let { + splits = [], + staticElementGroups = [], + overlayGroups = [] + } = $props<{ splits?: Split[]; staticElementGroups: StaticElementGroup[]; + overlayGroups: OverlayGroup[]; }>(); let L: typeof import('leaflet') | undefined; @@ -80,8 +98,9 @@ let combinedSplitLayer: LayerGroup | undefined; let controllerLayer: LayerGroup | undefined; let staticElementLayer: LayerGroup | undefined; + let overlayLayer: LayerGroup | undefined; - // Use Session Storage to persist settings - directly use the returned value + // Use Session Storage to persist settings let settings = $state( useSessionStorage('mapSettings', { showTiles: true, @@ -104,6 +123,9 @@ useSessionStorage('mapStaticElements', {}) ); + // state for tracking overlay visibility + let showOverlays = $state>(useSessionStorage('mapOverlays', {})); + let selectedSplit = $state(null); let isDropdownOpen = $state(false); @@ -193,8 +215,10 @@ initializeLayerGroups(); initializeThemeObserver(); initializeStaticElements(); + initializeOverlays(); await initializeSplits(); await renderStaticElements(showStaticElements); + await renderOverlays(showOverlays); } }); @@ -207,6 +231,15 @@ }); } + function initializeOverlays() { + // Add each overlay to the toggle state + overlayGroups.forEach((g: OverlayGroup) => { + if (!Object.keys(showOverlays).includes(g.id)) { + showOverlays[g.id] = false; + } + }); + } + function initializeTileLayers() { const lightTiles = L!.tileLayer( 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', @@ -250,6 +283,7 @@ combinedSplitLayer = L!.layerGroup().addTo(map!); controllerLayer = L!.layerGroup().addTo(map!); staticElementLayer = L!.layerGroup().addTo(map!); + overlayLayer = L!.layerGroup().addTo(map!); } function initializeThemeObserver() { @@ -451,6 +485,81 @@ renderStaticElements({ ...showStaticElements }); }); + async function renderDynamicGeoJson(layer: any, component: any) { + const geojsonData = component.geojson as GeoJSON.FeatureCollection; + + // Lines Layer + const lineFeatures = geojsonData.features.filter( + (f) => f.geometry.type === 'LineString' || f.geometry.type === 'MultiLineString' + ); + if (lineFeatures.length > 0) { + const lineLayer = L!.geoJSON( + { + type: 'FeatureCollection', + features: lineFeatures + }, + { + style: { + color: component.color, + weight: component.settings?.weight ?? 1, + opacity: component.settings?.opacity ?? 0.8, + lineCap: component.settings?.lineCap ?? 'round', + lineJoin: component.settings?.lineJoin ?? 'round' + } + } + ); + lineLayer.addTo(layer!); + } + + // Points Layer + const pointFeatures = geojsonData.features.filter((f) => f.geometry.type === 'Point'); + if (pointFeatures.length > 0) { + L! + .geoJSON( + { + type: 'FeatureCollection', + features: pointFeatures + }, + { + pointToLayer: (_feature, latlng) => { + return L!.circleMarker(latlng, { + radius: component.settings?.radius ?? 2, + fillColor: component.color, + color: component.color, + weight: component.settings?.weight ?? 1, + opacity: component.settings?.opacity ?? 0.8, + fillOpacity: component.settings?.fillOpacity ?? 0.8 + }); + } + } + ) + .addTo(layer!); + } + + // Polygons + const polygonFeatures = geojsonData.features.filter((f) => f.geometry.type === 'Polygon'); + if (polygonFeatures.length > 0) { + L! + .geoJSON( + { + type: 'FeatureCollection', + features: polygonFeatures + }, + { + style: { + color: component.color, + weight: component.settings?.weight ?? 1, + opacity: component.settings?.opacity ?? 0.8, + fillOpacity: component.settings?.fillOpacity ?? 0.8, + lineCap: component.settings?.lineCap ?? 'round', + lineJoin: component.settings?.lineJoin ?? 'round' + } + } + ) + .addTo(layer!); + } + } + async function renderStaticElements(visibility: Record) { if (!L || !map || !staticElementLayer) return; @@ -459,78 +568,21 @@ staticElementGroups.forEach((group: StaticElementGroup) => { if (visibility[group.id]) { group.components.forEach((component) => { - const geojsonData = component.geojson as GeoJSON.FeatureCollection; - - // Lines Layer - const lineFeatures = geojsonData.features.filter( - (f) => f.geometry.type === 'LineString' || f.geometry.type === 'MultiLineString' - ); - if (lineFeatures.length > 0) { - const lineLayer = L!.geoJSON( - { - type: 'FeatureCollection', - features: lineFeatures - }, - { - style: { - color: component.color, - weight: component.settings?.weight ?? 1, - opacity: component.settings?.opacity ?? 0.8, - lineCap: component.settings?.lineCap ?? 'round', - lineJoin: component.settings?.lineJoin ?? 'round' - } - } - ); - lineLayer.addTo(staticElementLayer!); - } + renderDynamicGeoJson(staticElementLayer, component); + }); + } + }); + } - // Points Layer - const pointFeatures = geojsonData.features.filter((f) => f.geometry.type === 'Point'); - if (pointFeatures.length > 0) { - L! - .geoJSON( - { - type: 'FeatureCollection', - features: pointFeatures - }, - { - pointToLayer: (_feature, latlng) => { - return L!.circleMarker(latlng, { - radius: component.settings?.radius ?? 2, - fillColor: component.color, - color: component.color, - weight: component.settings?.weight ?? 1, - opacity: component.settings?.opacity ?? 0.8, - fillOpacity: component.settings?.fillOpacity ?? 0.8 - }); - } - } - ) - .addTo(staticElementLayer!); - } + async function renderOverlays(visibility: Record) { + if (!L || !map || !overlayLayer) return; - // Polygons - const polygonFeatures = geojsonData.features.filter((f) => f.geometry.type === 'Polygon'); - if (polygonFeatures.length > 0) { - L! - .geoJSON( - { - type: 'FeatureCollection', - features: polygonFeatures - }, - { - style: { - color: component.color, - weight: component.settings?.weight ?? 1, - opacity: component.settings?.opacity ?? 0.8, - fillOpacity: component.settings?.fillOpacity ?? 0.8, - lineCap: component.settings?.lineCap ?? 'round', - lineJoin: component.settings?.lineJoin ?? 'round' - } - } - ) - .addTo(staticElementLayer!); - } + overlayLayer.clearLayers(); + + overlayGroups.forEach((group: OverlayGroup) => { + if (visibility[group.id]) { + group.components.forEach((component) => { + renderDynamicGeoJson(overlayLayer, component); }); } }); @@ -652,14 +704,6 @@ } }); - function handleCreateClick() { - goto('/admin/splits/create'); - } - - function handleEditClick() { - goto(`/admin/splits/${selectedSplit}/edit`); - } - function getTagMenuActions() { return getTagsAndColors().map((tag) => ({ text: tag.tag.toUpperCase(), @@ -710,6 +754,16 @@ return baseActions; }); + + $effect(() => { + renderOverlays({ ...showOverlays }); + }); + + function handleOverlayChange(selectedIds: string[]) { + Object.keys(showOverlays).forEach((k) => { + showOverlays[k] = selectedIds.includes(k); + }); + }
@@ -862,8 +916,24 @@ class:opacity-0={!showMobileControls} >
-
+
+ + {#if overlayGroups.length > 0} + g.isPublished || getUserInfo()?.isAdmin) + .map((g: OverlayGroup) => ({ + id: g.id, + label: g.name + }))} + selected={Object.entries(showOverlays) + .filter(([_, visible]) => visible) + .map(([id]) => id)} + onChange={handleOverlayChange} + placeholder="Configure Overlays" + /> + {/if}
@@ -887,9 +957,27 @@