Files
windmill/frontend/src/lib/components/MapResult.svelte
T
Diego ImbertandClaude Opus 4.6 5d79f33590 Final Svelte 5 migration (#8211)
* Remove $$props.field usage

* Rename slots to ensure no hyphen

* _props

* _trigger

* OnSelectedIteration type correct capitalization

* rename _content

* Remove afterUpdate

* Migrate everything to svelte 5

* array bind

* Fix popover

* type never

* nit fixes

* Fixed many trivial errors

* onClick

* Fix errors

* use let:

* nit typing

* fix: wrap state_referenced_locally vars with untrack()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add untrack import

* Fix all syntax errors due to untrack migration

* Fix undefined errors

* Fix more undefined errors

* untrack(() => initialOpen)

* svelte-ignore

* Fix state_descriptors_fixed error in Chart.svelte

Use $state.snapshot() to pass plain copies of data/options to Chart.js
instead of $state proxies. Chart.js's listenArrayEvents tries to define
property descriptors on data arrays, which Svelte 5 proxies reject.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit typing

* Merge issue

* Fix "path is not set" error in resource picker / editor

* Fix InputTransformForm error when rerunning some flows

* fix npm run check

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-05 18:11:40 +01:00

159 lines
3.5 KiB
Svelte

<script lang="ts">
import { run } from 'svelte/legacy';
import { Map, View, Feature } from 'ol'
import { Fill, Stroke, Style, Text } from 'ol/style.js'
import { useGeographic } from 'ol/proj.js'
import { OSM, Vector as VectorSource } from 'ol/source.js'
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer.js'
import { Point } from 'ol/geom.js'
import { defaults as defaultControls } from 'ol/control.js'
import CircleStyle from 'ol/style/Circle.js'
interface Marker {
lon: number
lat: number
title?: string
radius?: number
color?: string
strokeWidth?: number
strokeColor?: string
}
interface Props {
lon?: number | undefined;
lat?: number | undefined;
zoom?: number | undefined;
markers?: Marker[] | string | undefined;
}
let {
lon = undefined,
lat = undefined,
zoom = undefined,
markers = undefined
}: Props = $props();
const LAYER_NAME = {
MARKER: 'Marker'
} as const
let map: Map | undefined = $state(undefined)
let mapElement: HTMLDivElement | undefined = $state(undefined)
function getLayersByName(name: keyof typeof LAYER_NAME) {
return map
?.getLayers()
?.getArray()
?.filter((l) => l.getProperties().name === LAYER_NAME[name])
}
function getMarkerArray(): Marker[] | undefined {
let array: Marker[] | undefined = undefined
try {
if (typeof markers === 'string') {
const json = JSON.parse(markers)
array = Array.isArray(json) ? json : [json]
} else {
array = markers
}
return array?.filter((m) => !isNaN(+m.lat) && !isNaN(+m.lon))
} catch (error) {
console.log(error)
return undefined
}
}
function createMarkerLayers() {
const markerArray = getMarkerArray()
return markerArray?.map((m) => {
return new VectorLayer({
properties: {
name: LAYER_NAME.MARKER
},
source: new VectorSource({
features: [
new Feature({
geometry: new Point([+m.lon, +m.lat]),
label: m.title,
name: m.title
})
]
}),
style: new Style({
image: new CircleStyle({
radius: m.radius ?? 5,
fill: new Fill({
color: m.color ?? '#dc2626'
}),
stroke: new Stroke({
color: m.strokeColor ?? '#fca5a5',
width: m.strokeWidth ?? 3
})
}),
text: new Text({
text: m.title,
offsetY: -15,
fill: new Fill({
color: '#000'
})
})
})
})
})
}
function updateMarkers() {
const layers = getLayersByName('MARKER')
if (layers?.length) {
layers.forEach((l) => map?.removeLayer(l))
}
createMarkerLayers()?.forEach((l) => map?.addLayer(l))
}
run(() => {
if (!map && mapElement) {
useGeographic()
map = new Map({
target: mapElement,
layers: [
new TileLayer({
source: new OSM()
}),
...(createMarkerLayers() || [])
],
view: new View({
center: [lon ?? 0, lat ?? 0],
zoom: zoom ?? 2
}),
controls: defaultControls({
attribution: false
})
})
if (lat && lon) {
map.getView().setCenter([lon, lat])
}
if (map && zoom) {
map.getView().setZoom(zoom)
}
if (map && markers) {
updateMarkers()
}
}
});
</script>
<div bind:this={mapElement} class="w-full h-[300px]"></div>
<style global lang="postcss">
.ol-overlaycontainer-stopevent {
@apply flex flex-col justify-start items-end;
}
.ol-control button {
@apply w-7 h-7 center-center bg-surface border text-secondary
rounded mt-1 mr-1 shadow duration-200 hover:bg-surface-hover focus:bg-surface-hover;
}
</style>