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] Make UX Map compatible with Live Components (and some internal things) #2385

Merged
merged 5 commits into from
Nov 22, 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 @@ -4,6 +4,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)

## 2.20

Expand Down
35 changes: 22 additions & 13 deletions src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,16 @@ export type Point = {
lat: number;
lng: number;
};
export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions> = {
center: Point | null;
zoom: number | null;
fitBoundsToMarkers: boolean;
markers: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
polygons: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
options: Options;
};
export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
'@id': string;
position: Point;
title: string | null;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
rawOptions?: MarkerOptions;
extra: Record<string, unknown>;
};
export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
'@id': string;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
points: Array<Point>;
title: string | null;
Expand All @@ -37,22 +31,33 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon> extends Controller<HTMLElement> {
static values: {
providerOptions: ObjectConstructor;
view: ObjectConstructor;
center: ObjectConstructor;
zoom: NumberConstructor;
fitBoundsToMarkers: BooleanConstructor;
markers: ArrayConstructor;
polygons: ArrayConstructor;
options: ObjectConstructor;
};
viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions>;
centerValue: Point | null;
zoomValue: number | null;
fitBoundsToMarkersValue: boolean;
markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
optionsValue: MapOptions;
protected map: Map;
protected markers: Array<Marker>;
protected markers: globalThis.Map<any, any>;
protected infoWindows: Array<InfoWindow>;
protected polygons: Array<Polygon>;
protected polygons: globalThis.Map<any, any>;
connect(): void;
protected abstract doCreateMap({ center, zoom, options, }: {
center: Point | null;
zoom: number | null;
options: MapOptions;
}): Map;
createMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
protected abstract removeMarker(marker: Marker): void;
protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;
protected createInfoWindow({ definition, element, }: {
definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>['infoWindow'] | PolygonDefinition<PolygonOptions, InfoWindowOptions>['infoWindow'];
Expand All @@ -67,4 +72,8 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
}): 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;
}
66 changes: 54 additions & 12 deletions src/Map/assets/dist/abstract_map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,40 @@ import { Controller } from '@hotwired/stimulus';
class default_1 extends Controller {
constructor() {
super(...arguments);
this.markers = [];
this.markers = new Map();
this.infoWindows = [];
this.polygons = [];
this.polygons = new Map();
}
connect() {
const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
const options = this.optionsValue;
this.dispatchEvent('pre-connect', { options });
this.map = this.doCreateMap({ center, zoom, options });
markers.forEach((marker) => this.createMarker(marker));
polygons.forEach((polygon) => this.createPolygon(polygon));
if (fitBoundsToMarkers) {
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));
if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
polygons: this.polygons,
markers: [...this.markers.values()],
polygons: [...this.polygons.values()],
infoWindows: this.infoWindows,
});
}
createMarker(definition) {
this.dispatchEvent('marker:before-create', { definition });
const marker = this.doCreateMarker(definition);
this.dispatchEvent('marker:after-create', { marker });
this.markers.push(marker);
marker['@id'] = definition['@id'];
this.markers.set(definition['@id'], marker);
return marker;
}
createPolygon(definition) {
this.dispatchEvent('polygon:before-create', { definition });
const polygon = this.doCreatePolygon(definition);
this.dispatchEvent('polygon:after-create', { polygon });
this.polygons.push(polygon);
polygon['@id'] = definition['@id'];
this.polygons.set(definition['@id'], polygon);
return polygon;
}
createInfoWindow({ definition, element, }) {
Expand All @@ -44,10 +46,50 @@ class default_1 extends Controller {
this.infoWindows.push(infoWindow);
return infoWindow;
}
markersValueChanged() {
if (!this.map) {
return;
}
this.markers.forEach((marker) => {
if (!this.markersValue.find((m) => m['@id'] === marker['@id'])) {
this.removeMarker(marker);
this.markers.delete(marker['@id']);
}
});
this.markersValue.forEach((marker) => {
if (!this.markers.has(marker['@id'])) {
this.createMarker(marker);
}
});
if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
}
polygonsValueChanged() {
if (!this.map) {
return;
}
this.polygons.forEach((polygon) => {
if (!this.polygonsValue.find((p) => p['@id'] === polygon['@id'])) {
polygon.remove();
this.polygons.delete(polygon['@id']);
}
});
this.polygonsValue.forEach((polygon) => {
if (!this.polygons.has(polygon['@id'])) {
this.createPolygon(polygon);
}
});
}
}
default_1.values = {
providerOptions: Object,
view: Object,
center: Object,
zoom: Number,
fitBoundsToMarkers: Boolean,
markers: Array,
polygons: Array,
options: Object,
};

export { default_1 as default };
106 changes: 82 additions & 24 deletions src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@ import { Controller } from '@hotwired/stimulus';

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

export type MapView<Options, MarkerOptions, InfoWindowOptions, PolygonOptions> = {
center: Point | null;
zoom: number | null;
fitBoundsToMarkers: boolean;
markers: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
polygons: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
options: Options;
};

export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
'@id': string;
position: Point;
title: string | null;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
Expand All @@ -29,6 +21,7 @@ export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
};

export type PolygonDefinition<PolygonOptions, InfoWindowOptions> = {
'@id': string;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
points: Array<Point>;
title: string | null;
Expand Down Expand Up @@ -68,35 +61,45 @@ export default abstract class<
> extends Controller<HTMLElement> {
static values = {
providerOptions: Object,
view: Object,
center: Object,
zoom: Number,
fitBoundsToMarkers: Boolean,
markers: Array,
polygons: Array,
options: Object,
};

declare viewValue: MapView<MapOptions, MarkerOptions, InfoWindowOptions, PolygonOptions>;
declare centerValue: Point | null;
declare zoomValue: number | null;
declare fitBoundsToMarkersValue: boolean;
declare markersValue: Array<MarkerDefinition<MarkerOptions, InfoWindowOptions>>;
declare polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
declare optionsValue: MapOptions;

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

connect() {
const { center, zoom, options, markers, polygons, fitBoundsToMarkers } = this.viewValue;
const options = this.optionsValue;

this.dispatchEvent('pre-connect', { options });

this.map = this.doCreateMap({ center, zoom, options });
this.map = this.doCreateMap({ center: this.centerValue, zoom: this.zoomValue, options });

markers.forEach((marker) => this.createMarker(marker));
this.markersValue.forEach((marker) => this.createMarker(marker));

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

if (fitBoundsToMarkers) {
if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}

this.dispatchEvent('connect', {
map: this.map,
markers: this.markers,
polygons: this.polygons,
markers: [...this.markers.values()],
polygons: [...this.polygons.values()],
infoWindows: this.infoWindows,
});
}
Expand All @@ -116,20 +119,29 @@ export default abstract class<
const marker = this.doCreateMarker(definition);
this.dispatchEvent('marker:after-create', { marker });

this.markers.push(marker);
marker['@id'] = definition['@id'];

this.markers.set(definition['@id'], marker);

return marker;
}

createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon {
protected abstract removeMarker(marker: Marker): void;

protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;

public createPolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon {
this.dispatchEvent('polygon:before-create', { definition });
const polygon = this.doCreatePolygon(definition);
this.dispatchEvent('polygon:after-create', { polygon });
this.polygons.push(polygon);

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

this.polygons.set(definition['@id'], polygon);

return polygon;
}

protected abstract doCreateMarker(definition: MarkerDefinition<MarkerOptions, InfoWindowOptions>): Marker;
protected abstract doCreatePolygon(definition: PolygonDefinition<PolygonOptions, InfoWindowOptions>): Polygon;

protected createInfoWindow({
Expand Down Expand Up @@ -166,4 +178,50 @@ export default abstract class<
protected abstract doFitBoundsToMarkers(): void;

protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;

public abstract centerValueChanged(): void;

public abstract zoomValueChanged(): void;

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

this.markers.forEach((marker) => {
if (!this.markersValue.find((m) => m['@id'] === marker['@id'])) {
this.removeMarker(marker);
this.markers.delete(marker['@id']);
}
});

this.markersValue.forEach((marker) => {
if (!this.markers.has(marker['@id'])) {
this.createMarker(marker);
}
});

if (this.fitBoundsToMarkersValue) {
this.doFitBoundsToMarkers();
}
}

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

this.polygons.forEach((polygon) => {
if (!this.polygonsValue.find((p) => p['@id'] === polygon['@id'])) {
polygon.remove();
this.polygons.delete(polygon['@id']);
}
});

this.polygonsValue.forEach((polygon) => {
if (!this.polygons.has(polygon['@id'])) {
this.createPolygon(polygon);
}
});
}
}
Loading