diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.js b/src/Map/src/Bridge/Google/assets/dist/map_controller.js index 5e18af2261b..469052d6fff 100644 --- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js +++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js @@ -1,8 +1,68 @@ -import AbstractMapController from '@symfony/ux-map'; +import { Controller } from '@hotwired/stimulus'; import { Loader } from '@googlemaps/js-api-loader'; +let default_1$1 = class default_1 extends Controller { + constructor() { + super(...arguments); + this.markers = []; + this.infoWindows = []; + this.polygons = []; + this.polylines = []; + } + connect() { + const { center, zoom, options, markers, polygons, polylines, fitBoundsToMarkers } = this.viewValue; + this.dispatchEvent('pre-connect', { options }); + this.map = this.doCreateMap({ center, zoom, options }); + markers.forEach((marker) => this.createMarker(marker)); + polygons.forEach((polygon) => this.createPolygon(polygon)); + polylines.forEach((polyline) => this.createPolyline(polyline)); + if (fitBoundsToMarkers) { + this.doFitBoundsToMarkers(); + } + this.dispatchEvent('connect', { + map: this.map, + markers: this.markers, + polygons: this.polygons, + polylines: this.polylines, + infoWindows: this.infoWindows, + }); + } + createMarker(definition) { + this.dispatchEvent('marker:before-create', { definition }); + const marker = this.doCreateMarker(definition); + this.dispatchEvent('marker:after-create', { marker }); + this.markers.push(marker); + return marker; + } + createPolygon(definition) { + this.dispatchEvent('polygon:before-create', { definition }); + const polygon = this.doCreatePolygon(definition); + this.dispatchEvent('polygon:after-create', { polygon }); + this.polygons.push(polygon); + return polygon; + } + createPolyline(definition) { + this.dispatchEvent('polyline:before-create', { definition }); + const polyline = this.doCreatePolyline(definition); + this.dispatchEvent('polyline:after-create', { polyline }); + this.polylines.push(polyline); + return polyline; + } + createInfoWindow({ definition, element, }) { + this.dispatchEvent('info-window:before-create', { definition, element }); + const infoWindow = this.doCreateInfoWindow({ definition, element }); + this.dispatchEvent('info-window:after-create', { infoWindow, element }); + this.infoWindows.push(infoWindow); + return infoWindow; + } +}; +default_1$1.values = { + providerOptions: Object, + view: Object, +}; + let _google; -class default_1 extends AbstractMapController { +class default_1 extends default_1$1 { async connect() { if (!_google) { _google = { maps: {} }; diff --git a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts index ec688f28c4a..58a67418063 100644 --- a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts +++ b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts @@ -41,7 +41,7 @@ describe('GoogleMapsController', () => { data-controller="check google" style="height: 700px; margin: 10px" data-google-provider-options-value="{"version":"weekly","libraries":["maps","marker"],"apiKey":""}" - data-google-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"mapId":"YOUR_MAP_ID","gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControl":true,"zoomControlOptions":{"position":22},"mapTypeControl":true,"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControl":true,"streetViewControlOptions":{"position":22},"fullscreenControl":true,"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}],"polygons":[]}polylines":[]}" + data-google-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"mapId":"YOUR_MAP_ID","gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControl":true,"zoomControlOptions":{"position":22},"mapTypeControl":true,"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControl":true,"streetViewControlOptions":{"position":22},"fullscreenControl":true,"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}],"polygons":[]},"polylines":[]}" > `); }); diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts new file mode 100644 index 00000000000..f5dd9682125 --- /dev/null +++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts @@ -0,0 +1,30 @@ +import AbstractMapController from '@symfony/ux-map'; +import type { Point, MarkerDefinition, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map'; +import 'leaflet/dist/leaflet.min.css'; +import * as L from 'leaflet'; +import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions, PolygonOptions, PolylineOptions } from 'leaflet'; +type MapOptions = Pick & { + tileLayer: { + url: string; + attribution: string; + options: Record; + }; +}; +export default class extends AbstractMapController { + connect(): void; + protected dispatchEvent(name: string, payload?: Record): void; + protected doCreateMap({ center, zoom, options, }: { + center: Point | null; + zoom: number | null; + options: MapOptions; + }): L.Map; + protected doCreateMarker(definition: MarkerDefinition): L.Marker; + protected doCreatePolygon(definition: PolygonDefinition): L.Polygon; + protected doCreatePolyline(definition: PolylineDefinition): L.Polyline; + protected doCreateInfoWindow({ definition, element, }: { + definition: MarkerDefinition['infoWindow'] | PolygonDefinition['infoWindow'] | PolylineDefinition['infoWindow']; + element: L.Marker | L.Polygon | L.Polyline; + }): L.Popup; + protected doFitBoundsToMarkers(): void; +} +export {}; diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js new file mode 100644 index 00000000000..0087754f43d --- /dev/null +++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js @@ -0,0 +1,90 @@ +import AbstractMapController from '@symfony/ux-map'; +import 'leaflet/dist/leaflet.min.css'; +import * as L from 'leaflet'; + +class map_controller extends AbstractMapController { + connect() { + L.Marker.prototype.options.icon = L.divIcon({ + html: '', + iconSize: [25, 41], + iconAnchor: [12.5, 41], + popupAnchor: [0, -41], + className: '', + }); + super.connect(); + } + dispatchEvent(name, payload = {}) { + this.dispatch(name, { + prefix: 'ux:map', + detail: { + ...payload, + L, + }, + }); + } + doCreateMap({ center, zoom, options, }) { + const map = L.map(this.element, { + ...options, + center: center === null ? undefined : center, + zoom: zoom === null ? undefined : zoom, + }); + L.tileLayer(options.tileLayer.url, { + attribution: options.tileLayer.attribution, + ...options.tileLayer.options, + }).addTo(map); + return map; + } + doCreateMarker(definition) { + const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition; + const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map); + if (infoWindow) { + this.createInfoWindow({ definition: infoWindow, element: marker }); + } + return marker; + } + doCreatePolygon(definition) { + const { points, title, infoWindow, rawOptions = {} } = definition; + const polygon = L.polygon(points, { ...rawOptions }).addTo(this.map); + if (title) { + polygon.bindPopup(title); + } + if (infoWindow) { + this.createInfoWindow({ definition: infoWindow, element: polygon }); + } + return polygon; + } + doCreatePolyline(definition) { + const { points, title, infoWindow, rawOptions = {} } = definition; + const polyline = L.polyline(points, { ...rawOptions }).addTo(this.map); + if (title) { + polyline.bindPopup(title); + } + if (infoWindow) { + this.createInfoWindow({ definition: infoWindow, element: polyline }); + } + return polyline; + } + doCreateInfoWindow({ definition, element, }) { + const { headerContent, content, rawOptions = {}, ...otherOptions } = definition; + element.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions }); + if (definition.opened) { + element.openPopup(); + } + const popup = element.getPopup(); + if (!popup) { + throw new Error('Unable to get the Popup associated with the element.'); + } + return popup; + } + doFitBoundsToMarkers() { + if (this.markers.length === 0) { + return; + } + this.map.fitBounds(this.markers.map((marker) => { + const position = marker.getLatLng(); + return [position.lat, position.lng]; + })); + } +} + +export { map_controller as default }; diff --git a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts index 2fb368467e8..8983aaee1a5 100644 --- a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts +++ b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts @@ -41,7 +41,7 @@ describe('LeafletController', () => { data-controller="check leaflet" style="height: 700px; margin: 10px" data-leaflet-provider-options-value="{}" - data-leaflet-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"tileLayer":{"url":"https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png","attribution":"\u00a9 <a href=\"https:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a>","options":{}}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}],"polygons":[]}polylines":[]}" + data-leaflet-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"tileLayer":{"url":"https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png","attribution":"\u00a9 <a href=\"https:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a>","options":{}}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}],"polygons":[]},"polylines":[]}" > `); }); diff --git a/src/Map/src/Twig/MapRuntime.php b/src/Map/src/Twig/MapRuntime.php index 6dca2c53b4e..a729e46e31c 100644 --- a/src/Map/src/Twig/MapRuntime.php +++ b/src/Map/src/Twig/MapRuntime.php @@ -58,11 +58,11 @@ public function renderMap( foreach ($markers ?? [] as $marker) { $map->addMarker(Marker::fromArray($marker)); } - foreach ($polygons ?? [] as $polygons) { - $map->addPolygon(Polygon::fromArray($polygons)); + foreach ($polygons ?? [] as $polygon) { + $map->addPolygon(Polygon::fromArray($polygon)); } - foreach ($polylines ?? [] as $polylines) { - $map->addPolyline(Polyline::fromArray($polylines)); + foreach ($polylines ?? [] as $polyline) { + $map->addPolyline(Polyline::fromArray($polyline)); } if (null !== $center) { $map->center(Point::fromArray($center));