From e0ba5d4b02f4e6948611b4c426d3aff836b483a1 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 9 Aug 2024 23:05:40 +0200 Subject: [PATCH] [Map] Add support for `libraries` for Google Bridge, inject provider's SDK (`L` or `google`) to dispatched events --- .../assets/dist/abstract_map_controller.d.ts | 3 +- .../assets/dist/abstract_map_controller.js | 4 - src/Map/assets/src/abstract_map_controller.ts | 6 +- .../test/abstract_map_controller.test.ts | 8 ++ src/Map/src/Bridge/Google/README.md | 74 ++++++++++++++++--- .../Google/assets/dist/map_controller.d.ts | 3 +- .../Google/assets/dist/map_controller.js | 38 +++++++--- .../Google/assets/src/map_controller.ts | 55 ++++++++++---- .../Google/assets/test/map_controller.test.ts | 2 +- .../Google/src/Renderer/GoogleRenderer.php | 6 ++ .../src/Renderer/GoogleRendererFactory.php | 1 + .../tests/GoogleRendererFactoryTest.php | 9 ++- src/Map/src/Bridge/Leaflet/README.md | 53 +++++++++++++ .../Leaflet/assets/dist/map_controller.d.ts | 13 ++-- .../Leaflet/assets/dist/map_controller.js | 27 ++++--- .../Leaflet/assets/src/map_controller.ts | 45 +++++------ 16 files changed, 261 insertions(+), 86 deletions(-) diff --git a/src/Map/assets/dist/abstract_map_controller.d.ts b/src/Map/assets/dist/abstract_map_controller.d.ts index 07721103287..142c2f697b7 100644 --- a/src/Map/assets/dist/abstract_map_controller.d.ts +++ b/src/Map/assets/dist/abstract_map_controller.d.ts @@ -35,7 +35,6 @@ export default abstract class; protected infoWindows: Array; - initialize(): void; connect(): void; protected abstract doCreateMap({ center, zoom, options, }: { center: Point | null; @@ -53,5 +52,5 @@ export default abstract class): void; } diff --git a/src/Map/assets/dist/abstract_map_controller.js b/src/Map/assets/dist/abstract_map_controller.js index 324a29ce9fe..9d2e3024024 100644 --- a/src/Map/assets/dist/abstract_map_controller.js +++ b/src/Map/assets/dist/abstract_map_controller.js @@ -6,7 +6,6 @@ class default_1 extends Controller { this.markers = []; this.infoWindows = []; } - initialize() { } connect() { const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue; this.dispatchEvent('pre-connect', { options }); @@ -35,9 +34,6 @@ class default_1 extends Controller { this.infoWindows.push(infoWindow); return infoWindow; } - dispatchEvent(name, payload = {}) { - this.dispatch(name, { prefix: 'ux:map', detail: payload }); - } } default_1.values = { providerOptions: Object, diff --git a/src/Map/assets/src/abstract_map_controller.ts b/src/Map/assets/src/abstract_map_controller.ts index c97139b26f1..802b6477875 100644 --- a/src/Map/assets/src/abstract_map_controller.ts +++ b/src/Map/assets/src/abstract_map_controller.ts @@ -66,8 +66,6 @@ export default abstract class< protected markers: Array = []; protected infoWindows: Array = []; - initialize() {} - connect() { const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue; @@ -136,7 +134,5 @@ export default abstract class< protected abstract doFitBoundsToMarkers(): void; - private dispatchEvent(name: string, payload: Record = {}): void { - this.dispatch(name, { prefix: 'ux:map', detail: payload }); - } + protected abstract dispatchEvent(name: string, payload: Record): void; } diff --git a/src/Map/assets/test/abstract_map_controller.test.ts b/src/Map/assets/test/abstract_map_controller.test.ts index 1c1747718a0..0beadef2ee2 100644 --- a/src/Map/assets/test/abstract_map_controller.test.ts +++ b/src/Map/assets/test/abstract_map_controller.test.ts @@ -2,8 +2,16 @@ import { Application } from '@hotwired/stimulus'; import { getByTestId, waitFor } from '@testing-library/dom'; import { clearDOM, mountDOM } from '@symfony/stimulus-testing'; import AbstractMapController from '../src/abstract_map_controller.ts'; +import * as L from 'leaflet'; class MyMapController extends AbstractMapController { + protected dispatchEvent(name: string, payload: Record = {}): void { + this.dispatch(name, { + prefix: 'ux:map', + detail: payload, + }); + } + doCreateMap({ center, zoom, options }) { return { map: 'map', center, zoom, options }; } diff --git a/src/Map/src/Bridge/Google/README.md b/src/Map/src/Bridge/Google/README.md index 449db09b2ca..0d6bd01e0b3 100644 --- a/src/Map/src/Bridge/Google/README.md +++ b/src/Map/src/Bridge/Google/README.md @@ -10,19 +10,21 @@ UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default # With options UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?version=weekly UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?language=fr®ion=FR +UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default??libraries[]=geometry&libraries[]=places ``` Available options: -| Option | Description | Default | -|------------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------| -| `id` | The id of the script tag | `__googleMapsScriptId` | -| `language` | Force language, see [list of supported languages](https://developers.google.com/maps/faq#languagesupport) specified in the browser | The user's preferred language | -| `region` | Unicode region subtag identifiers compatible with [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) | | -| `nonce` | Use a cryptographic nonce attribute | | -| `retries` | The number of script load retries | 3 | -| `url` | Custom url to load the Google Maps API script | `https://maps.googleapis.com/maps/api/js` | -| `version` | The release channels or version numbers | `weekly` | +| Option | Description | Default | +|-------------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------| +| `id` | The id of the script tag | `__googleMapsScriptId` | +| `language` | Force language, see [list of supported languages](https://developers.google.com/maps/faq#languagesupport) specified in the browser | The user's preferred language | +| `region` | Unicode region subtag identifiers compatible with [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) | | +| `nonce` | Use a cryptographic nonce attribute | | +| `retries` | The number of script load retries | 3 | +| `url` | Custom url to load the Google Maps API script | `https://maps.googleapis.com/maps/api/js` | +| `version` | The release channels or version numbers | `weekly` | +| `libraries` | The additional libraries to load, see [list of supported libraries](https://googlemaps.github.io/js-api-loader/types/Library.html) | `['maps', 'marker']`, those two libraries are always loaded | ## Map options @@ -78,6 +80,60 @@ $googleOptions = (new GoogleOptions()) // Add the custom options to the map $map->options($googleOptions); ``` +## Use cases + +Below are some common or advanced use cases when using a map. + +### Customize the marker + +A common use case is to customize the marker. You can listen to the `ux:map:marker:before-create` event to customize the marker before it is created. + +Assuming you have a map with a custom controller: +```twig +{{ render_map(map, {'data-controller': 'my-map' }) }} +``` + +You can create a Stimulus controller to customize the markers before they are created: +```js +// assets/controllers/my_map_controller.js +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller +{ + connect() { + this.element.addEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate); + } + + disconnect() { + // Always remove listeners when the controller is disconnected + this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate); + } + + _onMarkerBeforeCreate(event) { + // You can access the marker definition and the google object + // Note: `definition.rawOptions` is the raw options object that will be passed to the `google.maps.Marker` constructor. + const { definition, google } = event.detail; + + // 1. To use a custom image for the marker + const beachFlagImg = document.createElement("img"); + // Note: instead of using an hardcoded URL, you can use the `extra` parameter from `new Marker()` (PHP) and access it here with `definition.extra`. + beachFlagImg.src = "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png"; + definition.rawOptions = { + content: beachFlagImg + } + + // 2. To use a custom glyph for the marker + const pinElement = new google.maps.marker.PinElement({ + // Note: instead of using an hardcoded URL, you can use the `extra` parameter from `new Marker()` (PHP) and access it here with `definition.extra`. + glyph: new URL('https://maps.gstatic.com/mapfiles/place_api/icons/v2/museum_pinlet.svg'), + glyphColor: "white", + }); + definition.rawOptions = { + content: pinElement.element, + } + } +} +``` ## Resources diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts index f3dc57d9057..9a8da864f15 100644 --- a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts +++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts @@ -7,8 +7,9 @@ export default class extends AbstractMapController; + providerOptionsValue: Pick; connect(): Promise; + protected dispatchEvent(name: string, payload?: Record): void; protected doCreateMap({ center, zoom, options, }: { center: Point | null; zoom: number | null; 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 03f2ed92142..5a74163291a 100644 --- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js +++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js @@ -1,24 +1,42 @@ import AbstractMapController from '@symfony/ux-map/abstract-map-controller'; import { Loader } from '@googlemaps/js-api-loader'; -let loader; -let library; +let _google; class default_1 extends AbstractMapController { async connect() { - if (!loader) { - loader = new Loader(this.providerOptionsValue); + if (!_google) { + _google = { maps: {} }; + let { libraries = [], ...loaderOptions } = this.providerOptionsValue; + const loader = new Loader(loaderOptions); + libraries = ['core', ...libraries.filter((library) => library !== 'core')]; + const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library))); + librariesImplementations.map((libraryImplementation, index) => { + const library = libraries[index]; + if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) { + _google.maps[library] = libraryImplementation; + } + else { + _google.maps = { ..._google.maps, ...libraryImplementation }; + } + }); } - const { Map: _Map, InfoWindow } = await loader.importLibrary('maps'); - const { AdvancedMarkerElement } = await loader.importLibrary('marker'); - library = { _Map, AdvancedMarkerElement, InfoWindow }; super.connect(); } + dispatchEvent(name, payload = {}) { + this.dispatch(name, { + prefix: 'ux:map', + detail: { + ...payload, + google: _google, + }, + }); + } doCreateMap({ center, zoom, options, }) { options.zoomControl = typeof options.zoomControlOptions !== 'undefined'; options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined'; options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined'; options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined'; - return new library._Map(this.element, { + return new _google.maps.Map(this.element, { ...options, center, zoom, @@ -26,7 +44,7 @@ class default_1 extends AbstractMapController { } doCreateMarker(definition) { const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition; - const marker = new library.AdvancedMarkerElement({ + const marker = new _google.maps.marker.AdvancedMarkerElement({ position, title, ...otherOptions, @@ -40,7 +58,7 @@ class default_1 extends AbstractMapController { } doCreateInfoWindow({ definition, marker, }) { const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition; - const infoWindow = new library.InfoWindow({ + const infoWindow = new _google.maps.InfoWindow({ headerContent: this.createTextOrElement(headerContent), content: this.createTextOrElement(content), ...otherOptions, diff --git a/src/Map/src/Bridge/Google/assets/src/map_controller.ts b/src/Map/src/Bridge/Google/assets/src/map_controller.ts index 5728b6bb759..1e1a1a06473 100644 --- a/src/Map/src/Bridge/Google/assets/src/map_controller.ts +++ b/src/Map/src/Bridge/Google/assets/src/map_controller.ts @@ -28,12 +28,7 @@ type MapOptions = Pick< | 'fullscreenControlOptions' >; -let loader: Loader; -let library: { - _Map: typeof google.maps.Map; - AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement; - InfoWindow: typeof google.maps.InfoWindow; -}; +let _google: typeof google; export default class extends AbstractMapController< MapOptions, @@ -47,21 +42,49 @@ export default class extends AbstractMapController< declare providerOptionsValue: Pick< LoaderOptions, - 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version' + 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version' | 'libraries' >; async connect() { - if (!loader) { - loader = new Loader(this.providerOptionsValue); + if (!_google) { + _google = { maps: {} }; + + let { libraries = [], ...loaderOptions } = this.providerOptionsValue; + + const loader = new Loader(loaderOptions); + + // We could have used `loader.load()` to correctly load libraries, but this method is deprecated in favor of `loader.importLibrary()`. + // But `loader.importLibrary()` is not a 1-1 replacement for `loader.load()`, we need to re-build the `google.maps` object ourselves, + // see https://github.com/googlemaps/js-api-loader/issues/837 for more information. + libraries = ['core', ...libraries.filter((library) => library !== 'core')]; // Ensure 'core' is loaded first + const librariesImplementations = await Promise.all( + libraries.map((library) => loader.importLibrary(library)) + ); + librariesImplementations.map((libraryImplementation, index) => { + const library = libraries[index]; + + // The following libraries are in a sub-namespace + if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) { + _google.maps[library] = libraryImplementation; + } else { + _google.maps = { ..._google.maps, ...libraryImplementation }; + } + }); } - const { Map: _Map, InfoWindow } = await loader.importLibrary('maps'); - const { AdvancedMarkerElement } = await loader.importLibrary('marker'); - library = { _Map, AdvancedMarkerElement, InfoWindow }; - super.connect(); } + protected dispatchEvent(name: string, payload: Record = {}): void { + this.dispatch(name, { + prefix: 'ux:map', + detail: { + ...payload, + google: _google, + }, + }); + } + protected doCreateMap({ center, zoom, @@ -77,7 +100,7 @@ export default class extends AbstractMapController< options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined'; options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined'; - return new library._Map(this.element, { + return new _google.maps.Map(this.element, { ...options, center, zoom, @@ -89,7 +112,7 @@ export default class extends AbstractMapController< ): google.maps.marker.AdvancedMarkerElement { const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition; - const marker = new library.AdvancedMarkerElement({ + const marker = new _google.maps.marker.AdvancedMarkerElement({ position, title, ...otherOptions, @@ -116,7 +139,7 @@ export default class extends AbstractMapController< }): google.maps.InfoWindow { const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition; - const infoWindow = new library.InfoWindow({ + const infoWindow = new _google.maps.InfoWindow({ headerContent: this.createTextOrElement(headerContent), content: this.createTextOrElement(content), ...otherOptions, 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 ebaf6375ab6..1db8edfff7b 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 @@ -40,7 +40,7 @@ describe('GoogleMapsController', () => { data-testid="map" data-controller="check google" style="height: 700px; margin: 10px" - data-google-provider-options-value="{"language":"fr","region":"FR","retries":10,"version":"weekly","apiKey":""}" + 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}}]}" > `); diff --git a/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php b/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php index fb214676dba..808278abbf3 100644 --- a/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php +++ b/src/Map/src/Bridge/Google/src/Renderer/GoogleRenderer.php @@ -37,6 +37,10 @@ public function __construct( private ?int $retries = null, private ?string $url = null, private ?string $version = null, + /** + * @var array<'core'|'maps'|'places'|'geocoding'|'routes'|'marker'|'geometry'|'elevation'|'streetView'|'journeySharing'|'drawing'|'visualization'> + */ + private array $libraries = [], ) { parent::__construct($stimulusHelper); } @@ -56,6 +60,7 @@ protected function getProviderOptions(): array 'retries' => $this->retries, 'url' => $this->url, 'version' => $this->version, + 'libraries' => $this->libraries, ]) + ['apiKey' => $this->apiKey]; } @@ -77,6 +82,7 @@ public function __toString(): string 'retries' => $this->retries, 'url' => $this->url, 'version' => $this->version, + 'libraries' => $this->libraries, ])) ); } diff --git a/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php b/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php index 04a39925050..a391f676152 100644 --- a/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php +++ b/src/Map/src/Bridge/Google/src/Renderer/GoogleRendererFactory.php @@ -41,6 +41,7 @@ public function create(Dsn $dsn): RendererInterface retries: $dsn->getOption('retries'), url: $dsn->getOption('url'), version: $dsn->getOption('version', 'weekly'), + libraries: ['maps', 'marker', ...$dsn->getOption('libraries', [])], ); } diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererFactoryTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererFactoryTest.php index 98542249aa0..eac705cfd7c 100644 --- a/src/Map/src/Bridge/Google/tests/GoogleRendererFactoryTest.php +++ b/src/Map/src/Bridge/Google/tests/GoogleRendererFactoryTest.php @@ -32,14 +32,19 @@ public static function supportsRenderer(): iterable public static function createRenderer(): iterable { yield [ - 'google://*******************@default/?version=weekly', + 'google://*******************@default/?version=weekly&libraries%5B0%5D=maps&libraries%5B1%5D=marker', 'google://GOOGLE_MAPS_API_KEY@default', ]; yield [ - 'google://*******************@default/?version=quartly', + 'google://*******************@default/?version=quartly&libraries%5B0%5D=maps&libraries%5B1%5D=marker', 'google://GOOGLE_MAPS_API_KEY@default?version=quartly', ]; + + yield [ + 'google://*******************@default/?version=quartly&libraries%5B0%5D=maps&libraries%5B1%5D=marker&libraries%5B2%5D=geometry', + 'google://GOOGLE_MAPS_API_KEY@default?version=quartly&libraries[]=geometry', + ]; } public static function unsupportedSchemeRenderer(): iterable diff --git a/src/Map/src/Bridge/Leaflet/README.md b/src/Map/src/Bridge/Leaflet/README.md index 5948f4df2d3..219cf511592 100644 --- a/src/Map/src/Bridge/Leaflet/README.md +++ b/src/Map/src/Bridge/Leaflet/README.md @@ -37,6 +37,59 @@ $leafletOptions = (new LeafletOptions()) $map->options($leafletOptions); ``` +## Use cases + +Below are some common or advanced use cases when using a map. + +### Customize the marker + +A common use case is to customize the marker. You can listen to the `ux:map:marker:before-create` event to customize the marker before it is created. + +Assuming you have a map with a custom controller: +```twig +{{ render_map(map, {'data-controller': 'my-map' }) }} +``` + +You can create a Stimulus controller to customize the markers before they are created: +```js +// assets/controllers/my_map_controller.js +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller +{ + connect() { + this.element.addEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate); + } + + disconnect() { + // Always remove listeners when the controller is disconnected + this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate); + } + + _onMarkerBeforeCreate(event) { + // You can access the marker definition and the Leaflet object + // Note: `definition.rawOptions` is the raw options object that will be passed to the `L.marker` constructor. + const { definition, L } = event.detail; + + // Use a custom icon for the marker + const redIcon = L.icon({ + // Note: instead of using an hardcoded URL, you can use the `extra` parameter from `new Marker()` (PHP) and access it here with `definition.extra`. + iconUrl: 'https://leafletjs.com/examples/custom-icons/leaf-red.png', + shadowUrl: 'https://leafletjs.com/examples/custom-icons/leaf-shadow.png', + iconSize: [38, 95], // size of the icon + shadowSize: [50, 64], // size of the shadow + iconAnchor: [22, 94], // point of the icon which will correspond to marker's location + shadowAnchor: [4, 62], // the same for the shadow + popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor + }) + + definition.rawOptions = { + icon: redIcon, + } + } +} +``` + ## Resources - [Documentation](https://symfony.com/bundles/ux-map/current/index.html) 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 index 76bb1ef7a3c..c152fb4cce6 100644 --- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts +++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts @@ -1,7 +1,7 @@ import AbstractMapController from '@symfony/ux-map/abstract-map-controller'; import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller'; import 'leaflet/dist/leaflet.min.css'; -import { type Map as LeafletMap, Marker, type Popup } from 'leaflet'; +import * as L from 'leaflet'; import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet'; type MapOptions = Pick & { tileLayer: { @@ -10,18 +10,19 @@ type MapOptions = Pick & { options: Record; }; }; -export default class extends AbstractMapController { +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; - }): LeafletMap; - protected doCreateMarker(definition: MarkerDefinition): Marker; + }): L.Map; + protected doCreateMarker(definition: MarkerDefinition): L.Marker; protected doCreateInfoWindow({ definition, marker, }: { definition: MarkerDefinition['infoWindow']; - marker: Marker; - }): Popup; + marker: L.Marker; + }): 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 index 9ce14c9473e..edb9dd872fa 100644 --- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js +++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js @@ -1,10 +1,10 @@ import AbstractMapController from '@symfony/ux-map/abstract-map-controller'; import 'leaflet/dist/leaflet.min.css'; -import { Marker, divIcon, map, tileLayer, marker } from 'leaflet'; +import * as L from 'leaflet'; class map_controller extends AbstractMapController { connect() { - Marker.prototype.options.icon = divIcon({ + L.Marker.prototype.options.icon = L.divIcon({ html: '', iconSize: [25, 41], iconAnchor: [12.5, 41], @@ -13,25 +13,34 @@ class map_controller extends AbstractMapController { }); super.connect(); } + dispatchEvent(name, payload = {}) { + this.dispatch(name, { + prefix: 'ux:map', + detail: { + ...payload, + leaflet: L, + }, + }); + } doCreateMap({ center, zoom, options, }) { - const map$1 = map(this.element, { + const map = L.map(this.element, { ...options, center: center === null ? undefined : center, zoom: zoom === null ? undefined : zoom, }); - tileLayer(options.tileLayer.url, { + L.tileLayer(options.tileLayer.url, { attribution: options.tileLayer.attribution, ...options.tileLayer.options, - }).addTo(map$1); - return map$1; + }).addTo(map); + return map; } doCreateMarker(definition) { const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition; - const marker$1 = marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map); + const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map); if (infoWindow) { - this.createInfoWindow({ definition: infoWindow, marker: marker$1 }); + this.createInfoWindow({ definition: infoWindow, marker }); } - return marker$1; + return marker; } doCreateInfoWindow({ definition, marker, }) { const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition; diff --git a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts index eac5df9ae08..4f901594cc4 100644 --- a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts +++ b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts @@ -1,15 +1,7 @@ import AbstractMapController from '@symfony/ux-map/abstract-map-controller'; import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller'; import 'leaflet/dist/leaflet.min.css'; -import { - map as createMap, - tileLayer as createTileLayer, - marker as createMarker, - divIcon, - type Map as LeafletMap, - Marker, - type Popup, -} from 'leaflet'; +import * as L from 'leaflet'; import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet'; type MapOptions = Pick & { @@ -18,35 +10,46 @@ type MapOptions = Pick & { export default class extends AbstractMapController< MapOptions, - typeof LeafletMap, + typeof L.Map, MarkerOptions, - Marker, - Popup, + typeof L.Marker, + typeof L.Popup, PopupOptions > { connect(): void { - Marker.prototype.options.icon = divIcon({ + L.Marker.prototype.options.icon = L.divIcon({ html: '', iconSize: [25, 41], iconAnchor: [12.5, 41], popupAnchor: [0, -41], className: '', }); + super.connect(); } + protected dispatchEvent(name: string, payload: Record = {}): void { + this.dispatch(name, { + prefix: 'ux:map', + detail: { + ...payload, + leaflet: L, + }, + }); + } + protected doCreateMap({ center, zoom, options, - }: { center: Point | null; zoom: number | null; options: MapOptions }): LeafletMap { - const map = createMap(this.element, { + }: { center: Point | null; zoom: number | null; options: MapOptions }): L.Map { + const map = L.map(this.element, { ...options, center: center === null ? undefined : center, zoom: zoom === null ? undefined : zoom, }); - createTileLayer(options.tileLayer.url, { + L.tileLayer(options.tileLayer.url, { attribution: options.tileLayer.attribution, ...options.tileLayer.options, }).addTo(map); @@ -54,10 +57,10 @@ export default class extends AbstractMapController< return map; } - protected doCreateMarker(definition: MarkerDefinition): Marker { + protected doCreateMarker(definition: MarkerDefinition): L.Marker { const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition; - const marker = createMarker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map); + const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map); if (infoWindow) { this.createInfoWindow({ definition: infoWindow, marker }); @@ -71,8 +74,8 @@ export default class extends AbstractMapController< marker, }: { definition: MarkerDefinition['infoWindow']; - marker: Marker; - }): Popup { + marker: L.Marker; + }): L.Popup { const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition; marker.bindPopup([headerContent, content].filter((x) => x).join('
'), { ...otherOptions, ...rawOptions }); @@ -93,7 +96,7 @@ export default class extends AbstractMapController< } this.map.fitBounds( - this.markers.map((marker: Marker) => { + this.markers.map((marker: L.Marker) => { const position = marker.getLatLng(); return [position.lat, position.lng];