map providers

This commit is contained in:
Yuri Kuznetsov
2023-12-02 12:12:05 +02:00
parent 3874afe5cc
commit 885d99374c
9 changed files with 302 additions and 138 deletions
+19
View File
@@ -854,6 +854,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/mapProviders">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/app/mapProviders" />
<option name="relativePathToSchema" value="schema/metadata/app/mapProviders.json" />
<option name="schemaVersion" value="JSON Schema version 7" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/Resources/metadata/app/mapProviders.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/massActions">
<value>
<SchemaInfo>
+6
View File
@@ -352,6 +352,12 @@
],
"url": "./schema/metadata/app/entityManager.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/mapProviders.json"
],
"url": "./schema/metadata/app/mapProviders.json"
},
{
"fileMatch": [
"*/Resources/metadata/app/massActions.json"
@@ -256,6 +256,7 @@ return [
'newNotificationCountInTitle' => false,
'pdfEngine' => 'Dompdf',
'smsProvider' => null,
'mapProvider' => 'Google',
'defaultFileStorage' => 'EspoUploadDir',
'ldapUserNameAttribute' => 'sAMAccountName',
'ldapUserFirstNameAttribute' => 'givenName',
@@ -0,0 +1,5 @@
{
"Google": {
"renderer": "handlers/map/google-maps-renderer"
}
}
@@ -1,13 +1,5 @@
{
"params": [
{
"name": "provider",
"type": "enum",
"options": [
"Google"
],
"default": "Google"
},
{
"name": "height",
"type": "int",
@@ -0,0 +1,160 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import MapRenderer from 'handlers/map/renderer';
class GoogleMapsRenderer extends MapRenderer {
/**
* @param {module:handlers/map/renderer~addressData} addressData
*/
render(addressData) {
if ('google' in window && window.google.maps) {
this.initMapGoogle(addressData);
return;
}
// noinspection SpellCheckingInspection
if (typeof window.mapapiloaded === 'function') {
// noinspection SpellCheckingInspection
const mapapiloaded = window.mapapiloaded;
// noinspection SpellCheckingInspection
window.mapapiloaded = () => {
this.initMapGoogle(addressData);
mapapiloaded();
};
return;
}
// noinspection SpellCheckingInspection
window.mapapiloaded = () => this.initMapGoogle(addressData);
let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
const apiKey = this.view.getConfig().get('googleMapsApiKey');
if (apiKey) {
src += '&key=' + apiKey;
}
const scriptElement = document.createElement('script');
scriptElement.setAttribute('async', 'async');
scriptElement.src = src;
document.head.appendChild(scriptElement);
}
/**
* @param {module:handlers/map/renderer~addressData} addressData
*/
initMapGoogle(addressData) {
// noinspection JSUnresolvedReference
const geocoder = new google.maps.Geocoder();
let map;
try {
// noinspection SpellCheckingInspection,JSUnresolvedReference
map = new google.maps.Map(this.view.$el.find('.map').get(0), {
zoom: 15,
center: {lat: 0, lng: 0},
scrollwheel: false,
});
}
catch (e) {
console.error(e.message);
return;
}
let address = '';
if (addressData.street) {
address += addressData.street;
}
if (addressData.city) {
if (address !== '') {
address += ', ';
}
address += addressData.city;
}
if (addressData.state) {
if (address !== '') {
address += ', ';
}
address += addressData.state;
}
if (addressData.postalCode) {
if (addressData.state || addressData.city) {
address += ' ';
}
else {
if (address) {
address += ', ';
}
}
address += addressData.postalCode;
}
if (addressData.country) {
if (address !== '') {
address += ', ';
}
address += addressData.country;
}
// noinspection JSUnresolvedReference
geocoder.geocode({'address': address}, (results, status) => {
// noinspection JSUnresolvedReference
if (status === google.maps.GeocoderStatus.OK) {
// noinspection JSUnresolvedReference
map.setCenter(results[0].geometry.location);
// noinspection JSUnresolvedReference
new google.maps.Marker({
map: map,
position: results[0].geometry.location,
});
}
});
}
}
// noinspection JSUnusedGlobalSymbols
export default GoogleMapsRenderer;
+63
View File
@@ -0,0 +1,63 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
/**
* @module handlers/map/renderer
*/
/**
* A map renderer.
*
* @abstract
*/
class MapRenderer {
/**
* @typedef {Object} module:handlers/map/renderer~addressData
* @property {string|null} street
* @property {string|null} city
* @property {string|null} country
* @property {string|null} state
* @property {string|null} postalCode
*/
/**
* @param {import('views/fields/map').default} view A field view.
*/
constructor(view) {
this.view = view;
}
/**
* @param {module:handlers/map/renderer~addressData} addressData
* @abstract
*/
render(addressData) {}
}
export default MapRenderer;
+31 -130
View File
@@ -28,10 +28,14 @@ class MapFieldView extends BaseFieldView {
detailTemplate = 'fields/map/detail'
listTemplate = 'fields/map/detail'
addressField = null
provider = null
/** @type {string} */
addressField
/** @type {string} */
provider
height = 300
DEFAULT_PROVIDER = 'Google';
// noinspection JSCheckFunctionSignatures
data() {
const data = super.data();
@@ -45,7 +49,7 @@ class MapFieldView extends BaseFieldView {
setup() {
this.addressField = this.name.slice(0, this.name.length - 3);
this.provider = this.options.provider || this.params.provider;
this.provider = this.provider || this.getConfig().get('mapProvider') || this.DEFAULT_PROVIDER;
this.height = this.options.height || this.params.height || this.height;
const addressAttributeList = Object.keys(this.getMetadata().get('fields.address.fields') || {})
@@ -93,71 +97,43 @@ class MapFieldView extends BaseFieldView {
this.$map = this.$el.find('.map');
if (this.hasAddress()) {
this.processSetHeight(true);
if (this.height === 'auto') {
$(window).off('resize.' + this.cid);
$(window).on('resize.' + this.cid, this.processSetHeight.bind(this));
}
let methodName = 'afterRender' + this.provider.replace(/\s+/g, '');
if (typeof this[methodName] === 'function') {
this[methodName]();
}
else {
let implClassName = this.getMetadata()
.get(['clientDefs', 'AddressMap', 'implementations', this.provider]);
if (implClassName) {
Espo.loader.require(implClassName, impl => {
impl.render(this);
});
}
}
this.renderMap();
}
}
// noinspection JSUnusedGlobalSymbols
afterRenderGoogle() {
if (window.google && window.google.maps) {
this.initMapGoogle();
renderMap() {
this.processSetHeight(true);
if (this.height === 'auto') {
$(window).off('resize.' + this.cid);
$(window).on('resize.' + this.cid, this.processSetHeight.bind(this));
}
const rendererId = this.getMetadata().get(['app', 'mapProviders', this.provider, 'renderer']);
if (rendererId) {
Espo.loader.require(rendererId, Renderer => {
(new Renderer(this)).render(this.addressData);
});
return;
}
// noinspection SpellCheckingInspection
if (typeof window.mapapiloaded === 'function') {
// noinspection SpellCheckingInspection
let mapapiloaded = window.mapapiloaded;
const methodName = 'afterRender' + this.provider.replace(/\s+/g, '');
// noinspection SpellCheckingInspection
window.mapapiloaded = () => {
this.initMapGoogle();
mapapiloaded();
};
if (typeof this[methodName] === 'function') {
this[methodName]();
return;
}
// noinspection SpellCheckingInspection
window.mapapiloaded = () => {
this.initMapGoogle();
};
// For bc.
// @todo Remove in v9.0.
const implId = this.getMetadata().get(['clientDefs', 'AddressMap', 'implementations', this.provider]);
let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
let apiKey = this.getConfig().get('googleMapsApiKey');
if (apiKey) {
src += '&key=' + apiKey;
if (implId) {
Espo.loader.require(implId, impl => impl.render(this));
}
let scriptElement = document.createElement('script');
scriptElement.setAttribute('async', 'async');
scriptElement.src = src;
document.head.appendChild(scriptElement);
}
processSetHeight(init) {
@@ -167,9 +143,7 @@ class MapFieldView extends BaseFieldView {
height = this.$el.parent().height();
if (init && height <= 0) {
setTimeout(() => {
this.processSetHeight(true);
}, 50);
setTimeout(() => this.processSetHeight(true), 50);
return;
}
@@ -177,79 +151,6 @@ class MapFieldView extends BaseFieldView {
this.$map.css('height', height + 'px');
}
initMapGoogle() {
const geocoder = new google.maps.Geocoder();
let map;
try {
// noinspection SpellCheckingInspection
map = new google.maps.Map(this.$el.find('.map').get(0), {
zoom: 15,
center: {lat: 0, lng: 0},
scrollwheel: false,
});
}
catch (e) {
console.error(e.message);
return;
}
let address = '';
if (this.addressData.street) {
address += this.addressData.street;
}
if (this.addressData.city) {
if (address !== '') {
address += ', ';
}
address += this.addressData.city;
}
if (this.addressData.state) {
if (address !== '') {
address += ', ';
}
address += this.addressData.state;
}
if (this.addressData.postalCode) {
if (this.addressData.state || this.addressData.city) {
address += ' ';
}
else {
if (address) {
address += ', ';
}
}
address += this.addressData.postalCode;
}
if (this.addressData.country) {
if (address !== '') {
address += ', ';
}
address += this.addressData.country;
}
geocoder.geocode({'address': address}, (results, status) => {
if (status === google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
new google.maps.Marker({
map: map,
position: results[0].geometry.location,
});
}
});
}
}
export default MapFieldView;
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://www.espocrm.com/schema/metadata/app/mapProviders.json",
"title": "app/mapProviders",
"description": "Map providers.",
"type": "object",
"additionalProperties": {
"type": "object",
"description": "A provider name.",
"properties": {
"renderer": {
"type": "string",
"description": "A frontend renderer. Should extend handlers/map/renderer."
}
}
}
}