feat(location-map): implement reusable location map component with dynamic markers and directions
This commit is contained in:
11
angular.json
11
angular.json
@@ -30,16 +30,11 @@
|
|||||||
"input": "public"
|
"input": "public"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": ["node_modules/leaflet/dist/leaflet.css", "src/styles.scss"],
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"server": "src/main.server.ts",
|
"server": "src/main.server.ts",
|
||||||
"outputMode": "server",
|
"outputMode": "server",
|
||||||
"security": {
|
"security": {
|
||||||
"allowedHosts": [
|
"allowedHosts": ["localhost", "127.0.0.1"]
|
||||||
"localhost",
|
|
||||||
"127.0.0.1"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"ssr": {
|
"ssr": {
|
||||||
"entry": "src/server.ts"
|
"entry": "src/server.ts"
|
||||||
@@ -95,4 +90,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -19,6 +19,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"@angular/cli": "^22.0.4",
|
"@angular/cli": "^22.0.4",
|
||||||
"@angular/compiler-cli": "^22.0.0",
|
"@angular/compiler-cli": "^22.0.0",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^20.17.19",
|
"@types/node": "^20.17.19",
|
||||||
"jsdom": "^28.0.0",
|
"jsdom": "^28.0.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
@@ -3816,6 +3818,13 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/geojson": {
|
||||||
|
"version": "7946.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||||
|
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/http-errors": {
|
"node_modules/@types/http-errors": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||||
@@ -3823,6 +3832,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/leaflet": {
|
||||||
|
"version": "1.9.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz",
|
||||||
|
"integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.43",
|
"version": "20.19.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||||
@@ -5936,6 +5955,12 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/listr2": {
|
"node_modules/listr2": {
|
||||||
"version": "10.2.1",
|
"version": "10.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"@angular/cli": "^22.0.4",
|
"@angular/cli": "^22.0.4",
|
||||||
"@angular/compiler-cli": "^22.0.0",
|
"@angular/compiler-cli": "^22.0.0",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/node": "^20.17.19",
|
"@types/node": "^20.17.19",
|
||||||
"jsdom": "^28.0.0",
|
"jsdom": "^28.0.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
|
|||||||
@@ -398,8 +398,8 @@
|
|||||||
<div class="accordion-showcase">
|
<div class="accordion-showcase">
|
||||||
<h2>Accordion</h2>
|
<h2>Accordion</h2>
|
||||||
<p class="text-muted mb-4">
|
<p class="text-muted mb-4">
|
||||||
Accordion reutilizable basado en las clases globales de Bootstrap. Por defecto mantiene
|
Accordion reutilizable basado en las clases globales de Bootstrap. Por defecto mantiene un
|
||||||
un solo item abierto.
|
solo item abierto.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<app-accordion>
|
<app-accordion>
|
||||||
@@ -409,8 +409,8 @@
|
|||||||
</app-accordion-item>
|
</app-accordion-item>
|
||||||
|
|
||||||
<app-accordion-item title="¿Qué medios de pago están disponibles?">
|
<app-accordion-item title="¿Qué medios de pago están disponibles?">
|
||||||
Los medios de pago dependen de la configuración de cada tienda y se muestran durante
|
Los medios de pago dependen de la configuración de cada tienda y se muestran durante el
|
||||||
el checkout.
|
checkout.
|
||||||
</app-accordion-item>
|
</app-accordion-item>
|
||||||
|
|
||||||
<app-accordion-item title="Item deshabilitado" [disabled]="true">
|
<app-accordion-item title="Item deshabilitado" [disabled]="true">
|
||||||
@@ -692,6 +692,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="component-demo card shadow-sm mt-4" data-testid="location-map-demo">
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="eyebrow">Reusable Component</span>
|
||||||
|
<h2 class="mb-2">Mapa de ubicaciones</h2>
|
||||||
|
<p class="text-muted mb-4">
|
||||||
|
Prueba de Leaflet con múltiples puntos, encuadre automático y acceso a indicaciones.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<app-location-map
|
||||||
|
[locations]="mapLocations"
|
||||||
|
ariaLabel="Mapa de puntos de atención en Rosario"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="component-demo card shadow-sm mt-4">
|
<section class="component-demo card shadow-sm mt-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span class="eyebrow">Reusable Component</span>
|
<span class="eyebrow">Reusable Component</span>
|
||||||
|
|||||||
@@ -141,6 +141,40 @@ describe('ReutilizablesTestPageComponent', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the reusable map demo with all configured locations', async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ReutilizablesTestPageComponent],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: TenantService,
|
||||||
|
useValue: createTenantServiceStub(tenant),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ToastService,
|
||||||
|
useValue: createToastServiceStub(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: createModalServiceStub(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(ReutilizablesTestPageComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const mapDemo = fixture.nativeElement.querySelector(
|
||||||
|
'[data-testid="location-map-demo"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
const accessibleLocations = mapDemo.querySelectorAll('app-location-map .visually-hidden li');
|
||||||
|
|
||||||
|
expect(mapDemo.querySelector('app-location-map')).not.toBeNull();
|
||||||
|
expect(accessibleLocations).toHaveLength(3);
|
||||||
|
expect(mapDemo.textContent).toContain('Gigante de Arroyito');
|
||||||
|
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders the carousel demo with mocked product images', async () => {
|
it('renders the carousel demo with mocked product images', async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ReutilizablesTestPageComponent],
|
imports: [ReutilizablesTestPageComponent],
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import { CarouselComponent } from '../../../../shared/components/carousel/carous
|
|||||||
import { MainCarouselComponent } from '../../../../shared/components/main-carousel/main-carousel.component';
|
import { MainCarouselComponent } from '../../../../shared/components/main-carousel/main-carousel.component';
|
||||||
import { AccordionComponent } from '../../../../shared/components/accordion/accordion.component';
|
import { AccordionComponent } from '../../../../shared/components/accordion/accordion.component';
|
||||||
import { AccordionItemComponent } from '../../../../shared/components/accordion/accordion-item.component';
|
import { AccordionItemComponent } from '../../../../shared/components/accordion/accordion-item.component';
|
||||||
|
import {
|
||||||
|
LocationMapComponent,
|
||||||
|
MapLocation,
|
||||||
|
} from '../../../../shared/components/location-map/location-map.component';
|
||||||
|
|
||||||
interface CarouselProductMock {
|
interface CarouselProductMock {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -47,6 +51,7 @@ interface CarouselProductMock {
|
|||||||
MainCarouselComponent,
|
MainCarouselComponent,
|
||||||
AccordionComponent,
|
AccordionComponent,
|
||||||
AccordionItemComponent,
|
AccordionItemComponent,
|
||||||
|
LocationMapComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './reutilizables-test-page.component.html',
|
templateUrl: './reutilizables-test-page.component.html',
|
||||||
styleUrl: './reutilizables-test-page.component.scss',
|
styleUrl: './reutilizables-test-page.component.scss',
|
||||||
@@ -79,6 +84,29 @@ export class ReutilizablesTestPageComponent {
|
|||||||
protected lastModalResult = 'Todavia no se abrio ningun modal.';
|
protected lastModalResult = 'Todavia no se abrio ningun modal.';
|
||||||
protected readonly paginatorTotalPages = 8;
|
protected readonly paginatorTotalPages = 8;
|
||||||
protected readonly cartBackgroundColor = '#ffffff';
|
protected readonly cartBackgroundColor = '#ffffff';
|
||||||
|
protected readonly mapLocations: readonly MapLocation[] = [
|
||||||
|
{
|
||||||
|
id: 'gigante-de-arroyito',
|
||||||
|
label: 'Gigante de Arroyito',
|
||||||
|
address: 'Av. Génova 640, Rosario',
|
||||||
|
latitude: -32.913997,
|
||||||
|
longitude: -60.674567,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'punto-centro',
|
||||||
|
label: 'Punto Centro',
|
||||||
|
address: 'Centro, Rosario',
|
||||||
|
latitude: -32.94682,
|
||||||
|
longitude: -60.63932,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'punto-sur',
|
||||||
|
label: 'Punto Sur',
|
||||||
|
address: 'Zona sur, Rosario',
|
||||||
|
latitude: -32.97151,
|
||||||
|
longitude: -60.65064,
|
||||||
|
},
|
||||||
|
];
|
||||||
protected readonly mainCarouselImages = [
|
protected readonly mainCarouselImages = [
|
||||||
'https://images.unsplash.com/photo-1441986300917-64674bd600d8?auto=format&fit=crop&w=1600&q=80',
|
'https://images.unsplash.com/photo-1441986300917-64674bd600d8?auto=format&fit=crop&w=1600&q=80',
|
||||||
'https://images.unsplash.com/photo-1529139574466-a303027c1d8b?auto=format&fit=crop&w=1600&q=80',
|
'https://images.unsplash.com/photo-1529139574466-a303027c1d8b?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<div #mapContainer class="location-map" role="region" [attr.aria-label]="ariaLabel"></div>
|
||||||
|
|
||||||
|
<ul class="visually-hidden">
|
||||||
|
@for (location of locations; track location.id) {
|
||||||
|
<li>
|
||||||
|
{{ location.label }}
|
||||||
|
@if (location.address) {
|
||||||
|
— {{ location.address }}
|
||||||
|
}
|
||||||
|
<a [href]="directionsUrl(location)" target="_blank" rel="noopener noreferrer">
|
||||||
|
Cómo llegar
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-map {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 22rem;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #e9ecef;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .leaflet-popup-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.2rem;
|
||||||
|
margin: 0.85rem 1rem;
|
||||||
|
color: #343a40;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
color: var(--color-primary, #0d6efd);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
.location-map {
|
||||||
|
min-height: 18rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/app/shared/components/location-map/location-map.component.ts
Normal file
172
src/app/shared/components/location-map/location-map.component.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Inject,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
PLATFORM_ID,
|
||||||
|
SimpleChanges,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
import type { LayerGroup, Map as LeafletMap, LatLngBounds, LatLngTuple } from 'leaflet';
|
||||||
|
|
||||||
|
export interface MapLocation {
|
||||||
|
id: string | number;
|
||||||
|
label: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-location-map',
|
||||||
|
templateUrl: './location-map.component.html',
|
||||||
|
styleUrl: './location-map.component.scss',
|
||||||
|
})
|
||||||
|
export class LocationMapComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||||
|
@Input({ required: true }) locations: readonly MapLocation[] = [];
|
||||||
|
@Input() ariaLabel = 'Mapa de ubicaciones';
|
||||||
|
@Input() fallbackCenter: LatLngTuple = [-32.94682, -60.63932];
|
||||||
|
@Input() fallbackZoom = 13;
|
||||||
|
@Input() maxFitZoom = 15;
|
||||||
|
|
||||||
|
@ViewChild('mapContainer', { static: true })
|
||||||
|
private readonly mapContainer!: ElementRef<HTMLDivElement>;
|
||||||
|
|
||||||
|
private leaflet: typeof import('leaflet') | null = null;
|
||||||
|
private map: LeafletMap | null = null;
|
||||||
|
private markerLayer: LayerGroup | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(PLATFORM_ID) private readonly platformId: object,
|
||||||
|
@Inject(DOCUMENT) private readonly document: Document,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
void this.initializeMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes['locations'] && this.map) {
|
||||||
|
this.renderLocations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.map?.remove();
|
||||||
|
this.map = null;
|
||||||
|
this.markerLayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected directionsUrl(location: MapLocation): string {
|
||||||
|
const destination = `${location.latitude},${location.longitude}`;
|
||||||
|
return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(destination)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initializeMap(): Promise<void> {
|
||||||
|
const leafletModule = await import('leaflet');
|
||||||
|
const leaflet =
|
||||||
|
(
|
||||||
|
leafletModule as unknown as {
|
||||||
|
default?: typeof import('leaflet');
|
||||||
|
}
|
||||||
|
).default ?? leafletModule;
|
||||||
|
|
||||||
|
if (!this.mapContainer.nativeElement.isConnected || this.map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leaflet = leaflet;
|
||||||
|
this.map = leaflet.map(this.mapContainer.nativeElement, {
|
||||||
|
center: this.fallbackCenter,
|
||||||
|
zoom: this.fallbackZoom,
|
||||||
|
scrollWheelZoom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
leaflet
|
||||||
|
.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap contributors',
|
||||||
|
})
|
||||||
|
.addTo(this.map);
|
||||||
|
|
||||||
|
this.markerLayer = leaflet.layerGroup().addTo(this.map);
|
||||||
|
this.renderLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLocations(): void {
|
||||||
|
if (!this.leaflet || !this.map || !this.markerLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.markerLayer.clearLayers();
|
||||||
|
const validLocations = this.locations.filter(
|
||||||
|
({ latitude, longitude }) =>
|
||||||
|
Number.isFinite(latitude) &&
|
||||||
|
Number.isFinite(longitude) &&
|
||||||
|
latitude >= -90 &&
|
||||||
|
latitude <= 90 &&
|
||||||
|
longitude >= -180 &&
|
||||||
|
longitude <= 180,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validLocations.length === 0) {
|
||||||
|
this.map.setView(this.fallbackCenter, this.fallbackZoom);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bounds: LatLngBounds = this.leaflet.latLngBounds([]);
|
||||||
|
|
||||||
|
for (const location of validLocations) {
|
||||||
|
const coordinates: LatLngTuple = [location.latitude, location.longitude];
|
||||||
|
bounds.extend(coordinates);
|
||||||
|
|
||||||
|
const popup = this.document.createElement('div');
|
||||||
|
const title = this.document.createElement('strong');
|
||||||
|
title.textContent = location.label;
|
||||||
|
popup.append(title);
|
||||||
|
|
||||||
|
if (location.address) {
|
||||||
|
const address = this.document.createElement('div');
|
||||||
|
address.textContent = location.address;
|
||||||
|
popup.append(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
const directions = this.document.createElement('a');
|
||||||
|
directions.href = this.directionsUrl(location);
|
||||||
|
directions.target = '_blank';
|
||||||
|
directions.rel = 'noopener noreferrer';
|
||||||
|
directions.textContent = 'Cómo llegar';
|
||||||
|
popup.append(directions);
|
||||||
|
|
||||||
|
const tooltip = this.document.createElement('span');
|
||||||
|
tooltip.textContent = location.label;
|
||||||
|
|
||||||
|
this.leaflet
|
||||||
|
.circleMarker(coordinates, {
|
||||||
|
radius: 8,
|
||||||
|
color: '#ffffff',
|
||||||
|
weight: 3,
|
||||||
|
fillColor: '#24a9e0',
|
||||||
|
fillOpacity: 1,
|
||||||
|
})
|
||||||
|
.bindTooltip(tooltip)
|
||||||
|
.bindPopup(popup)
|
||||||
|
.addTo(this.markerLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validLocations.length === 1) {
|
||||||
|
this.map.setView(bounds.getCenter(), this.maxFitZoom);
|
||||||
|
} else {
|
||||||
|
this.map.fitBounds(bounds, {
|
||||||
|
padding: [32, 32],
|
||||||
|
maxZoom: this.maxFitZoom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user