Skip to content

Commit 4fd1a57

Browse files
committed
[Map] Create Map component
1 parent 7a84975 commit 4fd1a57

File tree

87 files changed

+5152
-0
lines changed

Some content is hidden

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

87 files changed

+5152
-0
lines changed

src/Map/.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.gitattributes export-ignore
2+
/.gitignore export-ignore
3+
/.symfony.bundle.yaml export-ignore
4+
/phpunit.xml.dist export-ignore
5+
/assets/src export-ignore
6+
/assets/test export-ignore
7+
/tests export-ignore

src/Map/.gitignore

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

src/Map/.symfony.bundle.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
branches: ["2.x"]
2+
maintained_branches: ["2.x"]
3+
doc_dir: "doc"

src/Map/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# CHANGELOG
2+
3+
## 2.20.0
4+
5+
- Component added

src/Map/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2023-present Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

src/Map/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Symfony UX Map
2+
3+
**EXPERIMENTAL** This component is currently experimental and is
4+
likely to change, or even change drastically.
5+
6+
Symfony UX Map integrates [Symfony Translation](https://symfony.com/doc/current/translation.html) for JavaScript.
7+
8+
**This repository is a READ-ONLY sub-tree split**. See
9+
https://github.com/symfony/ux to create issues or submit pull requests.
10+
11+
## Resources
12+
13+
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
14+
- [Report issues](https://github.com/symfony/ux/issues) and
15+
[send Pull Requests](https://github.com/symfony/ux/pulls)
16+
in the [main Symfony UX repository](https://github.com/symfony/ux)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
declare global {
2+
interface Window {
3+
__symfony_ux_maps?: {
4+
providers?: {
5+
google_maps?: {
6+
key: string;
7+
};
8+
leaflet?: {};
9+
};
10+
};
11+
}
12+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// <reference types="google.maps" />
2+
import { Controller } from '@hotwired/stimulus';
3+
type MarkerId = number;
4+
export default class extends Controller<HTMLElement> {
5+
static values: {
6+
view: {
7+
type: ObjectConstructor;
8+
};
9+
};
10+
viewValue: {
11+
mapId: string | null;
12+
center: null | {
13+
lat: number;
14+
lng: number;
15+
};
16+
zoom: number;
17+
gestureHandling: string;
18+
backgroundColor: string;
19+
disableDoubleClickZoom: boolean;
20+
zoomControl: boolean;
21+
zoomControlOptions: google.maps.ZoomControlOptions;
22+
mapTypeControl: boolean;
23+
mapTypeControlOptions: google.maps.MapTypeControlOptions;
24+
streetViewControl: boolean;
25+
streetViewControlOptions: google.maps.StreetViewControlOptions;
26+
fullscreenControl: boolean;
27+
fullscreenControlOptions: google.maps.FullscreenControlOptions;
28+
markers: Array<{
29+
_id: MarkerId;
30+
position: {
31+
lat: number;
32+
lng: number;
33+
};
34+
title: string | null;
35+
}>;
36+
infoWindows: Array<{
37+
headerContent: string | null;
38+
content: string | null;
39+
position: {
40+
lat: number;
41+
lng: number;
42+
};
43+
opened: boolean;
44+
_markerId: MarkerId | null;
45+
autoClose: boolean;
46+
}>;
47+
fitBoundsToMarkers: boolean;
48+
};
49+
private loader;
50+
private map;
51+
private markers;
52+
private infoWindows;
53+
initialize(): void;
54+
connect(): Promise<void>;
55+
private createTextOrElement;
56+
private closeInfoWindowsExcept;
57+
private dispatchEvent;
58+
}
59+
export {};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import { Loader } from '@googlemaps/js-api-loader';
3+
4+
class default_1 extends Controller {
5+
constructor() {
6+
super(...arguments);
7+
this.markers = new Map();
8+
this.infoWindows = [];
9+
}
10+
initialize() {
11+
var _a;
12+
const providerConfig = (_a = window.__symfony_ux_maps.providers) === null || _a === void 0 ? void 0 : _a.google_maps;
13+
if (!providerConfig) {
14+
throw new Error('Google Maps provider configuration is missing, did you forget to call `{{ ux_map_script_tags() }}`?');
15+
}
16+
const loaderOptions = {
17+
apiKey: providerConfig.key,
18+
};
19+
this.dispatchEvent('init', {
20+
loaderOptions,
21+
});
22+
this.loader = new Loader(loaderOptions);
23+
}
24+
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,
50+
});
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);
61+
});
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+
}
72+
}
73+
this.viewValue.infoWindows.forEach(infoWindowConfiguration => {
74+
const marker = infoWindowConfiguration._markerId ? this.markers.get(infoWindowConfiguration._markerId) : undefined;
75+
const infoWindow = new InfoWindow({
76+
headerContent: this.createTextOrElement(infoWindowConfiguration.headerContent),
77+
content: this.createTextOrElement(infoWindowConfiguration.content),
78+
position: infoWindowConfiguration.position,
79+
});
80+
this.infoWindows.push(infoWindow);
81+
if (infoWindowConfiguration.opened) {
82+
infoWindow.open({
83+
map: this.map,
84+
shouldFocus: false,
85+
anchor: marker,
86+
});
87+
}
88+
if (marker) {
89+
marker.addListener('click', () => {
90+
if (infoWindowConfiguration.autoClose) {
91+
this.closeInfoWindowsExcept(infoWindow);
92+
}
93+
infoWindow.open({
94+
map: this.map,
95+
anchor: marker,
96+
});
97+
});
98+
}
99+
});
100+
this.dispatchEvent('connect', {
101+
map: this.map,
102+
markers: this.markers,
103+
infoWindows: this.infoWindows,
104+
});
105+
}
106+
createTextOrElement(content) {
107+
if (!content) {
108+
return null;
109+
}
110+
if (content.includes('<')) {
111+
const div = document.createElement('div');
112+
div.innerHTML = content;
113+
return div;
114+
}
115+
return content;
116+
}
117+
closeInfoWindowsExcept(infoWindow) {
118+
this.infoWindows.forEach(otherInfoWindow => {
119+
if (otherInfoWindow !== infoWindow) {
120+
otherInfoWindow.close();
121+
}
122+
});
123+
}
124+
dispatchEvent(name, payload) {
125+
this.dispatch(name, { detail: payload, prefix: 'google_maps' });
126+
}
127+
}
128+
default_1.values = {
129+
view: { type: Object },
130+
};
131+
132+
export { default_1 as default };
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import 'leaflet/dist/leaflet.min.css';
3+
import type { MarkerOptions } from 'leaflet';
4+
type MarkerId = number;
5+
export default class extends Controller<HTMLElement> {
6+
static values: {
7+
view: {
8+
type: ObjectConstructor;
9+
};
10+
};
11+
viewValue: {
12+
center: null | {
13+
lat: number;
14+
lng: number;
15+
};
16+
zoom: number | null;
17+
tileLayer: {
18+
url: string;
19+
attribution: string;
20+
} & Record<string, unknown>;
21+
fitBoundsToMarkers: boolean;
22+
markers: Array<{
23+
_id: MarkerId;
24+
position: {
25+
lat: number;
26+
lng: number;
27+
};
28+
} & MarkerOptions>;
29+
popups: Array<{
30+
_markerId: MarkerId | null;
31+
content: string;
32+
position: {
33+
lat: number;
34+
lng: number;
35+
};
36+
opened: boolean;
37+
autoClose: boolean;
38+
}>;
39+
};
40+
private map;
41+
private markers;
42+
private popups;
43+
connect(): void;
44+
private setupTileLayer;
45+
private dispatchEvent;
46+
}
47+
export {};

0 commit comments

Comments
 (0)