Skip to content

Commit 9e0ef18

Browse files
committed
add polyline support
1 parent 8dcf73d commit 9e0ef18

21 files changed

+505
-59
lines changed

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ export type Point = {
33
lat: number;
44
lng: number;
55
};
6-
export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions> = {
6+
export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions, PolylineOptions> = {
77
center: Point | null;
88
zoom: number | null;
99
fitBoundsToMarkers: boolean;
1010
markers: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
1111
polygons: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
12+
polylines: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
1213
options: Options;
1314
};
1415
export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
@@ -25,6 +26,13 @@ export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
2526
rawOptions?: PolygonOptions;
2627
extra: Record<string, unknown>;
2728
};
29+
export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = {
30+
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
31+
points: Array<Point>;
32+
title: string | null;
33+
rawOptions?: PolylineOptions;
34+
extra: Record<string, unknown>;
35+
};
2836
export type InfoWindowDefinition<InfoWindowOptions> = {
2937
headerContent: string | null;
3038
content: string | null;
@@ -34,16 +42,17 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
3442
rawOptions?: InfoWindowOptions;
3543
extra: Record<string, unknown>;
3644
};
37-
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon> extends Controller<HTMLElement> {
45+
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline> extends Controller<HTMLElement> {
3846
static values: {
3947
providerOptions: ObjectConstructor;
4048
view: ObjectConstructor;
4149
};
42-
viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions>;
50+
viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions, PolylineOptions>;
4351
protected map: Map;
4452
protected markers: Array<Marker>;
4553
protected infoWindows: Array<InfoWindow>;
4654
protected polygons: Array<Polygon>;
55+
protected polylines: Array<Polyline>;
4756
connect(): void;
4857
protected abstract doCreateMap({ center, zoom, options, }: {
4958
center: Point | null;
@@ -52,18 +61,23 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
5261
}): Map;
5362
createMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
5463
createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
64+
createPolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;
5565
protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
5666
protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
67+
protected abstract doCreatePolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;
5768
protected createInfoWindow({ definition, element, }: {
58-
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'] | PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
59-
element: Marker | Polygon;
69+
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'] | PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'] | PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
70+
element: Marker | Polygon| Polyline;
6071
}): InfoWindow;
6172
protected abstract doCreateInfoWindow({ definition, element, }: {
6273
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
6374
element: Marker;
6475
} | {
6576
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
6677
element: Polygon;
78+
}| {
79+
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
80+
element: Polyline;
6781
}): InfoWindow;
6882
protected abstract doFitBoundsToMarkers(): void;
6983
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;

src/Map/assets/dist/abstract_map_controller.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@ class default_1 extends Controller {
66
this.markers = [];
77
this.infoWindows = [];
88
this.polygons = [];
9+
this.polylines = [];
910
}
1011
connect() {
11-
const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
12+
const { center, zoom, options, markers, polygons, polylines, fitBoundsToMarkers } = this.viewValue;
1213
this.dispatchEvent('pre-connect', { options });
1314
this.map = this.doCreateMap({ center, zoom, options });
1415
markers.forEach((marker) => this.createMarker(marker));
1516
polygons.forEach((polygon) => this.createPolygon(polygon));
17+
polylines.forEach((polyline) => this.createPolyline(polyline));
1618
if (fitBoundsToMarkers) {
1719
this.doFitBoundsToMarkers();
1820
}
1921
this.dispatchEvent('connect', {
2022
map: this.map,
2123
markers: this.markers,
2224
polygons: this.polygons,
25+
polylines: this.polylines,
2326
infoWindows: this.infoWindows,
2427
});
2528
}
@@ -37,6 +40,13 @@ class default_1 extends Controller {
3740
this.polygons.push(polygon);
3841
return polygon;
3942
}
43+
createPolyline(definition) {
44+
this.dispatchEvent('polyline:before-create', { definition });
45+
const polyline = this.doCreatePolyline(definition);
46+
this.dispatchEvent('polyline:after-create', { polyline });
47+
this.polylines.push(polyline);
48+
return polyline;
49+
}
4050
createInfoWindow({ definition, element, }) {
4151
this.dispatchEvent('info-window:before-create', { definition, element });
4252
const infoWindow = this.doCreateInfoWindow({ definition, element });

src/Map/assets/src/abstract_map_controller.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { Controller } from '@hotwired/stimulus';
22

33
export type Point = { lat: number; lng: number };
44

5-
export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions> = {
5+
export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions, PolylineOptions> = {
66
center: Point | null;
77
zoom: number | null;
88
fitBoundsToMarkers: boolean;
99
markers: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
1010
polygons: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
11+
polylines: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
1112
options: Options;
1213
};
1314

@@ -36,6 +37,14 @@ export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
3637
extra: Record<string, unknown>;
3738
};
3839

40+
export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = {
41+
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
42+
points: Array<Point>;
43+
title: string | null;
44+
rawOptions?: PolylineOptions;
45+
extra: Record<string, unknown>;
46+
};
47+
3948
export type InfoWindowDefinition<InfoWindowOptions> = {
4049
headerContent: string | null;
4150
content: string | null;
@@ -56,7 +65,7 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
5665
extra: Record<string, unknown>;
5766
};
5867

59-
export default abstract class<
68+
export default abstract class <
6069
MapOptions,
6170
Map,
6271
MarkerOptions,
@@ -65,18 +74,21 @@ export default abstract class<
6574
InfoWindow,
6675
PolygonOptions,
6776
Polygon,
77+
PolylineOptions,
78+
Polyline,
6879
> extends Controller<HTMLElement> {
6980
static values = {
7081
providerOptions: Object,
7182
view: Object,
7283
};
7384

74-
declare viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions>;
85+
declare viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions, PolylineOptions>;
7586

7687
protected map: Map;
7788
protected markers: Array<Marker> = [];
7889
protected infoWindows: Array<InfoWindow> = [];
7990
protected polygons: Array<Polygon> = [];
91+
protected polylines: Array<Polyline> = [];
8092

8193
connect() {
8294
const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
@@ -89,6 +101,8 @@ export default abstract class<
89101

90102
polygons.forEach((polygon) => this.createPolygon(polygon));
91103

104+
polylines.forEach((polyline) => this.createPolyline(polyline));
105+
92106
if (fitBoundsToMarkers) {
93107
this.doFitBoundsToMarkers();
94108
}
@@ -97,6 +111,7 @@ export default abstract class<
97111
map: this.map,
98112
markers: this.markers,
99113
polygons: this.polygons,
114+
polylines: this.polylines,
100115
infoWindows: this.infoWindows,
101116
});
102117
}
@@ -129,17 +144,27 @@ export default abstract class<
129144
return polygon;
130145
}
131146

147+
createPolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline {
148+
this.dispatchEvent('polyline:before-create', { definition });
149+
const polyline = this.doCreatePolyline(definition);
150+
this.dispatchEvent('polyline:after-create', { polyline });
151+
this.polylines.push(polyline);
152+
return polyline;
153+
}
154+
132155
protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
133156
protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
157+
protected abstract doCreatePolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;
134158

135159
protected createInfoWindow({
136160
definition,
137161
element,
138162
}: {
139163
definition:
140-
| MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow']
141-
| PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
142-
element: Marker | Polygon;
164+
| MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow']
165+
| PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow']
166+
| PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
167+
element: Marker | Polygon | Polyline;
143168
}): InfoWindow {
144169
this.dispatchEvent('info-window:before-create', { definition, element });
145170
const infoWindow = this.doCreateInfoWindow({ definition, element });
@@ -155,13 +180,17 @@ export default abstract class<
155180
element,
156181
}:
157182
| {
158-
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
159-
element: Marker;
160-
}
183+
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
184+
element: Marker;
185+
}
186+
| {
187+
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
188+
element: Polygon;
189+
}
161190
| {
162-
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
163-
element: Polygon;
164-
}): InfoWindow;
191+
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
192+
element: Polyline;
193+
}): InfoWindow;
165194

166195
protected abstract doFitBoundsToMarkers(): void;
167196

src/Map/assets/test/abstract_map_controller.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,25 @@ class MyMapController extends AbstractMapController {
3535
return polygon;
3636
}
3737

38+
doCreatePolyline(definition) {
39+
const polyline = { polyline: 'polyline', title: definition.title };
40+
41+
if (definition.infoWindow) {
42+
this.createInfoWindow({ definition: definition.infoWindow, element: polyline });
43+
}
44+
return polyline;
45+
}
46+
3847
doCreateInfoWindow({ definition, element }) {
3948
if (element.marker) {
4049
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, marker: element.title };
4150
}
4251
if (element.polygon) {
4352
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, polygon: element.title };
4453
}
54+
if (element.polyline) {
55+
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, polyline: element.title };
56+
}
4557
}
4658

4759
doFitBoundsToMarkers() {
@@ -113,6 +125,32 @@ describe('AbstractMapController', () => {
113125
"autoClose": true
114126
}
115127
}
128+
],
129+
"polylines": [
130+
{
131+
"coordinates": [
132+
{ "lat": 48.858844, "lng": 2.294351 },
133+
{ "lat": 48.853, "lng": 2.3499 },
134+
{ "lat": 48.8566, "lng": 2.3522 }
135+
],
136+
"title": "Polyline 1",
137+
"infoWindow": null
138+
},
139+
{
140+
"coordinates": [
141+
{ "lat": 45.764043, "lng": 4.835659 },
142+
{ "lat": 45.750000, "lng": 4.850000 },
143+
{ "lat": 45.770000, "lng": 4.820000 }
144+
],
145+
"title": "Polyline 2",
146+
"infoWindow": {
147+
"headerContent": "<b>Polyline 2</b>",
148+
"content": "A polyline around Lyon with some additional info.",
149+
"position": null,
150+
"opened": false,
151+
"autoClose": true
152+
}
153+
}
116154
]
117155
}'>
118156
</div>
@@ -123,7 +161,7 @@ describe('AbstractMapController', () => {
123161
clearDOM();
124162
});
125163

126-
it('connect and create map, marker, polygon and info window', async () => {
164+
it('connect and create map, marker, polygon, polyline and info window', async () => {
127165
const div = getByTestId(container, 'map');
128166
expect(div).not.toHaveClass('connected');
129167

@@ -140,6 +178,10 @@ describe('AbstractMapController', () => {
140178
{ polygon: 'polygon', title: 'Polygon 1' },
141179
{ polygon: 'polygon', title: 'Polygon 2' },
142180
]);
181+
expect(controller.polylines).toEqual([
182+
{ polyline: 'polyline', title: 'Polyline 1' },
183+
{ polyline: 'polyline', title: 'Polyline 2' },
184+
]);
143185
expect(controller.infoWindows).toEqual([
144186
{
145187
headerContent: '<b>Lyon</b>',
@@ -151,6 +193,11 @@ describe('AbstractMapController', () => {
151193
infoWindow: 'infoWindow',
152194
polygon: 'Polygon 2',
153195
},
196+
{
197+
headerContent: '<b>Polyline 2</b>',
198+
infoWindow: 'infoWindow',
199+
polyline: 'Polyline 2',
200+
},
154201
]);
155202
});
156203
});

src/Map/doc/index.rst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,20 @@ A map is created by calling ``new Map()``. You can configure the center, zoom, a
114114
),
115115
)
116116
;
117+
// 3. You can also add Polylines, which represents a path made by a series of `Point` instances
118+
$myMap->addPolyline(new Polyline(
119+
points: [
120+
new Point(48.8566, 2.3522),
121+
new Point(45.7640, 4.8357),
122+
new Point(43.2965, 5.3698),
123+
new Point(44.8378, -0.5792),
124+
],
125+
infoWindow: new InfoWindow(
126+
content: 'A line passing through Paris, Lyon, Marseille, Bordeaux',
127+
),
128+
));
117129

118-
// 3. You can also add Polygons, which represents an area enclosed by a series of `Point` instances
130+
// 4. You can also add Polygons, which represents an area enclosed by a series of `Point` instances
119131
$myMap->addPolygon(new Polygon(
120132
points: [
121133
new Point(48.8566, 2.3522),
@@ -124,11 +136,11 @@ A map is created by calling ``new Map()``. You can configure the center, zoom, a
124136
new Point(44.8378, -0.5792),
125137
],
126138
infoWindow: new InfoWindow(
127-
content: 'Paris, Lyon, Marseille, Bordeaux',
139+
content: 'A polygon enclosed by Paris, Lyon, Marseille, Bordeaux',
128140
),
129141
));
130142

131-
// 4. And inject the map in your template to render it
143+
// 5. And inject the map in your template to render it
132144
return $this->render('contact/index.html.twig', [
133145
'my_map' => $myMap,
134146
]);

src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import AbstractMapController from '@symfony/ux-map';
2-
import type { Point, MarkerDefinition, PolygonDefinition } from '@symfony/ux-map';
2+
import type { Point, MarkerDefinition, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
33
import type { LoaderOptions } from '@googlemaps/js-api-loader';
44
type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
55
export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon> {
@@ -16,9 +16,10 @@ export default class extends AbstractMapController<MapOptions, google.maps.Map,
1616
}): google.maps.Map;
1717
protected doCreateMarker(definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>): google.maps.marker.AdvancedMarkerElement;
1818
protected doCreatePolygon(definition: PolygonDefinition<google.maps.Polygon, google.maps.InfoWindowOptions>): google.maps.Polygon;
19+
protected doCreatePolyline(definition: PolylineDefinition<google.maps.Polyline, google.maps.InfoWindowOptions>): google.maps.Polyline;
1920
protected doCreateInfoWindow({ definition, element, }: {
20-
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>['infoWindow'] | PolygonDefinition<google.maps.Polygon, google.maps.InfoWindowOptions>['infoWindow'];
21-
element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon;
21+
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>['infoWindow'] | PolygonDefinition<google.maps.Polygon, google.maps.InfoWindowOptions>['infoWindow'] | PolylineDefinition<google.maps.Polyline, google.maps.InfoWindowOptions>['infoWindow'];
22+
element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon | google.maps.Polyline;
2223
}): google.maps.InfoWindow;
2324
private createTextOrElement;
2425
private closeInfoWindowsExcept;

0 commit comments

Comments
 (0)