Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Map] add polyline support #2340

Merged
merged 1 commit into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Map/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
- Add `ux_map.google_maps.default_map_id` configuration to set the Google ``Map ID``
- Add `ComponentWithMapTrait` to ease maps integration in [Live Components](https://symfony.com/bundles/ux-live-component/current/index.html)
- Add `Polyline` support

## 2.20

Expand Down
31 changes: 28 additions & 3 deletions src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
rawOptions?: PolygonOptions;
extra: Record<string, unknown>;
};
export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = {
'@id': string;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
points: Array<Point>;
title: string | null;
rawOptions?: PolylineOptions;
extra: Record<string, unknown>;
};
export type InfoWindowDefinition<InfoWindowOptions> = {
headerContent: string | null;
content: string | null;
Expand All @@ -28,26 +36,29 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
rawOptions?: InfoWindowOptions;
extra: Record<string, unknown>;
};
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon> extends Controller<HTMLElement> {
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline> extends Controller<HTMLElement> {
static values: {
providerOptions: ObjectConstructor;
center: ObjectConstructor;
zoom: NumberConstructor;
fitBoundsToMarkers: BooleanConstructor;
markers: ArrayConstructor;
polygons: ArrayConstructor;
polylines: ArrayConstructor;
options: ObjectConstructor;
};
centerValue: Point | null;
zoomValue: number | null;
fitBoundsToMarkersValue: boolean;
markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
optionsValue: MapOptions;
protected map: Map;
protected markers: globalThis.Map<any, any>;
protected infoWindows: Array<InfoWindow>;
protected polygons: globalThis.Map<any, any>;
protected polylines: globalThis.Map<any, any>;
connect(): void;
protected abstract doCreateMap({ center, zoom, options, }: {
center: Point | null;
Expand All @@ -58,22 +69,36 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
protected abstract removeMarker(marker: Marker): void;
protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
protected abstract removePolygon(polygon: Polygon): void;
protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
createPolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;
protected abstract removePolyline(polyline: Polyline): void;
protected abstract doCreatePolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;
protected createInfoWindow({ definition, element, }: {
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'] | PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
element: Marker | Polygon;
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
element: Marker;
} | {
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
element: Polygon;
} | {
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
element: Polyline;
}): InfoWindow;
protected abstract doCreateInfoWindow({ definition, element, }: {
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
element: Marker;
} | {
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
element: Polygon;
} | {
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
element: Polyline;
}): InfoWindow;
protected abstract doFitBoundsToMarkers(): void;
protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
abstract centerValueChanged(): void;
abstract zoomValueChanged(): void;
markersValueChanged(): void;
polygonsValueChanged(): void;
polylinesValueChanged(): void;
}
30 changes: 29 additions & 1 deletion src/Map/assets/dist/abstract_map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ class default_1 extends Controller {
this.markers = new Map();
this.infoWindows = [];
this.polygons = new Map();
this.polylines = new Map();
}
connect() {
const options = this.optionsValue;
this.dispatchEvent('pre-connect', { options });
this.map = this.doCreateMap({ center: this.centerValue, zoom: this.zoomValue, options });
this.markersValue.forEach((marker) => this.createMarker(marker));
this.polygonsValue.forEach((polygon) => this.createPolygon(polygon));
this.polylinesValue.forEach((polyline) => this.createPolyline(polyline));
if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
this.dispatchEvent('connect', {
map: this.map,
markers: [...this.markers.values()],
polygons: [...this.polygons.values()],
polylines: [...this.polylines.values()],
infoWindows: this.infoWindows,
});
}
Expand All @@ -39,6 +42,14 @@ class default_1 extends Controller {
this.polygons.set(definition['@id'], polygon);
return polygon;
}
createPolyline(definition) {
this.dispatchEvent('polyline:before-create', { definition });
const polyline = this.doCreatePolyline(definition);
this.dispatchEvent('polyline:after-create', { polyline });
polyline['@id'] = definition['@id'];
this.polylines.set(definition['@id'], polyline);
return polyline;
}
createInfoWindow({ definition, element, }) {
this.dispatchEvent('info-window:before-create', { definition, element });
const infoWindow = this.doCreateInfoWindow({ definition, element });
Expand Down Expand Up @@ -71,7 +82,7 @@ class default_1 extends Controller {
}
this.polygons.forEach((polygon) => {
if (!this.polygonsValue.find((p) => p['@id'] === polygon['@id'])) {
polygon.remove();
this.removePolygon(polygon);
this.polygons.delete(polygon['@id']);
}
});
Expand All @@ -81,6 +92,22 @@ class default_1 extends Controller {
}
});
}
polylinesValueChanged() {
if (!this.map) {
return;
}
this.polylines.forEach((polyline) => {
if (!this.polylinesValue.find((p) => p['@id'] === polyline['@id'])) {
this.removePolyline(polyline);
this.polylines.delete(polyline['@id']);
}
});
this.polylinesValue.forEach((polyline) => {
if (!this.polylines.has(polyline['@id'])) {
this.createPolyline(polyline);
}
});
}
}
default_1.values = {
providerOptions: Object,
Expand All @@ -89,6 +116,7 @@ default_1.values = {
fitBoundsToMarkers: Boolean,
markers: Array,
polygons: Array,
polylines: Array,
options: Object,
};

Expand Down
79 changes: 66 additions & 13 deletions src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
extra: Record<string, unknown>;
};

export type PolylineDefinition<PolylineOptions, InfoWindowOptions> = {
'@id': string;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
points: Array<Point>;
title: string | null;
rawOptions?: PolylineOptions;
extra: Record<string, unknown>;
};

export type InfoWindowDefinition<InfoWindowOptions> = {
headerContent: string | null;
content: string | null;
Expand Down Expand Up @@ -58,6 +67,8 @@ export default abstract class<
InfoWindow,
PolygonOptions,
Polygon,
PolylineOptions,
Polyline,
> extends Controller<HTMLElement> {
static values = {
providerOptions: Object,
Expand All @@ -66,6 +77,7 @@ export default abstract class<
fitBoundsToMarkers: Boolean,
markers: Array,
polygons: Array,
polylines: Array,
options: Object,
};

Expand All @@ -74,12 +86,14 @@ export default abstract class<
declare fitBoundsToMarkersValue: boolean;
declare markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
declare polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
declare polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
declare optionsValue: MapOptions;

protected map: Map;
protected markers = new Map<Marker>();
protected infoWindows: Array<InfoWindow> = [];
protected polygons = new Map<Polygon>();
protected polylines = new Map<Polyline>();

connect() {
const options = this.optionsValue;
Expand All @@ -92,6 +106,8 @@ export default abstract class<

this.polygonsValue.forEach((polygon) => this.createPolygon(polygon));

this.polylinesValue.forEach((polyline) => this.createPolyline(polyline));

if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
Expand All @@ -100,6 +116,7 @@ export default abstract class<
map: this.map,
markers: [...this.markers.values()],
polygons: [...this.polygons.values()],
polylines: [...this.polylines.values()],
infoWindows: this.infoWindows,
});
}
Expand Down Expand Up @@ -142,17 +159,36 @@ export default abstract class<
return polygon;
}

protected abstract removePolygon(polygon: Polygon): void;

protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;

public createPolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline {
this.dispatchEvent('polyline:before-create', { definition });
const polyline = this.doCreatePolyline(definition);
this.dispatchEvent('polyline:after-create', { polyline });

polyline['@id'] = definition['@id'];

this.polylines.set(definition['@id'], polyline);

return polyline;
}

protected abstract removePolyline(polyline: Polyline): void;

protected abstract doCreatePolyline(definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>): Polyline;

protected createInfoWindow({
definition,
element,
}: {
definition:
| MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow']
| PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
element: Marker | Polygon;
}): InfoWindow {
}:
| { definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow']; element: Marker }
| { definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow']; element: Polygon }
| {
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
element: Polyline;
}): InfoWindow {
this.dispatchEvent('info-window:before-create', { definition, element });
const infoWindow = this.doCreateInfoWindow({ definition, element });
this.dispatchEvent('info-window:after-create', { infoWindow, element });
Expand All @@ -166,13 +202,11 @@ export default abstract class<
definition,
element,
}:
| { definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow']; element: Marker }
| { definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow']; element: Polygon }
| {
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'];
element: Marker;
}
| {
definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
element: Polygon;
definition: PolylineDefinition<PolylineOptions, InfoWindowOptions>['infoWindow'];
element: Polyline;
}): InfoWindow;

protected abstract doFitBoundsToMarkers(): void;
Expand Down Expand Up @@ -213,7 +247,7 @@ export default abstract class<

this.polygons.forEach((polygon) => {
if (!this.polygonsValue.find((p) => p['@id'] === polygon['@id'])) {
polygon.remove();
this.removePolygon(polygon);
this.polygons.delete(polygon['@id']);
}
});
Expand All @@ -224,4 +258,23 @@ export default abstract class<
}
});
}

public polylinesValueChanged(): void {
if (!this.map) {
return;
}

this.polylines.forEach((polyline) => {
if (!this.polylinesValue.find((p) => p['@id'] === polyline['@id'])) {
this.removePolyline(polyline);
this.polylines.delete(polyline['@id']);
}
});

this.polylinesValue.forEach((polyline) => {
if (!this.polylines.has(polyline['@id'])) {
this.createPolyline(polyline);
}
});
}
}
23 changes: 22 additions & 1 deletion src/Map/assets/test/abstract_map_controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,25 @@ class MyMapController extends AbstractMapController {
return polygon;
}

doCreatePolyline(definition) {
const polyline = { polyline: 'polyline', title: definition.title };

if (definition.infoWindow) {
this.createInfoWindow({ definition: definition.infoWindow, element: polyline });
}
return polyline;
}

doCreateInfoWindow({ definition, element }) {
if (element.marker) {
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, marker: element.title };
}
if (element.polygon) {
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, polygon: element.title };
}
if (element.polyline) {
return { infoWindow: 'infoWindow', headerContent: definition.headerContent, polyline: element.title };
}
}

doFitBoundsToMarkers() {
Expand Down Expand Up @@ -70,6 +82,7 @@ describe('AbstractMapController', () => {
data-map-options-value="{}"
data-map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Paris&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;a69f13edd2e571f3&quot;},{&quot;position&quot;:{&quot;lat&quot;:45.75,&quot;lng&quot;:4.85},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Lyon&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;cb9c1a30d562694b&quot;},{&quot;position&quot;:{&quot;lat&quot;:43.6047,&quot;lng&quot;:1.4442},&quot;title&quot;:&quot;Toulouse&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Toulouse&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;e6b3acef1325fb52&quot;}]"
data-map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:45.75,&quot;lng&quot;:4.85},{&quot;lat&quot;:43.6047,&quot;lng&quot;:1.4442}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;228ae6f5c1b17cfd&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.4442,&quot;lng&quot;:43.6047},{&quot;lat&quot;:4.85,&quot;lng&quot;:45.75},{&quot;lat&quot;:2.3522,&quot;lng&quot;:48.8566}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Polygon&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;fillColor&quot;:&quot;#ff0000&quot;},&quot;@id&quot;:&quot;9874334e4e8caa16&quot;}]"
data-map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.1173,&quot;lng&quot;:-1.6778},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.2082,&quot;lng&quot;:16.3738}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Polyline&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;strokeColor&quot;:&quot;#ff0000&quot;},&quot;@id&quot;:&quot;0fa955da866c7720&quot;}]"
style="height: 600px"
></div>
`);
Expand All @@ -79,7 +92,7 @@ describe('AbstractMapController', () => {
clearDOM();
});

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

Expand All @@ -101,6 +114,9 @@ describe('AbstractMapController', () => {
['9874334e4e8caa16', { '@id': '9874334e4e8caa16', polygon: 'polygon', title: null }],
])
);
expect(controller.polylines).toEqual(
new Map([['0fa955da866c7720', { '@id': '0fa955da866c7720', polyline: 'polyline', title: null }]])
);
expect(controller.infoWindows).toEqual([
{
headerContent: 'Paris',
Expand All @@ -122,6 +138,11 @@ describe('AbstractMapController', () => {
infoWindow: 'infoWindow',
polygon: null,
},
{
headerContent: 'Polyline',
infoWindow: 'infoWindow',
polyline: null,
},
]);
});
});
Loading