Your IP : 18.188.241.246


Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/src/
Upload File :
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/src/mapmobileservice.js

import {
	Dom,
	Event,
	Tag,
	Text
} from 'main.core';
import {
	Location,
	MapBase,
	ControlMode,
	Point,
	ErrorPublisher,
} from 'location.core';
import { Leaflet } from '../leaflet/src/leaflet';
import OSM from './osm';

import './css/mapmobileservice.css';

export default class MapMobileService extends MapBase
{
	static #onChangedEvent = 'onChanged';
	static #onStartChanging = 'onStartChanging';
	static #onEndChanging = 'onEndChanging';
	static #onMapViewChanged = 'onMapViewChanged';

	/** {number} */
	#zoom;
	/** {ControlMode} */
	#mode;
	/** {?Location} */
	#location;
	/** {String} */
	#languageId;
	/** {String} */
	#sourceLanguageId;
	/** {Leaflet.map} */
	#map;
	/** {Leaflet.marker} */
	#marker;
	#markerNode;
	/** {GeocodingService} */
	#geocodingService;

	#timerId;

	#changeDelay = 700;
	#tileUrlTemplate;
	#attribution = '<a href="https://leafletjs.com" title="A JS library for interactive maps" target="_blank">Leaflet</a> | Map data &copy; <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> contributors';

	#mapFactoryMethod;
	#markerFactoryMethod;
	#iconFactoryMethod
	#tileLayerFactoryMethod;
	#locationRepository;
	#isMapChanging = false;

	constructor(props)
	{
		super(props);

		this.#languageId = props.languageId;
		this.#sourceLanguageId = props.sourceLanguageId;
		this.#geocodingService = props.geocodingService;
		this.#mapFactoryMethod = props.mapFactoryMethod;
		this.#markerFactoryMethod = props.markerFactoryMethod;
		this.#iconFactoryMethod = props.iconFactoryMethod;
		this.#tileLayerFactoryMethod = props.tileLayerFactoryMethod;
		this.#tileUrlTemplate = `${props.mapServiceUrl}/hot/en/{z}/{x}/{y}.png`;
		this.#locationRepository = props.locationRepository;
	}

	set mode(mode: string): void
	{
		this.#mode = mode;
	}

	get map(): ?Object
	{
		return this.#map;
	}

	set map(map)
	{
		this.#map = map;
	}

	get mode(): string
	{
		return this.#mode;
	}

	get zoom(): number
	{
		return this.#zoom;
	}

	set zoom(zoom: number): void
	{
		this.#zoom = zoom;

		if (this.#map)
		{
			this.#map.setZoom(zoom);
		}
	}

	set location(location: ?Location): void
	{
		this.#location = location;

		if (location)
		{
			this.panTo(location.latitude, location.longitude);
		}

		this.#adjustZoom();
	}

	panTo(latitude: string, longitude: string): void
	{
		if (this.#map)
		{
			this.#map.panTo([latitude, longitude]);
		}
	}

	#adjustZoom(): void
	{
		if (!this.#location)
		{
			return;
		}

		const zoom = MapMobileService.getZoomByLocation(this.#location);
		if (zoom !== null && zoom !== this.#zoom)
		{
			this.zoom = zoom;
		}
	}

	get location(): Location
	{
		return this.#location;
	}

	onLocationChangedEventSubscribe(listener: function): void
	{
		this.subscribe(MapMobileService.#onChangedEvent, listener);
	}

	onStartChangingSubscribe(listener: function): void
	{
		this.subscribe(MapMobileService.#onStartChanging, listener);
	}

	onEndChangingSubscribe(listener: function): void
	{
		this.subscribe(MapMobileService.#onEndChanging, listener);
	}

	onMapViewChangedSubscribe(listener: function): void
	{
		this.subscribe(MapMobileService.#onMapViewChanged, listener);
	}

	#onMove()
	{
		if (this.#timerId !== null)
		{
			clearTimeout(this.#timerId);
		}
	}

	#onMoveStart()
	{
		if (this.#mode === ControlMode.edit)
		{
			this.#isMapChanging = true;

			Dom.addClass(this.#markerNode, 'location-map-mobile-center-marker-up');
		}

		this.emit(MapMobileService.#onMapViewChanged);
	}

	#onMoveEnd(): void
	{
		if (this.#mode === ControlMode.edit)
		{
			if (this.#isMapChanging === false)
			{
				return;
			}

			const upClass = 'location-map-mobile-center-marker-up';
			if (Dom.hasClass(this.#markerNode, upClass))
			{
				Dom.removeClass(this.#markerNode, upClass);
			}

			const center = this.#map.getCenter();
			this.#createTimer(center.lat, center.lng);
			this.#isMapChanging = false;
		}
	}

	#onZoomStart()
	{
		this.emit(MapMobileService.#onMapViewChanged);
	}

	#onZoomEnd()
	{
		this.#zoom = this.#map.getZoom();
	}

	#createTimer(lat: string, lng: string): void
	{
		if (this.#timerId !== null)
		{
			clearTimeout(this.#timerId);
		}

		this.#timerId = setTimeout(() => {
			const requestId = Text.getRandom();
			this.emit(MapMobileService.#onStartChanging, { requestId });
			this.#timerId = null;
			const point = new Point(lat, lng);

			this.#geocodingService.reverse(point, this.#getReverseZoom())
				.then(
					(location) => {
						let result;

						if (location)
						{
							result = this.#locationRepository.findByExternalId(
								location.externalId,
								OSM.code,
								this.#languageId
							).then((foundLocation) => {
								if (foundLocation)
								{
									foundLocation.longitude = point.longitude;
									if (foundLocation.address)
									{
										foundLocation.address.longitude = point.longitude;
									}

									foundLocation.latitude = point.latitude;
									if (foundLocation.address)
									{
										foundLocation.address.latitude = point.latitude;
									}
								}

								return foundLocation;
							});
						}
						else
						{
							result = new Promise((resolve) => {
								resolve(null);
							});
						}

						return result;
					}
				)
				.then((location) => {
					this.emit(MapMobileService.#onEndChanging, { requestId });
					this.#emitOnLocationChangedEvent(location);
				})
				.catch((response) => {
					this.emit(MapMobileService.#onEndChanging, { requestId });
					ErrorPublisher.getInstance().notify(response.errors);
				});
			},
			this.#changeDelay
		);
	}

	#getReverseZoom(): number
	{
		return this.#zoom >= 15 ? 18 : this.#zoom;
	}

	#emitOnLocationChangedEvent(location: ?Location): void
	{
		if (this.#mode === ControlMode.edit)
		{
			this.emit(MapMobileService.#onChangedEvent, { location: location	});
		}
	}

	render(props: Object): Promise
	{
		this.#mode = props.mode;
		this.#location = props.location || null;
		const container = Tag.render`<div class="location-osm-map-mobile-container"></div>`;
		props.mapContainer.appendChild(container);

		return new Promise((resolve) =>
		{
			this.#map = this.#mapFactoryMethod(container, {
				attributionControl: false,
				zoomControl: BX.prop.getBoolean(props, 'zoomControl', true),
			});

			this.#map.on('load', () =>
			{
				resolve();
			});

			if (this.#mode === ControlMode.edit)
			{
				this.#markerNode = Tag.render`<div class="location-map-mobile-center-marker"></div>`;
				container.appendChild(this.#markerNode);
			}
			else
			{
				this.#marker = this.#markerFactoryMethod(
					[this.#location.latitude, this.#location.longitude],
					{
						icon: this.#iconFactoryMethod({
							iconUrl: '/bitrix/js/location/css/image/marker.png',
							iconSize: [26, 37],
							iconAnchor: [13, 37],
						}),
					}
				);
				this.#marker.addTo(this.#map);
			}

			this.#map.setView(
				[this.#location.latitude, this.#location.longitude],
				MapMobileService.getZoomByLocation(this.#location)
			);

			const tile = this.#tileLayerFactoryMethod.call();

			tile.initialize(this.#tileUrlTemplate, {
				/**
				 * In order to avoid blurry tiles on retina screens, we need to apply the below options:
				 * detectRetina: true,
				 * maxNativeZoom: 22,
				 * maxZoom: 18,
				 *
				 * but we can't do it right now because of the following bug in the leaflet library:
				 * https://github.com/Leaflet/Leaflet/issues/8850
				 * which causes fetching non-existent tiles (19, 20, etc. zoom levels)
				 */
				maxZoom: 18,
			});

			tile.addTo(this.#map);
			this.#map.on('zoomstart', (e) => this.#onZoomStart());
			this.#map.on('zoomend', (e) => this.#onZoomEnd());

			const attribution = new Leaflet.Control.Attribution();
			attribution.setPrefix('');
			attribution.addAttribution(this.#attribution);
			this.#map.addControl(attribution);

			this.#map.on('move', (e) => this.#onMove());
			this.#map.on('movestart', (e) => this.#onMoveStart());
			this.#map.on('moveend', (e) => this.#onMoveEnd());

			if (props.searchOnRender)
			{
				const center = this.#map.getCenter();
				this.#createTimer(center.lat, center.lng);
			}
		});
	}

	destroy()
	{
		Event.unbindAll(this);
		this.#map.remove();
		super.destroy();
	}
}