Skip to content

Commit 0b49a5c

Browse files
committed
[Map] Second iteration, simplify configuration a lot, make Map/Markers/InfoWindow generic!
1 parent 9436fa3 commit 0b49a5c

File tree

120 files changed

+2886
-3024
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+2886
-3024
lines changed

.github/workflows/test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ jobs:
8989
dependency-version: 'highest'
9090
component: ${{ fromJson(needs.tests-php-components.outputs.components )}}
9191
exclude:
92+
- component: Map # does not support PHP 8.1
93+
php-version: '8.1'
9294
- component: Swup # has no tests
9395
- component: Turbo # has its own workflow (test-turbo.yml)
9496
- component: Typed # has no tests

src/Map/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
/phpunit.xml.dist export-ignore
55
/assets/src export-ignore
66
/assets/test export-ignore
7+
/assets/vitest.config.js export-ignore
78
/tests export-ignore

src/Map/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
vendor
22
composer.lock
3-
.php_cs.cache
43
.phpunit.result.cache

src/Map/LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2023-present Fabien Potencier
1+
Copyright (c) 2024-present Fabien Potencier
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

src/Map/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**EXPERIMENTAL** This component is currently experimental and is
44
likely to change, or even change drastically.
55

6-
Symfony UX Map integrates [Symfony Translation](https://symfony.com/doc/current/translation.html) for JavaScript.
6+
Symfony UX Map integrates interactive Maps in Symfony applications, like Leaflet or GoogleMaps.
77

88
**This repository is a READ-ONLY sub-tree split**. See
99
https://github.com/symfony/ux to create issues or submit pull requests.

src/Map/assets/dist/google_maps_controller.d.ts

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,22 @@
11
/// <reference types="google.maps" />
22
import { Controller } from '@hotwired/stimulus';
3-
type MarkerId = number;
3+
import type { MapView } from './map';
4+
type GoogleMapsOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
45
export default class extends Controller<HTMLElement> {
56
static values: {
67
view: ObjectConstructor;
78
};
8-
viewValue: {
9-
mapId: string | null;
10-
center: null | {
11-
lat: number;
12-
lng: number;
13-
};
14-
zoom: number;
15-
gestureHandling: string;
16-
backgroundColor: string;
17-
disableDoubleClickZoom: boolean;
18-
zoomControl: boolean;
19-
zoomControlOptions: google.maps.ZoomControlOptions;
20-
mapTypeControl: boolean;
21-
mapTypeControlOptions: google.maps.MapTypeControlOptions;
22-
streetViewControl: boolean;
23-
streetViewControlOptions: google.maps.StreetViewControlOptions;
24-
fullscreenControl: boolean;
25-
fullscreenControlOptions: google.maps.FullscreenControlOptions;
26-
markers: Array<{
27-
_id: MarkerId;
28-
position: {
29-
lat: number;
30-
lng: number;
31-
};
32-
title: string | null;
33-
}>;
34-
infoWindows: Array<{
35-
headerContent: string | null;
36-
content: string | null;
37-
position: {
38-
lat: number;
39-
lng: number;
40-
};
41-
opened: boolean;
42-
_markerId: MarkerId | null;
43-
autoClose: boolean;
44-
}>;
45-
fitBoundsToMarkers: boolean;
46-
};
9+
viewValue: MapView<GoogleMapsOptions>;
4710
private loader;
4811
private map;
4912
private markers;
5013
private infoWindows;
5114
initialize(): void;
5215
connect(): Promise<void>;
16+
private createMarkers;
17+
private createMarker;
18+
private createInfoWindows;
19+
private createInfoWindow;
5320
private createTextOrElement;
5421
private closeInfoWindowsExcept;
5522
private dispatchEvent;

src/Map/assets/dist/google_maps_controller.js

Lines changed: 68 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,106 +4,94 @@ import { Loader } from '@googlemaps/js-api-loader';
44
class default_1 extends Controller {
55
constructor() {
66
super(...arguments);
7-
this.markers = new Map();
7+
this.markers = [];
88
this.infoWindows = [];
99
}
1010
initialize() {
11-
var _a;
12-
const providerConfig = (_a = window.__symfony_ux_maps.providers) === null || _a === void 0 ? void 0 : _a.google_maps;
11+
var _a, _b;
12+
const providerConfig = (_b = (_a = window.__symfony_ux_maps) === null || _a === void 0 ? void 0 : _a.providers) === null || _b === void 0 ? void 0 : _b['google-maps'];
1313
if (!providerConfig) {
1414
throw new Error('Google Maps provider configuration is missing, did you forget to call `{{ ux_map_script_tags() }}`?');
1515
}
16-
const loaderOptions = {
17-
apiKey: providerConfig.key,
18-
};
16+
const loaderOptions = providerConfig;
1917
this.dispatchEvent('init', {
2018
loaderOptions,
2119
});
2220
this.loader = new Loader(loaderOptions);
2321
}
2422
async connect() {
25-
const { Map: GoogleMap, InfoWindow } = await this.loader.importLibrary('maps');
26-
const mapOptions = {
27-
gestureHandling: this.viewValue.gestureHandling,
28-
backgroundColor: this.viewValue.backgroundColor,
29-
disableDoubleClickZoom: this.viewValue.disableDoubleClickZoom,
30-
zoomControl: this.viewValue.zoomControl,
31-
zoomControlOptions: this.viewValue.zoomControlOptions,
32-
mapTypeControl: this.viewValue.mapTypeControl,
33-
mapTypeControlOptions: this.viewValue.mapTypeControlOptions,
34-
streetViewControl: this.viewValue.streetViewControl,
35-
streetViewControlOptions: this.viewValue.streetViewControlOptions,
36-
fullscreenControl: this.viewValue.fullscreenControl,
37-
fullscreenControlOptions: this.viewValue.fullscreenControlOptions,
38-
};
39-
if (this.viewValue.mapId) {
40-
mapOptions.mapId = this.viewValue.mapId;
41-
}
42-
if (this.viewValue.center) {
43-
mapOptions.center = this.viewValue.center;
44-
}
45-
if (this.viewValue.zoom) {
46-
mapOptions.zoom = this.viewValue.zoom;
47-
}
48-
this.dispatchEvent('pre-connect', {
49-
mapOptions,
23+
const { Map: GoogleMap } = await this.loader.importLibrary('maps');
24+
const { center, zoom, fitBoundsToMarkers, options, markers, infoWindows } = this.viewValue;
25+
this.dispatchEvent('pre-connect', { options });
26+
this.map = new GoogleMap(this.element, Object.assign(Object.assign({}, options), { center,
27+
zoom }));
28+
this.createMarkers(markers, fitBoundsToMarkers);
29+
this.createInfoWindows(infoWindows);
30+
this.dispatchEvent('connect', {
31+
map: this.map,
32+
markers: this.markers,
33+
infoWindows: this.infoWindows,
5034
});
51-
this.map = new GoogleMap(this.element, mapOptions);
52-
if (this.viewValue.markers) {
53-
const { AdvancedMarkerElement } = await this.loader.importLibrary('marker');
54-
this.viewValue.markers.forEach((markerConfiguration) => {
55-
const marker = new AdvancedMarkerElement({
56-
position: markerConfiguration.position,
57-
title: markerConfiguration.title,
58-
map: this.map,
59-
});
60-
this.markers.set(markerConfiguration._id, marker);
35+
}
36+
createMarkers(markers, fitBoundsToMarkers) {
37+
markers.forEach((definition) => this.createMarker(definition));
38+
if (this.markers.length > 0 && fitBoundsToMarkers) {
39+
const bounds = new google.maps.LatLngBounds();
40+
this.markers.forEach((marker) => {
41+
if (!marker.position) {
42+
return;
43+
}
44+
bounds.extend(marker.position);
6145
});
62-
if (this.viewValue.fitBoundsToMarkers) {
63-
const bounds = new google.maps.LatLngBounds();
64-
this.markers.forEach((marker) => {
65-
if (!marker.position) {
66-
return;
67-
}
68-
bounds.extend(marker.position);
69-
});
70-
this.map.fitBounds(bounds);
71-
}
46+
this.map.fitBounds(bounds);
47+
}
48+
}
49+
async createMarker(definition) {
50+
const { AdvancedMarkerElement } = await this.loader.importLibrary('marker');
51+
const options = {
52+
position: definition.position,
53+
title: definition.title,
54+
};
55+
this.dispatchEvent('marker:before-create', { options });
56+
const marker = new AdvancedMarkerElement(Object.assign(Object.assign({}, options), { map: this.map }));
57+
if (definition.infoWindow) {
58+
this.createInfoWindow(definition.infoWindow, marker);
7259
}
73-
this.viewValue.infoWindows.forEach((infoWindowConfiguration) => {
74-
const marker = infoWindowConfiguration._markerId
75-
? this.markers.get(infoWindowConfiguration._markerId)
76-
: undefined;
77-
const infoWindow = new InfoWindow({
78-
headerContent: this.createTextOrElement(infoWindowConfiguration.headerContent),
79-
content: this.createTextOrElement(infoWindowConfiguration.content),
80-
position: infoWindowConfiguration.position,
60+
this.dispatchEvent('marker:after-create', { marker });
61+
this.markers.push(marker);
62+
}
63+
createInfoWindows(infoWindows) {
64+
infoWindows.forEach((definition) => this.createInfoWindow(definition));
65+
}
66+
async createInfoWindow(definition, marker) {
67+
const { InfoWindow } = await this.loader.importLibrary('maps');
68+
const options = {
69+
headerContent: this.createTextOrElement(definition.headerContent),
70+
content: this.createTextOrElement(definition.content),
71+
position: definition.position,
72+
};
73+
this.dispatchEvent('info-window:before-create', { options });
74+
const infoWindow = new InfoWindow(options);
75+
this.infoWindows.push(infoWindow);
76+
if (definition.opened) {
77+
infoWindow.open({
78+
map: this.map,
79+
shouldFocus: false,
80+
anchor: marker,
8181
});
82-
this.infoWindows.push(infoWindow);
83-
if (infoWindowConfiguration.opened) {
82+
}
83+
if (marker) {
84+
marker.addListener('click', () => {
85+
if (definition.autoClose) {
86+
this.closeInfoWindowsExcept(infoWindow);
87+
}
8488
infoWindow.open({
8589
map: this.map,
86-
shouldFocus: false,
8790
anchor: marker,
8891
});
89-
}
90-
if (marker) {
91-
marker.addListener('click', () => {
92-
if (infoWindowConfiguration.autoClose) {
93-
this.closeInfoWindowsExcept(infoWindow);
94-
}
95-
infoWindow.open({
96-
map: this.map,
97-
anchor: marker,
98-
});
99-
});
100-
}
101-
});
102-
this.dispatchEvent('connect', {
103-
map: this.map,
104-
markers: this.markers,
105-
infoWindows: this.infoWindows,
106-
});
92+
});
93+
}
94+
this.dispatchEvent('info-window:after-create', { infoWindow });
10795
}
10896
createTextOrElement(content) {
10997
if (!content) {
Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,29 @@
11
import { Controller } from '@hotwired/stimulus';
22
import 'leaflet/dist/leaflet.min.css';
3-
import type { MarkerOptions } from 'leaflet';
4-
type MarkerId = number;
3+
import type { MapOptions } from 'leaflet';
4+
import type { MapView } from './map';
5+
type LeafletOptions = Pick<MapOptions, 'center' | 'zoom'>;
6+
type AdditionalOptions = {
7+
tileLayer: {
8+
url: string;
9+
attribution: string;
10+
options: Record<string, unknown>;
11+
};
12+
};
513
export default class extends Controller<HTMLElement> {
614
static values: {
715
view: ObjectConstructor;
816
};
9-
viewValue: {
10-
center: null | {
11-
lat: number;
12-
lng: number;
13-
};
14-
zoom: number | null;
15-
tileLayer: {
16-
url: string;
17-
attribution: string;
18-
} & Record<string, unknown>;
19-
fitBoundsToMarkers: boolean;
20-
markers: Array<{
21-
_id: MarkerId;
22-
position: {
23-
lat: number;
24-
lng: number;
25-
};
26-
} & MarkerOptions>;
27-
popups: Array<{
28-
_markerId: MarkerId | null;
29-
content: string;
30-
position: {
31-
lat: number;
32-
lng: number;
33-
};
34-
opened: boolean;
35-
autoClose: boolean;
36-
}>;
37-
};
17+
viewValue: MapView<LeafletOptions & AdditionalOptions>;
3818
private map;
3919
private markers;
40-
private popups;
20+
private infoWindows;
4121
connect(): void;
42-
private setupTileLayer;
22+
private createTileLayer;
23+
private createMarkers;
24+
private createMarker;
25+
private createInfoWindows;
26+
private createInfoWindow;
4327
private dispatchEvent;
4428
}
4529
export {};

0 commit comments

Comments
 (0)