Improve map actions, tidy up some parts of the map component and ensure settings update properly
This commit is contained in:
parent
3fb71f91ef
commit
52c8742067
|
@ -2558,7 +2558,7 @@
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On marker click",
|
"label": "On marker click",
|
||||||
"key": "onMarkerClick",
|
"key": "onClickMarker",
|
||||||
"context": [
|
"context": [
|
||||||
{
|
{
|
||||||
"label": "Clicked marker",
|
"label": "Clicked marker",
|
||||||
|
@ -2566,58 +2566,66 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Enable creating markers",
|
||||||
|
"key": "creationEnabled",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On map click",
|
"label": "On create marker",
|
||||||
"key": "onMapClick",
|
"key": "onCreateMarker",
|
||||||
|
"dependsOn": "creationEnabled",
|
||||||
"context": [
|
"context": [
|
||||||
{
|
{
|
||||||
"label": "Clicked latitude",
|
"label": "New marker latitude",
|
||||||
"key": "lat"
|
"key": "lat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Clicked longitude",
|
"label": "New marker longitude",
|
||||||
"key": "lng"
|
"key": "lng"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Enable Fullscreen",
|
"label": "Enable fullscreen",
|
||||||
"key": "fullScreenEnabled",
|
"key": "fullScreenEnabled",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Enable Location",
|
"label": "Enable location",
|
||||||
"key": "locationEnabled",
|
"key": "locationEnabled",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Enable Zoom",
|
"label": "Enable zoom",
|
||||||
"key": "zoomEnabled",
|
"key": "zoomEnabled",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"label": "Zoom Level (0-100)",
|
|
||||||
"key": "zoomLevel",
|
|
||||||
"defaultValue": 72,
|
|
||||||
"max": 100,
|
|
||||||
"min": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Tile URL",
|
"label": "Tile URL",
|
||||||
"key": "tileURL",
|
"key": "tileURL",
|
||||||
"defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
"defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Default Location",
|
"label": "Default Location (when empty)",
|
||||||
"key": "defaultLocation",
|
"key": "defaultLocation",
|
||||||
"defaultValue": "51.5072,-0.1276"
|
"placeholder": "51.5072,-0.1276"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Default Location Zoom Level (0-100)",
|
||||||
|
"key": "zoomLevel",
|
||||||
|
"placeholder": 50,
|
||||||
|
"max": 100,
|
||||||
|
"min": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import L from "leaflet"
|
import L from "leaflet"
|
||||||
import sanitizeHtml from "sanitize-html"
|
import sanitizeHtml from "sanitize-html"
|
||||||
import "leaflet/dist/leaflet.css"
|
import "leaflet/dist/leaflet.css"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers, Button } from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import {
|
import {
|
||||||
FullScreenControl,
|
FullScreenControl,
|
||||||
LocationControl,
|
LocationControl,
|
||||||
|
@ -24,91 +24,16 @@
|
||||||
export let defaultLocation
|
export let defaultLocation
|
||||||
export let tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
export let tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
export let mapAttribution
|
export let mapAttribution
|
||||||
export let onMarkerClick
|
export let creationEnabled = false
|
||||||
export let onMapClick
|
export let onClickMarker
|
||||||
|
export let onCreateMarker
|
||||||
|
|
||||||
const { styleable, notificationStore } = getContext("sdk")
|
const { styleable, notificationStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const embeddedMapId = `${Helpers.uuid()}-wrapper`
|
const embeddedMapId = `${Helpers.uuid()}-wrapper`
|
||||||
|
|
||||||
let cachedDeviceCoordinates
|
|
||||||
const fallbackCoordinates = [51.5072, -0.1276] //London
|
|
||||||
|
|
||||||
let mapInstance
|
|
||||||
let mapMarkerGroup = new L.FeatureGroup()
|
|
||||||
let mapMarkers = []
|
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
let minZoomLevel = 0
|
|
||||||
let maxZoomLevel = 18
|
|
||||||
let adjustedZoomLevel = !Number.isInteger(zoomLevel)
|
|
||||||
? 72
|
|
||||||
: Math.round(zoomLevel * (maxZoomLevel / 100))
|
|
||||||
|
|
||||||
$: zoomControlUpdated(mapInstance, zoomEnabled)
|
|
||||||
$: locationControlUpdated(mapInstance, locationEnabled)
|
|
||||||
$: fullScreenControlUpdated(mapInstance, fullScreenEnabled)
|
|
||||||
$: updateMapDimensions(
|
|
||||||
mapInstance,
|
|
||||||
$component.styles.normal.width,
|
|
||||||
$component.styles.normal.height
|
|
||||||
)
|
|
||||||
$: addMapMarkers(
|
|
||||||
mapInstance,
|
|
||||||
dataProvider?.rows,
|
|
||||||
latitudeKey,
|
|
||||||
longitudeKey,
|
|
||||||
titleKey,
|
|
||||||
onMarkerClick
|
|
||||||
)
|
|
||||||
$: if (!loaded && typeof mapInstance === "object" && mapMarkers.length > 0) {
|
|
||||||
loaded = true
|
|
||||||
mapInstance.setZoom(0)
|
|
||||||
mapInstance.fitBounds(mapMarkerGroup.getBounds(), {
|
|
||||||
paddingTopLeft: [0, 24],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateMapDimensions = mapInstance => {
|
|
||||||
if (typeof mapInstance !== "object") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mapInstance.invalidateSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
let isValidLatitude = value => {
|
|
||||||
return !isNaN(value) && value > -90 && value < 90
|
|
||||||
}
|
|
||||||
|
|
||||||
let isValidLongitude = value => {
|
|
||||||
return !isNaN(value) && value > -180 && value < 180
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDefaultLocation = defaultLocation => {
|
|
||||||
if (typeof defaultLocation !== "string") {
|
|
||||||
return fallbackCoordinates
|
|
||||||
}
|
|
||||||
let defaultLocationParts = defaultLocation.split(",")
|
|
||||||
if (defaultLocationParts.length !== 2) {
|
|
||||||
return fallbackCoordinates
|
|
||||||
}
|
|
||||||
|
|
||||||
let parsedDefaultLatitude = parseFloat(defaultLocationParts[0].trim())
|
|
||||||
let parsedDefaultLongitude = parseFloat(defaultLocationParts[1].trim())
|
|
||||||
|
|
||||||
return isValidLatitude(parsedDefaultLatitude) === true &&
|
|
||||||
isValidLongitude(parsedDefaultLongitude) === true
|
|
||||||
? [parsedDefaultLatitude, parsedDefaultLongitude]
|
|
||||||
: fallbackCoordinates
|
|
||||||
}
|
|
||||||
|
|
||||||
$: defaultCoordinates =
|
|
||||||
mapMarkers.length > 0
|
|
||||||
? parseDefaultLocation(defaultLocation)
|
|
||||||
: fallbackCoordinates
|
|
||||||
|
|
||||||
// Map Button Controls
|
// Map Button Controls
|
||||||
let locationControl = new LocationControl({
|
const locationControl = new LocationControl({
|
||||||
position: "bottomright",
|
position: "bottomright",
|
||||||
onLocationFail: err => {
|
onLocationFail: err => {
|
||||||
if (err.code === GeolocationPositionError.PERMISSION_DENIED) {
|
if (err.code === GeolocationPositionError.PERMISSION_DENIED) {
|
||||||
|
@ -134,13 +59,135 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
let fullScreenControl = new FullScreenControl({
|
const fullScreenControl = new FullScreenControl({
|
||||||
position: "topright",
|
position: "topright",
|
||||||
})
|
})
|
||||||
let zoomControl = L.control.zoom({
|
const zoomControl = L.control.zoom({
|
||||||
position: "bottomright",
|
position: "bottomright",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Map and marker configuration
|
||||||
|
const defaultMarkerOptions = {
|
||||||
|
html:
|
||||||
|
'<div><svg width="26px" height="26px" class="spectrum-Icon" focusable="false" stroke="#b12b27" stroke-width="1%">' +
|
||||||
|
'<use xlink:href="#spectrum-icon-18-Location" /></svg></div>',
|
||||||
|
className: "embedded-map-marker",
|
||||||
|
iconSize: [26, 26],
|
||||||
|
iconAnchor: [13, 26],
|
||||||
|
popupAnchor: [0, -13],
|
||||||
|
}
|
||||||
|
const mapMarkerOptions = {
|
||||||
|
icon: L.divIcon(defaultMarkerOptions),
|
||||||
|
draggable: false,
|
||||||
|
alt: "Location Marker",
|
||||||
|
}
|
||||||
|
const candidateMarkerOptions = {
|
||||||
|
icon: L.divIcon({
|
||||||
|
...defaultMarkerOptions,
|
||||||
|
className: "embedded-map-marker--candidate",
|
||||||
|
}),
|
||||||
|
draggable: false,
|
||||||
|
alt: "Location Marker",
|
||||||
|
}
|
||||||
|
const mapOptions = {
|
||||||
|
fullScreen: false,
|
||||||
|
zoomControl: false,
|
||||||
|
scrollWheelZoom: zoomEnabled,
|
||||||
|
minZoomLevel,
|
||||||
|
maxZoomLevel,
|
||||||
|
}
|
||||||
|
const fallbackCoordinates = [51.5072, -0.1276] //London
|
||||||
|
|
||||||
|
let mapInstance
|
||||||
|
let mapMarkerGroup = new L.FeatureGroup()
|
||||||
|
let candidateMarkerGroup = new L.FeatureGroup()
|
||||||
|
let candidateMarkerPosition
|
||||||
|
let mounted = false
|
||||||
|
let initialMarkerZoomCompleted = false
|
||||||
|
let minZoomLevel = 0
|
||||||
|
let maxZoomLevel = 18
|
||||||
|
let cachedDeviceCoordinates
|
||||||
|
|
||||||
|
$: validRows = getValidRows(dataProvider?.rows, latitudeKey, longitudeKey)
|
||||||
|
$: safeZoomLevel = parseZoomLevel(zoomLevel)
|
||||||
|
$: defaultCoordinates = parseDefaultLocation(defaultLocation)
|
||||||
|
$: initMap(tileURL, mapAttribution, safeZoomLevel)
|
||||||
|
$: zoomControlUpdated(mapInstance, zoomEnabled)
|
||||||
|
$: locationControlUpdated(mapInstance, locationEnabled)
|
||||||
|
$: fullScreenControlUpdated(mapInstance, fullScreenEnabled)
|
||||||
|
$: width = $component.styles.normal.width
|
||||||
|
$: height = $component.styles.normal.height
|
||||||
|
$: width, height, mapInstance?.invalidateSize()
|
||||||
|
$: defaultCoordinates, resetView()
|
||||||
|
$: addMapMarkers(
|
||||||
|
mapInstance,
|
||||||
|
validRows,
|
||||||
|
latitudeKey,
|
||||||
|
longitudeKey,
|
||||||
|
titleKey,
|
||||||
|
onClickMarker
|
||||||
|
)
|
||||||
|
|
||||||
|
const isValidLatitude = value => {
|
||||||
|
return !isNaN(value) && value > -90 && value < 90
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidLongitude = value => {
|
||||||
|
return !isNaN(value) && value > -180 && value < 180
|
||||||
|
}
|
||||||
|
|
||||||
|
const getValidRows = (rows, latKey, lngKey) => {
|
||||||
|
if (!rows?.length || !latKey || !lngKey) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return rows.filter(row => {
|
||||||
|
return isValidLatitude(row[latKey]) && isValidLongitude(row[lngKey])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseZoomLevel = zoomLevel => {
|
||||||
|
let zoom = zoomLevel
|
||||||
|
if (zoom == null || isNaN(zoom)) {
|
||||||
|
zoom = 50
|
||||||
|
} else {
|
||||||
|
zoom = parseFloat(zoom)
|
||||||
|
zoom = Math.max(0, Math.min(100, zoom))
|
||||||
|
}
|
||||||
|
return Math.round((zoom * maxZoomLevel) / 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDefaultLocation = defaultLocation => {
|
||||||
|
if (typeof defaultLocation !== "string") {
|
||||||
|
return fallbackCoordinates
|
||||||
|
}
|
||||||
|
let defaultLocationParts = defaultLocation.split(",")
|
||||||
|
if (defaultLocationParts.length !== 2) {
|
||||||
|
return fallbackCoordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedDefaultLatitude = parseFloat(defaultLocationParts[0].trim())
|
||||||
|
let parsedDefaultLongitude = parseFloat(defaultLocationParts[1].trim())
|
||||||
|
|
||||||
|
return isValidLatitude(parsedDefaultLatitude) === true &&
|
||||||
|
isValidLongitude(parsedDefaultLongitude) === true
|
||||||
|
? [parsedDefaultLatitude, parsedDefaultLongitude]
|
||||||
|
: fallbackCoordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetView = () => {
|
||||||
|
if (!mapInstance) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (mapMarkerGroup.getLayers().length) {
|
||||||
|
mapInstance.setZoom(0)
|
||||||
|
mapInstance.fitBounds(mapMarkerGroup.getBounds(), {
|
||||||
|
paddingTopLeft: [0, 24],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
mapInstance.setView(defaultCoordinates, safeZoomLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const locationControlUpdated = (mapInstance, locationEnabled) => {
|
const locationControlUpdated = (mapInstance, locationEnabled) => {
|
||||||
if (typeof mapInstance !== "object") {
|
if (typeof mapInstance !== "object") {
|
||||||
return
|
return
|
||||||
|
@ -176,51 +223,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Map icon and marker configuration
|
|
||||||
const mapIconMarkup =
|
|
||||||
'<div><svg width="26px" height="26px" class="spectrum-Icon" focusable="false" stroke="#b12b27" stroke-width="1%">' +
|
|
||||||
'<use xlink:href="#spectrum-icon-18-Location" /></svg></div>'
|
|
||||||
const mapIcon = L.divIcon({
|
|
||||||
html: mapIconMarkup,
|
|
||||||
className: "embedded-map-marker",
|
|
||||||
iconSize: [26, 26],
|
|
||||||
iconAnchor: [13, 26],
|
|
||||||
popupAnchor: [0, -13],
|
|
||||||
})
|
|
||||||
const mapMarkerOptions = {
|
|
||||||
icon: mapIcon,
|
|
||||||
draggable: false,
|
|
||||||
alt: "Location Marker",
|
|
||||||
}
|
|
||||||
let mapOptions = {
|
|
||||||
fullScreen: false,
|
|
||||||
zoomControl: false,
|
|
||||||
scrollWheelZoom: zoomEnabled,
|
|
||||||
minZoomLevel,
|
|
||||||
maxZoomLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
const addMapMarkers = (
|
const addMapMarkers = (
|
||||||
mapInstance,
|
mapInstance,
|
||||||
rows,
|
validRows,
|
||||||
latKey,
|
latKey,
|
||||||
lngKey,
|
lngKey,
|
||||||
titleKey,
|
titleKey,
|
||||||
onClick
|
onClick
|
||||||
) => {
|
) => {
|
||||||
if (typeof mapInstance !== "object" || !rows || !latKey || !lngKey) {
|
if (!mapInstance || !validRows?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mapMarkerGroup.clearLayers()
|
mapMarkerGroup.clearLayers()
|
||||||
|
|
||||||
const validRows = rows.filter(row => {
|
|
||||||
return isValidLatitude(row[latKey]) && isValidLongitude(row[lngKey])
|
|
||||||
})
|
|
||||||
|
|
||||||
validRows.forEach(row => {
|
validRows.forEach(row => {
|
||||||
let markerCoords = [row[latKey], row[lngKey]]
|
let markerCoords = [row[latKey], row[lngKey]]
|
||||||
|
|
||||||
let marker = L.marker(markerCoords, mapMarkerOptions).addTo(mapInstance)
|
let marker = L.marker(markerCoords, mapMarkerOptions).addTo(mapInstance)
|
||||||
let markerContent = generateMarkerPopupContent(
|
let markerContent = generateMarkerPopupContent(
|
||||||
row[latKey],
|
row[latKey],
|
||||||
|
@ -242,57 +259,91 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mapMarkers = [...mapMarkers, marker]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Zoom to markers if this is the first time
|
||||||
|
if (!initialMarkerZoomCompleted) {
|
||||||
|
resetView()
|
||||||
|
initialMarkerZoomCompleted = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateMarkerPopupContent = (latitude, longitude, text) => {
|
const generateMarkerPopupContent = (latitude, longitude, text) => {
|
||||||
return text || latitude + "," + longitude
|
return text || latitude + "," + longitude
|
||||||
}
|
}
|
||||||
|
|
||||||
const initMap = () => {
|
const initMap = (tileURL, attribution, zoom) => {
|
||||||
const initCoords = defaultCoordinates
|
if (!mounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (mapInstance) {
|
||||||
|
mapInstance.remove()
|
||||||
|
}
|
||||||
mapInstance = L.map(embeddedMapId, mapOptions)
|
mapInstance = L.map(embeddedMapId, mapOptions)
|
||||||
mapMarkerGroup.addTo(mapInstance)
|
mapMarkerGroup.addTo(mapInstance)
|
||||||
|
candidateMarkerGroup.addTo(mapInstance)
|
||||||
|
|
||||||
const cleanAttribution = sanitizeHtml(mapAttribution, {
|
// Add attribution
|
||||||
|
const cleanAttribution = sanitizeHtml(attribution, {
|
||||||
allowedTags: ["a"],
|
allowedTags: ["a"],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
a: ["href", "target"],
|
a: ["href", "target"],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
L.tileLayer(tileURL, {
|
L.tileLayer(tileURL, {
|
||||||
attribution: "© " + cleanAttribution,
|
attribution: "© " + cleanAttribution,
|
||||||
zoom: adjustedZoomLevel,
|
zoom,
|
||||||
}).addTo(mapInstance)
|
}).addTo(mapInstance)
|
||||||
|
|
||||||
//Initialise the map
|
|
||||||
mapInstance.setView(initCoords, adjustedZoomLevel)
|
|
||||||
|
|
||||||
// Add click handler
|
// Add click handler
|
||||||
mapInstance?.on("click", e => {
|
mapInstance.on("click", handleMapClick)
|
||||||
console.log("map clicked!")
|
|
||||||
if (onMapClick) {
|
// Reset view
|
||||||
onMapClick({
|
resetView()
|
||||||
lat: e.latlng.lat,
|
|
||||||
lng: e.latlng.lng,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapAction = () => {
|
const handleMapClick = e => {
|
||||||
initMap()
|
if (!creationEnabled) {
|
||||||
return {
|
return
|
||||||
destroy() {
|
}
|
||||||
mapInstance.remove()
|
candidateMarkerGroup.clearLayers()
|
||||||
mapInstance = undefined
|
candidateMarkerPosition = [e.latlng.lat, e.latlng.lng]
|
||||||
},
|
let candidateMarker = L.marker(
|
||||||
|
candidateMarkerPosition,
|
||||||
|
candidateMarkerOptions
|
||||||
|
)
|
||||||
|
candidateMarker
|
||||||
|
.bindTooltip("New marker", {
|
||||||
|
permanent: true,
|
||||||
|
direction: "top",
|
||||||
|
offset: [0, -25],
|
||||||
|
})
|
||||||
|
.addTo(candidateMarkerGroup)
|
||||||
|
.on("click", clearCandidateMarker)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMarker = async () => {
|
||||||
|
if (!onCreateMarker) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await onCreateMarker({
|
||||||
|
lat: candidateMarkerPosition[0],
|
||||||
|
lng: candidateMarkerPosition[1],
|
||||||
|
})
|
||||||
|
if (res !== false) {
|
||||||
|
clearCandidateMarker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearCandidateMarker = () => {
|
||||||
|
candidateMarkerGroup.clearLayers()
|
||||||
|
candidateMarkerPosition = null
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
mounted = true
|
||||||
|
initMap(tileURL, mapAttribution, safeZoomLevel)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="embedded-map-wrapper map-default" use:styleable={$component.styles}>
|
<div class="embedded-map-wrapper map-default" use:styleable={$component.styles}>
|
||||||
|
@ -300,7 +351,14 @@
|
||||||
<div>{error}</div>
|
<div>{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div id={embeddedMapId} class="embedded embedded-map" use:mapAction />
|
<div id={embeddedMapId} class="embedded embedded-map" />
|
||||||
|
|
||||||
|
{#if candidateMarkerPosition}
|
||||||
|
<div class="button-container">
|
||||||
|
<Button secondary quiet on:click={clearCandidateMarker}>Cancel</Button>
|
||||||
|
<Button cta on:click={createMarker}>Create marker</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -318,6 +376,9 @@
|
||||||
.embedded-map :global(.embedded-map-marker) {
|
.embedded-map :global(.embedded-map-marker) {
|
||||||
color: #ee3b35;
|
color: #ee3b35;
|
||||||
}
|
}
|
||||||
|
.embedded-map :global(.embedded-map-marker--candidate) {
|
||||||
|
color: var(--primaryColor);
|
||||||
|
}
|
||||||
.embedded-map :global(.embedded-map-control) {
|
.embedded-map :global(.embedded-map-control) {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
@ -325,4 +386,12 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,30 +4,36 @@ const initialState = {
|
||||||
showConfirmation: false,
|
showConfirmation: false,
|
||||||
title: null,
|
title: null,
|
||||||
text: null,
|
text: null,
|
||||||
callback: null,
|
onConfirm: null,
|
||||||
|
onCancel: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const createConfirmationStore = () => {
|
const createConfirmationStore = () => {
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
|
|
||||||
const showConfirmation = (title, text, callback) => {
|
const showConfirmation = (title, text, onConfirm, onCancel) => {
|
||||||
store.set({
|
store.set({
|
||||||
showConfirmation: true,
|
showConfirmation: true,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
callback,
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const confirm = async () => {
|
const confirm = async () => {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
if (!state.showConfirmation || !state.callback) {
|
if (!state.showConfirmation || !state.onConfirm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.set(initialState)
|
store.set(initialState)
|
||||||
await state.callback()
|
await state.onConfirm()
|
||||||
}
|
}
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
|
const state = get(store)
|
||||||
store.set(initialState)
|
store.set(initialState)
|
||||||
|
if (state.onCancel) {
|
||||||
|
state.onCancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -331,33 +331,36 @@ export const enrichButtonActions = (actions, context) => {
|
||||||
// If this action is confirmable, show confirmation and await a
|
// If this action is confirmable, show confirmation and await a
|
||||||
// callback to execute further actions
|
// callback to execute further actions
|
||||||
if (action.parameters?.confirm) {
|
if (action.parameters?.confirm) {
|
||||||
const defaultText = confirmTextMap[action["##eventHandlerType"]]
|
return new Promise(resolve => {
|
||||||
const confirmText = action.parameters?.confirmText || defaultText
|
const defaultText = confirmTextMap[action["##eventHandlerType"]]
|
||||||
confirmationStore.actions.showConfirmation(
|
const confirmText = action.parameters?.confirmText || defaultText
|
||||||
action["##eventHandlerType"],
|
confirmationStore.actions.showConfirmation(
|
||||||
confirmText,
|
action["##eventHandlerType"],
|
||||||
async () => {
|
confirmText,
|
||||||
// When confirmed, execute this action immediately,
|
async () => {
|
||||||
// then execute the rest of the actions in the chain
|
// When confirmed, execute this action immediately,
|
||||||
const result = await callback()
|
// then execute the rest of the actions in the chain
|
||||||
if (result !== false) {
|
const result = await callback()
|
||||||
// Generate a new total context to pass into the next enrichment
|
if (result !== false) {
|
||||||
buttonContext.push(result)
|
// Generate a new total context to pass into the next enrichment
|
||||||
const newContext = { ...context, actions: buttonContext }
|
buttonContext.push(result)
|
||||||
|
const newContext = { ...context, actions: buttonContext }
|
||||||
|
|
||||||
// Enrich and call the next button action
|
// Enrich and call the next button action
|
||||||
const next = enrichButtonActions(
|
const next = enrichButtonActions(
|
||||||
actions.slice(i + 1),
|
actions.slice(i + 1),
|
||||||
newContext
|
newContext
|
||||||
)
|
)
|
||||||
await next()
|
resolve(await next())
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
resolve(false)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
})
|
||||||
|
|
||||||
// Stop enriching actions when encountering a confirmable action,
|
|
||||||
// as the callback continues the action chain
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-confirmable actions, execute the handler immediately
|
// For non-confirmable actions, execute the handler immediately
|
||||||
|
|
Loading…
Reference in New Issue