diff --git a/.eslintrc b/.eslintrc index edc9138d7455e8743114e0a5b5b6647fee4814e0..9af3fc79b55ed079012dfb25a07eb94ca007a8b6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,6 +19,7 @@ "new-cap": "off", "radix": "off", "no-jquery/no-ajax-events": "off", + "no-case-declarations": "off", "no-restricted-syntax": "off", "no-bitwise": "off" }, diff --git a/geolocation.module b/geolocation.module index 73066d3aa4f34f069729862de073a916f20ce594..73abfcce5dd69713e1a4229ef32d4fbfe229dfe1 100644 --- a/geolocation.module +++ b/geolocation.module @@ -70,7 +70,7 @@ function geolocation_theme(): array { 'row' => NULL, ], ], - 'geolocation_map_geometry' => [ + 'geolocation_map_shape' => [ 'variables' => [ 'attributes' => NULL, 'children' => NULL, diff --git a/geolocation.views.inc b/geolocation.views.inc index 066803cf85fd1a843d451e61f4213fb1a1ad4999..6cfb5e588a5ca4847e47f14ee2a261c55e8dbff2 100644 --- a/geolocation.views.inc +++ b/geolocation.views.inc @@ -16,7 +16,7 @@ function geolocation_field_views_data(FieldStorageConfigInterface $field_storage $entity_definition = Drupal::entityTypeManager()->getDefinition($field_storage->getTargetEntityTypeId()); // Get the default data from the views module. - $data = Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage); + $data = \Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage); $title_short = $help = ''; diff --git a/js/Base/GeolocationGeometry.js b/js/Base/GeolocationGeometry.js new file mode 100644 index 0000000000000000000000000000000000000000..c0ca8e02da00c90f2070646baa1f01090dc2a222 --- /dev/null +++ b/js/Base/GeolocationGeometry.js @@ -0,0 +1,14 @@ +/** + * @prop {string} type + * @prop { number[] | number[][] | number[][][] | number[][][][]} coordinates + */ +export class GeolocationGeometry { + /** + * @param {string} type + * @param {array} coordinates + */ + constructor(type, coordinates) { + this.type = type; + this.coordinates = coordinates; + } +} diff --git a/js/Base/GeolocationShape.js b/js/Base/GeolocationShape.js index d638b1808778a873b909b4cbbae4d33be2b5f3f4..74c7cdf2b78390aa765bd1f175391700fc34dc4f 100644 --- a/js/Base/GeolocationShape.js +++ b/js/Base/GeolocationShape.js @@ -12,14 +12,6 @@ * @prop {Number} [fillOpacity] */ -/** - * @typedef {Object} GeolocationGeometry - * - * @prop {GeolocationCoordinates[]} [points] - * @prop {Array.<GeolocationCoordinates[]>} [lines] - * @prop {Array.<GeolocationCoordinates[]>} [polygons] - */ - import { GeolocationCoordinates } from "./GeolocationCoordinates.js"; import { GeolocationBoundaries } from "./GeolocationBoundaries.js"; @@ -72,37 +64,6 @@ export class GeolocationShape { } } - /** - * @param {Element} metaWrapper - * Element. - * @return {GeolocationCoordinates[]} - * Points. - */ - static getPointsByGeoShapeMeta(metaWrapper) { - const points = []; - - if (!metaWrapper) { - return points; - } - - metaWrapper - .getAttribute("content") - ?.split(" ") - .forEach((value) => { - const coordinates = value.split(","); - if (coordinates.length !== 2) { - return; - } - - const lat = parseFloat(coordinates[0]); - const lon = parseFloat(coordinates[1]); - - points.push(new GeolocationCoordinates(lat, lon)); - }); - - return points; - } - getContent() { if (!this.content) { this.content = this.wrapper?.querySelector(".location-content")?.innerHTML ?? ""; @@ -112,7 +73,7 @@ export class GeolocationShape { } /** - * @param {Object} [geometry] + * @param {GeolocationGeometry} [geometry] * Geometry. * @param {GeolocationShapeSettings} [settings] * Settings. @@ -169,30 +130,30 @@ export class GeolocationShape { switch (this.type) { case "line": case "polygon": - this.geometry.points.forEach((value) => { - bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north; - bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south; - bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east; - bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west; + this.geometry.coordinates.forEach((value) => { + bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north; + bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south; + bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east; + bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west; }); break; case "multiline": - this.geometry.lines.forEach((line) => { - line.points.forEach((value) => { - bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north; - bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south; - bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east; - bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west; + this.geometry.coordinates.forEach((line) => { + line.coordinates.forEach((value) => { + bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north; + bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south; + bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east; + bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west; }); }); break; case "multipolygon": - this.geometry.polygons.forEach((polygon) => { - polygon.points.forEach((value) => { - bounds.north = bounds.north === null || value.lat > bounds.north ? value.lat : bounds.north; - bounds.south = bounds.south === null || value.lat < bounds.south ? value.lat : bounds.south; - bounds.east = bounds.east === null || value.lat > bounds.east ? value.lat : bounds.east; - bounds.west = bounds.west === null || value.lat < bounds.west ? value.lat : bounds.west; + this.geometry.coordinates.forEach((polygon) => { + polygon.coordinates.forEach((value) => { + bounds.north = bounds.north === null || value[1] > bounds.north ? value[1] : bounds.north; + bounds.south = bounds.south === null || value[1] < bounds.south ? value[1] : bounds.south; + bounds.east = bounds.east === null || value[0] > bounds.east ? value[0] : bounds.east; + bounds.west = bounds.west === null || value[0] < bounds.west ? value[0] : bounds.west; }); }); break; @@ -205,7 +166,11 @@ export class GeolocationShape { return new GeolocationBoundaries(bounds); } - remove() {} + remove() { + this.map.dataLayers.forEach((layer) => { + layer.shapeRemoved(this); + }); + } /** * Click handler delegation. diff --git a/js/Base/GeolocationShapeLine.js b/js/Base/GeolocationShapeLine.js index 65734e35091410d093868bc7f809d9ccfc55cd71..c9f89029bc1ff302766676cd6949622acdb50cbf 100644 --- a/js/Base/GeolocationShapeLine.js +++ b/js/Base/GeolocationShapeLine.js @@ -1,9 +1,7 @@ -import { GeolocationCoordinates } from "./GeolocationCoordinates.js"; import { GeolocationShape } from "./GeolocationShape.js"; /** - * @prop {Object} geometry - * @prop {GeolocationCoordinates[]} geometry.points + * @prop {GeolocationGeometry} geometry */ export class GeolocationShapeLine extends GeolocationShape { constructor(geometry, settings = {}, map) { diff --git a/js/Base/GeolocationShapeMultiLine.js b/js/Base/GeolocationShapeMultiLine.js index 2a7697953d4af83ece18f765131ba8511f11da24..beb1761991cb402c88c1f686ee5e023148b4f5db 100644 --- a/js/Base/GeolocationShapeMultiLine.js +++ b/js/Base/GeolocationShapeMultiLine.js @@ -1,9 +1,7 @@ -import { GeolocationCoordinates } from "./GeolocationCoordinates.js"; import { GeolocationShape } from "./GeolocationShape.js"; /** - * @prop {Object} geometry - * @prop {{points: GeolocationCoordinates[]}} geometry.lines + * @prop {GeolocationGeometry} geometry */ export class GeolocationShapeMultiLine extends GeolocationShape { constructor(geometry, settings = {}, map) { diff --git a/js/Base/GeolocationShapeMultiPolygon.js b/js/Base/GeolocationShapeMultiPolygon.js index b41e6591ccc30cd349a10873513da2e4ce43537b..38754cfa9c95cf4e49b0078374052447b2b0dd9b 100644 --- a/js/Base/GeolocationShapeMultiPolygon.js +++ b/js/Base/GeolocationShapeMultiPolygon.js @@ -2,8 +2,7 @@ import { GeolocationCoordinates } from "./GeolocationCoordinates.js"; import { GeolocationShape } from "./GeolocationShape.js"; /** - * @prop {Object} geometry - * @prop {{points: GeolocationCoordinates[]}} geometry.polygons + * @prop {GeolocationGeometry} geometry */ export class GeolocationShapeMultiPolygon extends GeolocationShape { constructor(geometry, settings = {}, map) { diff --git a/js/Base/GeolocationShapePolygon.js b/js/Base/GeolocationShapePolygon.js index 481575addad07837c3b179633d3a0184e827a299..fe978c40f3fee99dfe909fec17e2355f24b6c0a8 100644 --- a/js/Base/GeolocationShapePolygon.js +++ b/js/Base/GeolocationShapePolygon.js @@ -1,9 +1,7 @@ -import { GeolocationCoordinates } from "./GeolocationCoordinates.js"; import { GeolocationShape } from "./GeolocationShape.js"; /** - * @prop {Object} geometry - * @prop {GeolocationCoordinates[]} geometry.points + * @prop {GeolocationGeometry} geometry */ export class GeolocationShapePolygon extends GeolocationShape { constructor(geometry, settings = {}, map) { diff --git a/js/DataLayerProvider/GeolocationDataLayer.js b/js/DataLayerProvider/GeolocationDataLayer.js index 425bab333eaca24f5f19deda3153e128d9803805..0f2b775575f9b538abfd33d866a0387fa4a55082 100644 --- a/js/DataLayerProvider/GeolocationDataLayer.js +++ b/js/DataLayerProvider/GeolocationDataLayer.js @@ -6,7 +6,7 @@ import { GeolocationShape } from "../Base/GeolocationShape.js"; * * @prop {String} import_path * @prop {Object} settings - * @prop {Object.<string, Object>} features + * @prop {Map<string, GeolocationLayerFeature>} features * @prop {String[]} scripts * @prop {String[]} async_scripts * @prop {String[]} stylesheets @@ -28,7 +28,7 @@ export default class GeolocationDataLayer { constructor(map, id, layerSettings) { this.map = map; this.settings = layerSettings.settings; - this.features = []; + this.features = new Map(); this.markers = []; this.shapes = []; this.id = id; @@ -37,10 +37,12 @@ export default class GeolocationDataLayer { /** * @param {GeolocationLayerFeatureSettings} layerFeatureSettings * Layer feature settings. + * @param {?string} id + * Layer feature ID. * @return {Promise<GeolocationLayerFeature>|null} * Loading feature Promise. */ - loadFeature(layerFeatureSettings) { + loadFeature(layerFeatureSettings, id = null) { if (!layerFeatureSettings.import_path) { return null; } @@ -76,7 +78,7 @@ export default class GeolocationDataLayer { .then((featureImport) => { try { const feature = new featureImport.default(layerFeatureSettings.settings, this); - this.features.push(feature); + this.features.set(id, feature); return feature; } catch (e) { @@ -93,7 +95,7 @@ export default class GeolocationDataLayer { const featureImports = []; Object.keys(this.settings.features ?? {}).forEach((featureName) => { - const featurePromise = this.loadFeature(this.settings.features[featureName]); + const featurePromise = this.loadFeature(this.settings.features[featureName], featureName); if (featurePromise) { featureImports.push(featurePromise); @@ -234,63 +236,10 @@ export default class GeolocationDataLayer { fillOpacity: shapeElement.getAttribute("data-fill-opacity") ?? 0.2, }; - let geometry = {}; - const geometryWrapper = shapeElement.querySelector(".geometry"); - if (!geometryWrapper) { - return; - } - - let points; - - switch (geometryWrapper.getAttribute("data-type")) { - case "line": - case "polygon": - points = GeolocationShape.getPointsByGeoShapeMeta(geometryWrapper.querySelector('span[typeof="GeoShape"] meta')); - - if (!points) { - break; - } - geometry = { - points, - }; - break; - - case "multiline": - geometry = { - lines: [], - }; - geometryWrapper.querySelectorAll('span[typeof="GeoShape"] meta').forEach((meta) => { - points = GeolocationShape.getPointsByGeoShapeMeta(meta); - if (!points) { - return; - } - geometry.lines.push({ - points, - }); - }); - break; - - case "multipolygon": - geometry = { - polygons: [], - }; - geometryWrapper.querySelectorAll('span[typeof="GeoShape"] meta').forEach((meta) => { - points = GeolocationShape.getPointsByGeoShapeMeta(meta); - if (!points) { - return; - } - geometry.polygons.push({ - points, - }); - }); - break; - - default: - console.error("Unknown shape type cannot be added."); - } + const geometry = JSON.parse(shapeElement.querySelector(".geometry")?.textContent); let shape; - switch (geometryWrapper.getAttribute("data-type")) { + switch (shapeElement.getAttribute("data-geometry-type")) { case "line": shape = this.map.createShapeLine(geometry, settings); break; diff --git a/js/GeolocationWidgetBroker.js b/js/GeolocationWidgetBroker.js index 3658bb952c33a3cd20cf91c0e21b17786448c803..7ffb56208755d18624a5eb883c603ce374ceb58d 100644 --- a/js/GeolocationWidgetBroker.js +++ b/js/GeolocationWidgetBroker.js @@ -112,4 +112,65 @@ export default class GeolocationWidgetBroker { } }); } + + /** + * @param {GeolocationGeometry} geometry + * Geometry. + * @param {Number} index + * Index. + * @param {String} caller + * Calling entity. + */ + geometryAdded(geometry, index, caller) { + this.subscribers.forEach((subscriber, id) => { + if (id === caller) { + return; + } + try { + subscriber.addGeometry(geometry, index, caller); + } catch (e) { + console.error(e, `Subscriber ${subscriber.id} failed addGeometry: ${e.toString()}`); + } + }); + } + + /** + * @param {Number} index + * Index. + * @param {String} caller + * Caller. + */ + geometryRemoved(index, caller) { + this.subscribers.forEach((subscriber, id) => { + if (id === caller) { + return; + } + try { + subscriber.removeGeometry(index, caller); + } catch (e) { + console.error(e, `Subscriber ${subscriber.id} failed removeGeometry: ${e.toString()}`); + } + }); + } + + /** + * @param {GeolocationGeometry} geometry + * Geometry. + * @param {Number} index + * Index. + * @param {String} caller + * Caller. + */ + geometryAltered(geometry, index, caller) { + this.subscribers.forEach((subscriber, id) => { + if (id === caller) { + return; + } + try { + subscriber.alterGeometry(geometry, index, caller); + } catch (e) { + console.error(e, `Subscriber ${subscriber.id} failed alterGeometry: ${e.toString()}`); + } + }); + } } diff --git a/js/MapFeature/GeolocationFieldWidgetMapConnector.js b/js/MapFeature/GeolocationFieldWidgetMapConnector.js index 6e88bf3fd9e3cfe8d24726caeda288c029caa4de..463308eae358c84e88a0be4aa82ae35b87603584 100644 --- a/js/MapFeature/GeolocationFieldWidgetMapConnector.js +++ b/js/MapFeature/GeolocationFieldWidgetMapConnector.js @@ -1,9 +1,16 @@ +/** + * @typedef {Object} GeolocationFieldWidgetMapConnectorSettings + * + * @prop {int} cardinality + * @prop {string} field_type + */ + import { GeolocationMapFeature } from "./GeolocationMapFeature.js"; /** * @prop {WidgetSubscriberBase} subscriber - * @prop {Object} settings - * @prop {int} settings.cardinality + * + * @prop {GeolocationFieldWidgetMapConnectorSettings} settings */ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFeature { setWidgetSubscriber(subscriber) { @@ -79,7 +86,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe this.map.dataLayers.get("default").markerAdded(marker); delete marker.geolocationWidgetIgnore; - this.map.fitMapToMarkers(); + this.map.fitMapToElements(); return marker; } @@ -113,7 +120,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe marker.update(coordinates, settings ?? {}); delete marker.geolocationWidgetIgnore; - this.map.fitMapToMarkers(); + this.map.fitMapToElements(); return marker; } @@ -124,15 +131,13 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe marker.geolocationWidgetIgnore = true; marker.remove(); - this.map.fitMapToMarkers(); + this.map.fitMapToElements(); } onClick(coordinates) { super.onClick(coordinates); - const numberOfMarkers = this.map.dataLayers.get("default").markers.length; - - if (this.settings.cardinality > numberOfMarkers || this.settings.cardinality === -1) { + if (this.settings.cardinality > this.map.dataLayers.get("default").markers.length || this.settings.cardinality === -1) { let newIndex = 0; this.map.dataLayers.get("default").markers.forEach((marker) => { const markerIndex = this.getIndexByMarker(marker) ?? 0; @@ -158,7 +163,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe const warning = document.createElement("div"); warning.innerHTML = `<p>${Drupal.t("Maximum number of locations reached.")}</p>`; Drupal.dialog(warning, { - title: Drupal.t("Address synchronization"), + title: Drupal.t("Synchronization"), }).showModal(); } else { const marker = this.getMarkerByIndex(0); @@ -174,7 +179,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe if (marker.geolocationWidgetIgnore ?? false) return; - this.subscriber.coordinatesAdded(marker.coordinates, this.getIndexByMarker(marker) ?? 0); + this.subscriber?.coordinatesAdded(marker.coordinates, this.getIndexByMarker(marker) ?? 0); } onMarkerClicked(marker) { @@ -189,7 +194,7 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe if (marker.geolocationWidgetIgnore ?? false) return; - this.subscriber.coordinatesAltered(marker.coordinates, this.getIndexByMarker(marker)); + this.subscriber?.coordinatesAltered(marker.coordinates, this.getIndexByMarker(marker)); } onMarkerRemove(marker) { @@ -197,6 +202,6 @@ export default class GeolocationFieldWidgetMapConnector extends GeolocationMapFe if (marker.geolocationWidgetIgnore ?? false) return; - this.subscriber.coordinatesRemoved(this.getIndexByMarker(marker)); + this.subscriber?.coordinatesRemoved(this.getIndexByMarker(marker)); } } diff --git a/js/MapFeature/GeolocationMapFeature.js b/js/MapFeature/GeolocationMapFeature.js index 7faab9a7abccea66269b5ff389859309ec422bb2..654ca08b1cf5b8fe845c8c09f714c5e2ad17ea1a 100644 --- a/js/MapFeature/GeolocationMapFeature.js +++ b/js/MapFeature/GeolocationMapFeature.js @@ -18,6 +18,8 @@ export class GeolocationMapFeature { /** * @constructor * + * Called when map is initialized, but no map content is loaded yet. + * * @param {GeolocationMapFeatureSettings} settings * Settings. * @param {GeolocationMapBase} map @@ -52,6 +54,9 @@ export class GeolocationMapFeature { */ onContextClick(coordinates) {} + /** + * Called when map content is fully loaded. + */ onMapReady() {} onMapIdle() {} diff --git a/js/MapProvider/GeolocationMapBase.js b/js/MapProvider/GeolocationMapBase.js index 9a0f8833086c37693fc84884aaa13bc9dd175043..5b66591e57ee710a32b9e884ce568b2b7b581ef3 100644 --- a/js/MapProvider/GeolocationMapBase.js +++ b/js/MapProvider/GeolocationMapBase.js @@ -50,7 +50,7 @@ import { GeolocationShape } from "../Base/GeolocationShape.js"; * @prop {HTMLElement} container * @prop {Map<String, GeolocationDataLayer>} dataLayers * @prop {Map<String, Object>} tileLayers - * @prop {GeolocationMapFeature[]} features + * @prop {Map<String, GeolocationMapFeature>} features * @prop {GeolocationMapCenterBase[]} mapCenter */ export class GeolocationMapBase { @@ -64,7 +64,7 @@ export class GeolocationMapBase { throw new Error("Geolocation - Map container not found"); } - this.features = []; + this.features = new Map(); this.mapCenter = []; this.dataLayers = new Map(); this.tileLayers = new Map(); @@ -127,10 +127,12 @@ export class GeolocationMapBase { /** * @param {GeolocationMapFeatureSettings} featureSettings * Feature settings. + * @param {?string} id + * Feature ID. * @return {Promise<GeolocationMapFeature>|null} * Loaded feature. */ - loadFeature(featureSettings) { + loadFeature(featureSettings, id = null) { if (!featureSettings.import_path) { return null; } @@ -157,7 +159,7 @@ export class GeolocationMapBase { .then((featureImport) => { try { const feature = new featureImport.default(featureSettings.settings, this); - this.features.push(feature); + this.features.set(id, feature); return feature; } catch (e) { @@ -173,7 +175,7 @@ export class GeolocationMapBase { const featureImports = []; Object.keys(this.settings.features ?? {}).forEach((featureName) => { - const featurePromise = this.loadFeature(this.settings.features[featureName]); + const featurePromise = this.loadFeature(this.settings.features[featureName], featureName); if (featurePromise) { featureImports.push(featurePromise); diff --git a/js/WidgetSubscriber/FieldWidgetBase.js b/js/WidgetSubscriber/FieldWidgetBase.js index 42a32eeee7f7c9ee9df2f0725c3a9f98e7bfdf74..8956e14b65916d106a3ba3aaac27b977994362de 100644 --- a/js/WidgetSubscriber/FieldWidgetBase.js +++ b/js/WidgetSubscriber/FieldWidgetBase.js @@ -12,6 +12,8 @@ import { WidgetSubscriberBase } from "./WidgetSubscriberBase.js"; import { GeolocationCoordinates } from "../Base/GeolocationCoordinates.js"; /** + * @abstract + * * @prop {GeolocationWidgetBroker} broker * @prop {Object} settings */ @@ -137,7 +139,7 @@ export class FieldWidgetBase extends WidgetSubscriberBase { */ getAllInputElements(returnElements = false) { const map = new Map(); - const elements = this.form.querySelectorAll(".geolocation-widget-input"); + const elements = this.form.querySelectorAll(this.getElementSelector()); if (returnElements) { return elements; @@ -163,6 +165,10 @@ export class FieldWidgetBase extends WidgetSubscriberBase { return parseInt(element.getAttribute("data-geolocation-widget-index")); } + getElementSelector() { + return ".geolocation-widget-input"; + } + getElementSelectorByIndex(index) { return `[data-geolocation-widget-index='${index.toString()}']`; } @@ -208,8 +214,18 @@ export class FieldWidgetBase extends WidgetSubscriberBase { return promise; } + /** + * @param {GeolocationCoordinates} coordinates + * @param {Element} element + */ setCoordinatesByElement(coordinates, element) {} + /** + * @param {GeolocationGeometry} geometry + * @param {Element} element + */ + setGeometryByElement(geometry, element) {} + /** * @param {Element} element * Element. @@ -221,6 +237,17 @@ export class FieldWidgetBase extends WidgetSubscriberBase { return null; } + /** + * @param {Element} element + * Element. + * + * @return {Promise<GeolocationGeometry>} + * Coordinates. + */ + getGeometryByElement(element) { + return null; + } + reorder(newOrder, source) { super.reorder(newOrder, source); @@ -292,4 +319,28 @@ export class FieldWidgetBase extends WidgetSubscriberBase { this.setCoordinatesByElement(coordinates, element); }); } + + addGeometry(geometry, index, source) { + super.addGeometry(geometry, index, source); + + this.getElementByIndex(index).then((element) => { + this.setGeometryByElement(geometry, element); + }); + } + + removeGeometry(index, source) { + super.removeGeometry(index, source); + + this.getElementByIndex(index).then((element) => { + this.setGeometryByElement(null, element); + }); + } + + alterGeometry(geometry, index, source) { + super.alterGeometry(geometry, index, source); + + this.getElementByIndex(index).then((element) => { + this.setGeometryByElement(geometry, element); + }); + } } diff --git a/js/WidgetSubscriber/GeolocationFieldMapWidget.js b/js/WidgetSubscriber/GeolocationFieldMapWidget.js index e326f4cb804309b740f6099c8354101341e691d8..94b6fff27560871eccca7447db1e47a87e9b4789 100644 --- a/js/WidgetSubscriber/GeolocationFieldMapWidget.js +++ b/js/WidgetSubscriber/GeolocationFieldMapWidget.js @@ -13,18 +13,16 @@ export default class GeolocationFieldMapWidget extends WidgetSubscriberBase { Drupal.geolocation.maps.getMap(settings.mapId).then((map) => { this.map = map; - this.settings.featureSettings.settings = this.settings.featureSettings.settings ?? {}; - this.settings.featureSettings.settings.cardinality = this.settings.featureSettings.settings.cardinality ?? this.settings.cardinality; - - this.map.loadFeature(this.settings.featureSettings).then( - /** @param {GeolocationFieldWidgetMapConnector} feature Feature */ (feature) => { - this.mapFeature = feature; - if (typeof this.mapFeature.setWidgetSubscriber === "function") { - this.mapFeature.setWidgetSubscriber(this); - } - resolve(feature); + this.map.features.forEach((feature, id) => { + if (this.settings.feature_id !== id) { + return; } - ); + this.mapFeature = feature; + if (typeof this.mapFeature.setWidgetSubscriber === "function") { + this.mapFeature.setWidgetSubscriber(this); + } + resolve(feature); + }); }); }); } diff --git a/js/WidgetSubscriber/GeolocationFieldWidget.js b/js/WidgetSubscriber/GeolocationFieldWidget.js index 0ce4c59f14aab93c981b478ea5c28e84557948fc..d867157b6c64f29cdb218bc81d0acc39093df9bd 100644 --- a/js/WidgetSubscriber/GeolocationFieldWidget.js +++ b/js/WidgetSubscriber/GeolocationFieldWidget.js @@ -36,7 +36,7 @@ export default class GeolocationFieldWidget extends FieldWidgetBase { } getElementSelectorByIndex(index) { - return `.geolocation-widget-input[data-geolocation-widget-index='${index.toString()}']`; + return `${this.getElementSelector()}[data-geolocation-widget-index='${index.toString()}']`; } getCoordinatesByElement(element) { diff --git a/js/WidgetSubscriber/WidgetSubscriberBase.js b/js/WidgetSubscriber/WidgetSubscriberBase.js index e130639d3511eb9eb6ca825ee08b1a7616fa1061..01e687886c0ee222241c4f43bda02ff32a5ffba1 100644 --- a/js/WidgetSubscriber/WidgetSubscriberBase.js +++ b/js/WidgetSubscriber/WidgetSubscriberBase.js @@ -3,6 +3,8 @@ */ /** + * @abstract + * * @prop {String} id * @prop {Object} settings * @prop {int} settings.cardinality @@ -51,4 +53,32 @@ export class WidgetSubscriberBase { * Source. */ alterCoordinates(coordinates, index, source) {} + + /** + * @param {GeolocationGeometry} geometry + * Shape. + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + addGeometry(geometry, index, source) {} + + /** + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + removeGeometry(index, source) {} + + /** + * @param {GeolocationGeometry} geometry + * Shape. + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + alterGeometry(geometry, index, source) {} } diff --git a/modules/geolocation_geometry/geolocation_geometry.views.inc b/modules/geolocation_geometry/geolocation_geometry.views.inc index 5a50618b4d76d405bc2805c654ca128acc422265..1f41caf651fdfd0b03ac6a20e386868805fb5dc6 100644 --- a/modules/geolocation_geometry/geolocation_geometry.views.inc +++ b/modules/geolocation_geometry/geolocation_geometry.views.inc @@ -55,7 +55,7 @@ function geolocation_geometry_field_views_data(FieldStorageConfigInterface $fiel } // Get the default data from the views module. - $data = Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage); + $data = \Drupal::service('views.field_data_provider')->defaultFieldImplementation($field_storage); $args = ['@field_name' => $field_storage->getName()]; diff --git a/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js new file mode 100644 index 0000000000000000000000000000000000000000..945a100c922d3c2ce2ae227be5a9a362d58c76c3 --- /dev/null +++ b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js @@ -0,0 +1,83 @@ +import GeolocationFieldMapWidget from "../../../../js/WidgetSubscriber/GeolocationFieldMapWidget.js"; + +/** + * @prop {GeolocationMapBase} map + * @prop {GeolocationMapFeature} mapFeature + */ +export default class GeolocationGeometryFieldMapWidget extends GeolocationFieldMapWidget { + /** + * @return {Promise<GeolocationMapFeature>} + */ + getMapFeature() { + return super.getMapFeature(); + } + + onClick() {} + + /** + * @param {GeolocationGeometry} geometry + * Shape. + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + addGeometry(geometry, index, source) { + super.addGeometry(geometry, index, source); + + switch (geometry.type) { + case "Point": + case "MultiPoint": + if (this.broker.settings.cardinality > 0 && this.map.dataLayers.get("default").markers.length >= this.broker.settings.cardinality) { + console.error(Drupal.t(`Maximum number of entries reached. Cardinality set to ${this.broker.settings.cardinality}`)); + return; + } + break; + + default: + if (this.broker.settings.cardinality > 0 && this.map.dataLayers.get("default").shapes.length >= this.broker.settings.cardinality) { + console.error(Drupal.t(`Maximum number of entries reached. Cardinality set to ${this.broker.settings.cardinality}`)); + return; + } + } + this.getMapFeature().then((feature) => feature.addShapeSilently(index, geometry)); + } + + /** + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + removeGeometry(index, source) { + super.removeGeometry(index, source); + + this.getMapFeature().then((feature) => feature.removeShapeSilently(index)); + } + + /** + * @param {GeolocationGeometry} geometry + * Shape. + * @param {Number} index + * Index. + * @param {String} source + * Source. + */ + alterGeometry(geometry, index, source) { + super.alterGeometry(geometry, index, source); + + this.getMapFeature().then((feature) => feature.updateShapeSilently(index, geometry)); + } + + geometryAltered(geometry, index) { + this.broker.geometryAltered(geometry, index, this.id); + } + + geometryAdded(geometry, index) { + this.broker.geometryAdded(geometry, index, this.id); + } + + geometryRemoved(index) { + this.broker.geometryRemoved(index, this.id); + } +} diff --git a/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js new file mode 100644 index 0000000000000000000000000000000000000000..86b4df1f4aed0625bf5a82fe5cc7e6c648992c08 --- /dev/null +++ b/modules/geolocation_geometry/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js @@ -0,0 +1,77 @@ +/** + * @name GeolocationWidgetSettings + * + * @prop {String} id + * @prop {String} type + * @prop {String} fieldName + * @prop {String} cardinality + */ + +import { FieldWidgetBase } from "../../../../js/WidgetSubscriber/FieldWidgetBase.js"; +import { GeolocationGeometry } from "../../../../js/Base/GeolocationGeometry.js"; + +export default class GeolocationGeometryFieldWidget extends FieldWidgetBase { + getElementSelector() { + return ".geolocation-geometry-widget-geojson-input"; + } + + onFormChange(element, index) { + this.getGeometryByElement(element) + .then((newGeometry) => { + if (!newGeometry) { + this.broker.geometryRemoved(index, this.id); + } else { + this.broker.geometryAltered(newGeometry, index, this.id); + } + }) + .catch(() => { + this.broker.geometryRemoved(index, this.id); + }); + } + + setGeometryByElement(geometry, element) { + if (geometry) { + element.value = JSON.stringify(geometry); + element.setAttribute("data-geolocation-current-value", JSON.stringify(geometry)); + } else { + element.value = ""; + element.setAttribute("data-geolocation-current-value", ""); + } + } + + getElementSelectorByIndex(index) { + return `.geolocation-geometry-widget-geojson-input[data-geolocation-widget-index='${index.toString()}']`; + } + + getGeometryByElement(element) { + let geometry; + + try { + geometry = JSON.parse(element.value); + } catch (e) { + return Promise.reject(new Error(`GeolocationGeometryFieldWidget: Cannot get geometry by element due to JSON error. ${e.toString()}`)); + } + + if (!geometry.type || !geometry.coordinates) { + return Promise.reject(new Error("GeolocationGeometryFieldWidget: Cannot get geometry as type or coordinates are missing.")); + } + + return Promise.resolve(new GeolocationGeometry(geometry.type, geometry.coordinates)); + } + + alterGeometry(geometry, index, source) { + this.getElementByIndex(index).then((element) => { + this.getGeometryByElement(element) + .then((currentGeometry) => { + if (currentGeometry === geometry) { + return; + } + + super.alterGeometry(geometry, index, source); + }) + .catch(() => { + super.alterGeometry(geometry, index, source); + }); + }); + } +} diff --git a/modules/geolocation_geometry/src/GeometryFormat/WKT.php b/modules/geolocation_geometry/src/GeometryFormat/WKT.php index fe318e0482b51e950751b2906dafdf0e3a2086de..fd60c19b0cb8b8e1297f3e758a63857d95780bf6 100644 --- a/modules/geolocation_geometry/src/GeometryFormat/WKT.php +++ b/modules/geolocation_geometry/src/GeometryFormat/WKT.php @@ -82,11 +82,12 @@ class WKT implements GeometryFormatInterface { // phpcs:ignore case 'Polygon': case 'MultiPolygon': $components = []; + /** @var string $subvalue */ foreach (preg_split('/\)\s*,\s*\(/', $value) as $subvalue) { - if ($subvalue[0] ?? FALSE == '(') { + if (($subvalue[0] ?? FALSE) === '(') { $subvalue = substr($subvalue, 1); } - if ($subvalue[strlen($subvalue) - 1] ?? FALSE == ')') { + if (($subvalue[strlen($subvalue) - 1] ?? FALSE) == ')') { $subvalue = substr($subvalue, 0, -1); } diff --git a/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php b/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php index b37d59a798d8592b600f544dc00952ad9c1a01e5..a7b5cad665f9fb8a447e057a38311af19d0e137c 100644 --- a/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php +++ b/modules/geolocation_geometry/src/Plugin/Field/FieldType/GeolocationGeometryBase.php @@ -12,6 +12,8 @@ use Drupal\Core\TypedData\MapDataDefinition; * Class Geolocation Geometry Base. * * @package Drupal\geolocation_geometry\Plugin\Field\FieldType + * + * @property string $geojson */ abstract class GeolocationGeometryBase extends FieldItemBase { @@ -134,6 +136,7 @@ abstract class GeolocationGeometryBase extends FieldItemBase { * Coordinates. */ protected static function getRandomCoordinates(?array $reference_point = NULL, float $range = 5): array { + // @todo Update to Gemeotry! if ($reference_point) { return [ 'latitude' => rand( diff --git a/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php b/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..41a46fcb82af76a469b1229e9867bd577d7f8fc9 --- /dev/null +++ b/modules/geolocation_geometry/src/Plugin/Field/FieldWidget/GeolocationGeometryMapWidget.php @@ -0,0 +1,105 @@ +<?php + +namespace Drupal\geolocation_geometry\Plugin\Field\FieldWidget; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\BubbleableMetadata; +use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationMapWidgetBase; +use Drupal\geolocation_geometry\Plugin\geolocation\DataProvider\GeolocationGeometry; + +/** + * Plugin implementation of the 'geolocation_map' widget. + */ +abstract class GeolocationGeometryMapWidget extends GeolocationMapWidgetBase { + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { + $element['#type'] = 'container'; + $element['#attributes'] = [ + 'data-geometry-type' => str_replace('geolocation_geometry_', '', $this->fieldDefinition->getType()), + 'class' => [ + str_replace('_', '-', $this->getPluginId()) . '-geojson', + ], + ]; + + $element['geojson'] = [ + '#type' => 'textarea', + '#title' => $this->t('GeoJSON'), + '#default_value' => $items[$delta]->geojson ?? NULL, + '#empty_value' => '', + '#required' => $element['#required'], + '#attributes' => [ + 'class' => [ + 'geolocation-geometry-widget-geojson-input', + ], + ], + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL): array { + $element = parent::form($items, $form, $form_state, $get_delta); + + $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], [ + 'drupalSettings' => [ + 'geolocation' => [ + 'widgetSettings' => [ + $element['#attributes']['id'] => [ + 'widgetSubscribers' => [ + 'geolocation_geometry_field' => [ + 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation_geometry')->getPath() . '/js/WidgetSubscriber/GeolocationGeometryFieldWidget.js', + 'settings' => [ + 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + 'field_name' => $this->fieldDefinition->getName(), + 'field_type' => $this->fieldDefinition->getType(), + ], + ], + 'geolocation_geometry_map' => [ + 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation_geometry')->getPath() . '/js/WidgetSubscriber/GeolocationGeometryFieldMapWidget.js', + 'settings' => [ + 'mapId' => $element['map']['#id'], + 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + 'field_name' => $this->fieldDefinition->getName(), + 'field_type' => $this->fieldDefinition->getType(), + 'feature_id' => $this->getWidgetFeatureId(), + ], + ], + ], + ], + ], + ], + ], + ]); + + /** + * @var Integer $index + * @var \Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryLinestring|\Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryPolygon|\Drupal\geolocation_geometry\Plugin\Field\FieldType\GeolocationGeometryPoint $item + */ + foreach ($items as $index => $item) { + if ($item->isEmpty()) { + continue; + } + + if (!json_validate($item->get('geojson')->getValue())) { + continue; + } + + $element['map']['locations']['location-' . $index] = GeolocationGeometry::getRenderedElementByGeoJSON(json_decode($item->get('geojson')->getValue())); + $element['map']['locations']['location-' . $index]['#title'] = ($index + 1); + $element['map']['locations']['location-' . $index]['#label'] = ($index + 1); + $element['map']['locations']['location-' . $index]['#attributes'] = [ + 'data-geolocation-widget-index' => $index, + ]; + } + + return $element; + } + +} diff --git a/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php b/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php index cef20673b80d2338fd4b4e05d8332fe34e11664e..6c92d197f852c5a845771036895d238e260487f3 100644 --- a/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php +++ b/modules/geolocation_geometry/src/Plugin/geolocation/DataProvider/GeolocationGeometry.php @@ -31,10 +31,10 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf $settings['stroke_color'] = '#FF0044'; $settings['stroke_width'] = 1; - $settings['stroke_opacity'] = 0.8; + $settings['stroke_opacity'] = 0.9; $settings['fill_color'] = '#0033FF'; - $settings['fill_opacity'] = 0.1; + $settings['fill_opacity'] = 0.2; return $settings; @@ -178,13 +178,13 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf public function getShapesFromItem(FieldItemInterface $fieldItem): array { $settings = $this->getSettings(); - $geometries = []; + $shapes = []; - foreach ($this->getShapesFromGeoJson($fieldItem->get('geojson')->getString()) as $shapeElement) { - $geometries[] = self::getRenderedElementByGeoJSON($shapeElement, $settings); + foreach ($this->getShapeGeometriesFromGeoJson($fieldItem->get('geojson')->getString()) as $geometry) { + $shapes[] = self::getRenderedElementByGeoJSON($geometry, $settings); } - return $geometries; + return $shapes; } /** @@ -193,13 +193,13 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf public function getLocationsFromItem(FieldItemInterface $fieldItem): array { $settings = $this->getSettings(); - $positions = []; + $locations = []; - foreach ($this->getLocationsFromGeoJson($fieldItem->get('geojson')->getString()) as $location) { - $positions[] = self::getRenderedElementByGeoJSON($location, $settings); + foreach ($this->getLocationGeometriesFromGeoJson($fieldItem->get('geojson')->getString()) as $geometry) { + $locations[] = self::getRenderedElementByGeoJSON($geometry, $settings); } - return $positions; + return $locations; } /** @@ -259,17 +259,10 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf return $container; case 'Polygon': - $geometry = [ - 'type' => 'polygon', - 'points' => [], - ]; - foreach ($geojson->coordinates[0] as $coordinate) { - $geometry['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]]; - } - return [ - '#type' => 'geolocation_map_geometry', - '#geometry' => $geometry, + '#type' => 'geolocation_map_shape', + '#geometry' => json_encode($geojson), + '#geometry_type' => 'polygon', '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'], '#stroke_width' => (int) $settings['stroke_width'], '#stroke_opacity' => (float) $settings['stroke_opacity'], @@ -278,23 +271,9 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf ]; case 'MultiPolygon': - $geometry = [ - 'type' => 'multipolygon', - 'polygons' => [], - ]; - foreach ($geojson->coordinates as $current_polygon) { - $polygon = [ - 'type' => 'polygon', - 'points' => [], - ]; - foreach ($current_polygon[0] as $coordinate) { - $polygon['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]]; - } - $geometry['polygons'][] = $polygon; - } return [ - '#type' => 'geolocation_map_geometry', - '#geometry' => $geometry, + '#type' => 'geolocation_map_shape', + '#geometry' => json_encode($geojson), '#geometry_type' => 'multipolygon', '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'], '#stroke_width' => (int) $settings['stroke_width'], @@ -304,40 +283,20 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf ]; case 'LineString': - $geometry = [ - 'type' => 'line', - 'points' => [], - ]; - foreach ($geojson->coordinates as $coordinate) { - $geometry['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]]; - } - return [ - '#type' => 'geolocation_map_geometry', - '#$geometry' => $geometry, + '#type' => 'geolocation_map_shape', + '#geometry' => json_encode($geojson), + '#geometry_type' => 'line', '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'], '#stroke_width' => (int) $settings['stroke_width'], '#stroke_opacity' => (float) $settings['stroke_opacity'], ]; case 'MultiLineString': - $geometry = [ - 'type' => 'multiline', - 'lines' => [], - ]; - foreach ($geojson->coordinates as $current_line) { - $line = [ - 'type' => 'line', - 'points' => [], - ]; - foreach ($current_line as $coordinate) { - $line['points'][] = ['lat' => $coordinate[1], 'lng' => $coordinate[0]]; - } - $geometry['lines'][] = $line; - } return [ - '#type' => 'geolocation_map_geometry', - '#geometry' => $geometry, + '#type' => 'geolocation_map_shape', + '#geometry' => json_encode($geojson), + '#geometry_type' => 'multiline', '#stroke_color' => $settings['color_randomize'] ? $random_color : $settings['stroke_color'], '#stroke_width' => (int) $settings['stroke_width'], '#stroke_opacity' => (float) $settings['stroke_opacity'], @@ -373,8 +332,8 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf * @return array * Shapes. */ - protected function getShapesFromGeoJson(string $geoJson): array { - $shapes = []; + protected function getShapeGeometriesFromGeoJson(string $geoJson): array { + $geometries = []; $json = json_decode($geoJson); @@ -394,21 +353,21 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf if (empty($entry->features)) { continue 2; } - $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features))); + $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features))); break; case 'Feature': if (empty($entry->geometry)) { continue 2; } - $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry))); + $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry))); break; case 'GeometryCollection': if (empty($entry->geometries)) { continue 2; } - $shapes = array_merge($shapes, $this->getShapesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries))); + $geometries = array_merge($geometries, $this->getShapeGeometriesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries))); break; case 'MultiPolygon': @@ -418,12 +377,12 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf if (empty($entry->coordinates)) { continue 2; } - $shapes[] = $entry; + $geometries[] = $entry; break; } } - return $shapes; + return $geometries; } /** @@ -435,8 +394,8 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf * @return array * Locations. */ - protected function getLocationsFromGeoJson(string $geoJson): array { - $locations = []; + protected function getLocationGeometriesFromGeoJson(string $geoJson): array { + $geometries = []; $json = json_decode($geoJson); @@ -456,21 +415,21 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf if (empty($entry->features)) { continue 2; } - $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features))); + $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->features) ?: json_encode($entry->features))); break; case 'Feature': if (empty($entry->geometry)) { continue 2; } - $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry))); + $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->geometry) ?: json_encode($entry->geometry))); break; case 'GeometryCollection': if (empty($entry->geometries)) { continue 2; } - $locations = array_merge($locations, $this->getShapesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries))); + $geometries = array_merge($geometries, $this->getLocationGeometriesFromGeoJson(is_string($entry->geometries) ?: json_encode($entry->geometries))); break; case 'MultiPoint': @@ -478,12 +437,12 @@ class GeolocationGeometry extends DataProviderBase implements DataProviderInterf if (empty($entry->coordinates)) { continue 2; } - $locations[] = $entry; + $geometries[] = $entry; break; } } - return $locations; + return $geometries; } } diff --git a/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php b/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php index d8d6a153688251e59c9dfa3f63ea527d45f4f027..2049417a0d939d00e0ac19a802ff0869210462aa 100644 --- a/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php +++ b/modules/geolocation_geometry/src/Plugin/views/field/GeoProximityField.php @@ -33,7 +33,8 @@ class GeoProximityField extends ProximityField { // Get a placeholder for this query and save the field_alias for it. // Remove the initial ':' from the placeholder and avoid collision with - // original field name. + // the original field name. + // @phpstan-ignore-next-line $this->field_alias = $query->addField(NULL, $expression, substr($this->placeholder(), 1)); } diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php index 4b7ac0da538bf3c58187d1e43eb27ed19f74bccc..4b2c587f2f12fc676f0d3499a89cdacd7da07e60 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationContains.php @@ -19,7 +19,7 @@ class GeolocationContains extends JoinPluginBase implements JoinPluginInterface * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php index fe520ee60cb001c0e0b0e1e8cb0f944d8310bdea..619aa0e41b98bbdf2673addbc608deea40da885a 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryContains.php @@ -19,7 +19,7 @@ class GeolocationGeometryContains extends JoinPluginBase implements JoinPluginIn * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php index 705121eee019b34e776a26c971479079ddc1714d..9be11817e94fb527856e7f4527d9b23e726ad4af 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryIntersects.php @@ -19,7 +19,7 @@ class GeolocationGeometryIntersects extends JoinPluginBase implements JoinPlugin * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php index e4ec5cca989fa86982a538d06aa632ab23106e39..0f47728c8fa41e191919d78b937215288c6660da 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationGeometryWithin.php @@ -19,7 +19,7 @@ class GeolocationGeometryWithin extends JoinPluginBase implements JoinPluginInte * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php index 2475022862a226ed66282182b15dd8f895c778cb..0a5cca09452e309151b07eb1355bfbfd6febb050 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationIntersects.php @@ -19,7 +19,7 @@ class GeolocationIntersects extends JoinPluginBase implements JoinPluginInterfac * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php index e9740a6248e7a6b2d7269a1edc686077556493f8..67d2b3c63e51ac29381bc216d388d4d0641b19ca 100644 --- a/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php +++ b/modules/geolocation_geometry/src/Plugin/views/join/GeolocationWithin.php @@ -19,7 +19,7 @@ class GeolocationWithin extends JoinPluginBase implements JoinPluginInterface { * * @param \Drupal\Core\Database\Query\SelectInterface $select_query * Select query. - * @param array $table + * @param string|array $table * Table data. * @param \Drupal\views\Plugin\views\query\QueryPluginBase $view_query * View query. diff --git a/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml b/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml index 1cf7efcb354da923c67b82dc8e26a7c76f39f8fe..a77d081bc70c6ddcbe49da03b672cfcd907e8d03 100644 --- a/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml +++ b/modules/geolocation_google_maps/geolocation_google_maps.libraries.yml @@ -2,10 +2,3 @@ geolocation_google_maps.loader: version: 4.x js: js/geolocation-google-maps-loader.js: {} - -widget.google_maps.geojson: - version: 4.x - js: - js/geolocation-geometry-widget-google-maps.js: {} - dependencies: - - geolocation/geolocation.map diff --git a/modules/geolocation_google_maps/js/GoogleShapeLine.js b/modules/geolocation_google_maps/js/GoogleShapeLine.js index f5b7c6aaeb1928365e5974091d42cfeba39d633b..e22b72dfc949acadfc92588d5cb1f455470d23fb 100644 --- a/modules/geolocation_google_maps/js/GoogleShapeLine.js +++ b/modules/geolocation_google_maps/js/GoogleShapeLine.js @@ -4,24 +4,30 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates. /** * @prop {GoogleMaps} map + * + * @mixes GoogleShapeTrait */ export class GoogleShapeLine extends GeolocationShapeLine { constructor(geometry, settings = {}, map) { super(geometry, settings, map); - this.googleShapeTrait = new GoogleShapeTrait(); + Object.assign(this, GoogleShapeTrait); this.googleShapes = []; const line = new google.maps.Polyline({ - path: geometry.points, + path: [ + geometry.coordinates.map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], strokeColor: this.strokeColor, strokeOpacity: this.strokeOpacity, strokeWeight: this.strokeWidth, }); if (this.title) { - this.googleShapeTrait.setTitle(line, this.title, this.map); + this.setTitle(line, this.title, this.map); } line.addListener("click", (event) => { @@ -35,7 +41,7 @@ export class GoogleShapeLine extends GeolocationShapeLine { remove() { this.googleShapes.forEach((googleShape) => { - googleShape.remove(); + googleShape.setMap(); }); super.remove(); diff --git a/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js b/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js index 348c22c6d9da02dc753d621f7968e654e5e0716f..d6b5b1225c112d85ada8c6a33f930b0ee00d630b 100644 --- a/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js +++ b/modules/geolocation_google_maps/js/GoogleShapeMultiLine.js @@ -4,24 +4,30 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates. /** * @prop {GoogleMaps} map + * + * @mixes GoogleShapeTrait */ export class GoogleShapeMultiLine extends GeolocationShapeMultiLine { constructor(geometry, settings = {}, map) { super(geometry, settings, map); - this.googleShapeTrait = new GoogleShapeTrait(); + Object.assign(this, GoogleShapeTrait); this.googleShapes = []; this.geometry.lines.forEach((lineGeometry) => { const line = new google.maps.Polyline({ - path: lineGeometry.points, + path: [ + lineGeometry.coordinates.map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], strokeColor: this.strokeColor, strokeOpacity: this.strokeOpacity, strokeWeight: this.strokeWidth, }); if (this.title) { - this.googleShapeTrait.setTitle(line, this.title, this.map); + this.setTitle(line, this.title, this.map); } line.addListener("click", (event) => { @@ -36,7 +42,7 @@ export class GoogleShapeMultiLine extends GeolocationShapeMultiLine { remove() { this.googleShapes.forEach((googleShape) => { - googleShape.remove(); + googleShape.setMap(); }); super.remove(); diff --git a/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js b/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js index 9d3db0d3b4d38e5f3f0c11314117ad2b19dd8cae..ef3454d618478f8fd35e7048632c9fa772017b16 100644 --- a/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js +++ b/modules/geolocation_google_maps/js/GoogleShapeMultiPolygon.js @@ -4,17 +4,23 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates. /** * @prop {GoogleMaps} map + * + * @mixes GoogleShapeTrait */ export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon { constructor(geometry, settings = {}, map) { super(geometry, settings, map); - this.googleShapeTrait = new GoogleShapeTrait(); + Object.assign(this, GoogleShapeTrait); this.googleShapes = []; - this.geometry.polygons.forEach((polygonGeometry) => { + this.geometry.coordinates.forEach((polygonGeometry) => { const polygon = new google.maps.Polygon({ - paths: polygonGeometry.points, + paths: [ + polygonGeometry.coordinates[0].map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], strokeColor: this.strokeColor, strokeOpacity: this.strokeOpacity, strokeWeight: this.strokeWidth, @@ -22,7 +28,7 @@ export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon { fillOpacity: this.fillOpacity, }); if (this.title) { - this.googleShapeTrait.setTitle(polygon, this.title, this.map); + this.setTitle(polygon, this.title, this.map); } polygon.addListener("click", (event) => { @@ -37,7 +43,7 @@ export class GoogleShapeMultiPolygon extends GeolocationShapeMultiPolygon { remove() { this.googleShapes.forEach((googleShape) => { - googleShape.remove(); + googleShape.setMap(); }); super.remove(); diff --git a/modules/geolocation_google_maps/js/GoogleShapePolygon.js b/modules/geolocation_google_maps/js/GoogleShapePolygon.js index 288c421a42d4000e75a3006577633abf14b99be5..b17d8c919323a781ad7ab1625b818a061046601a 100644 --- a/modules/geolocation_google_maps/js/GoogleShapePolygon.js +++ b/modules/geolocation_google_maps/js/GoogleShapePolygon.js @@ -4,17 +4,24 @@ import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates. /** * @prop {GoogleMaps} map + * @prop {Array} googleShapes + * + * @mixes GoogleShapeTrait */ export class GoogleShapePolygon extends GeolocationShapePolygon { constructor(geometry, settings = {}, map) { super(geometry, settings, map); - this.googleShapeTrait = new GoogleShapeTrait(); + Object.assign(this, GoogleShapeTrait); this.googleShapes = []; const polygon = new google.maps.Polygon({ - paths: geometry.points, + paths: [ + geometry.coordinates[0].map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], strokeColor: this.strokeColor, strokeOpacity: this.strokeOpacity, strokeWeight: this.strokeWidth, @@ -23,7 +30,7 @@ export class GoogleShapePolygon extends GeolocationShapePolygon { }); if (this.title) { - this.googleShapeTrait.setTitle(polygon, this.title, this.map); + this.setTitle(polygon, this.title, this.map); } polygon.addListener("click", (event) => { @@ -37,7 +44,7 @@ export class GoogleShapePolygon extends GeolocationShapePolygon { remove() { this.googleShapes.forEach((googleShape) => { - googleShape.remove(); + googleShape.setMap(); }); super.remove(); diff --git a/modules/geolocation_google_maps/js/GoogleShapeTrait.js b/modules/geolocation_google_maps/js/GoogleShapeTrait.js index fbe695c2e0b4d1b55b7006d6305bae77d539e021..cb1b08fccb97a176783ae318f641a42a92c1465a 100644 --- a/modules/geolocation_google_maps/js/GoogleShapeTrait.js +++ b/modules/geolocation_google_maps/js/GoogleShapeTrait.js @@ -1,4 +1,11 @@ -export class GoogleShapeTrait { +import { GeolocationGeometry } from "../../../js/Base/GeolocationGeometry.js"; + +/** + * Google Shape support functions. + * + * @mixin + */ +export const GoogleShapeTrait = { /** * @param {google.maps.MVCObject} shape * @param {String} title @@ -19,5 +26,46 @@ export class GoogleShapeTrait { google.maps.event.addListener(shape, "mouseout", () => { infoWindow.close(); }); - } -} + }, + + /** + * + * @param {google.maps.Polyline|google.maps.Polygon|google.maps.Rectangle} googleShape + */ + updateByGoogleShape(googleShape) { + if (googleShape instanceof google.maps.Rectangle) { + this.geometry = new GeolocationGeometry("Polygon", [ + [ + [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getNorthEast().lat()], + [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getSouthWest().lat()], + [googleShape.getBounds().getNorthEast().lng(), googleShape.getBounds().getSouthWest().lat()], + [googleShape.getBounds().getNorthEast().lng(), googleShape.getBounds().getNorthEast().lat()], + [googleShape.getBounds().getSouthWest().lng(), googleShape.getBounds().getNorthEast().lat()], + ], + ]); + } else if (googleShape instanceof google.maps.Polyline) { + const coordinates = []; + googleShape.getPath().forEach((coordinate) => { + coordinates.push([coordinate.lng(), coordinate.lat()]); + }); + this.geometry = new GeolocationGeometry("Polyline", coordinates); + } else if (googleShape instanceof google.maps.Polygon) { + const coordinates = []; + googleShape.getPaths().forEach((path) => { + let first = null; + path.forEach((coordinate) => { + if (first === null) { + first = coordinate; + } + coordinates.push([coordinate.lng(), coordinate.lat()]); + }); + coordinates.push([first.lng(), first.lat()]); + }); + this.geometry = new GeolocationGeometry("Polygon", [coordinates]); + } else { + return false; + } + + this.googleShapes.push(googleShape); + }, +}; diff --git a/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js b/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js new file mode 100644 index 0000000000000000000000000000000000000000..e155e51ed5470e109a268c05ffdaffd3e95cb7b4 --- /dev/null +++ b/modules/geolocation_google_maps/js/MapFeature/GoogleGeometryWidgetMapConnector.js @@ -0,0 +1,310 @@ +import { GoogleMapFeature } from "./GoogleMapFeature.js"; +import { GeolocationCoordinates } from "../../../../js/Base/GeolocationCoordinates.js"; + +/** + * @prop {google.maps.drawing.DrawingManager} drawingManager + */ +export default class GoogleGeometryWidgetMapConnector extends GoogleMapFeature { + constructor(settings, map) { + super(settings, map); + + let drawingModes; + + switch (this.settings.field_type.replace("geolocation_geometry_", "")) { + case "multiline": + case "line": + drawingModes = [google.maps.drawing.OverlayType.POLYLINE]; + break; + + case "multipolygon": + case "polygon": + drawingModes = [google.maps.drawing.OverlayType.RECTANGLE, google.maps.drawing.OverlayType.POLYGON]; + break; + + case "multipoint": + case "point": + drawingModes = [google.maps.drawing.OverlayType.MARKER]; + break; + + default: + drawingModes = [google.maps.drawing.OverlayType.MARKER, google.maps.drawing.OverlayType.POLYLINE, google.maps.drawing.OverlayType.POLYGON, google.maps.drawing.OverlayType.RECTANGLE]; + break; + } + + this.drawingManager = new google.maps.drawing.DrawingManager({ + drawingMode: null, + drawingControl: true, + drawingControlOptions: { + position: google.maps.ControlPosition.TOP_CENTER, + drawingModes, + }, + }); + + this.drawingManager.setMap(this.map.googleMap); + } + + onMapReady() { + google.maps.event.addListener(this.drawingManager, "overlaycomplete", (event) => { + let currentElementCount = 0; + let newIndex = 0; + + switch (this.settings.field_type.replace("geolocation_geometry_", "")) { + case "point": + case "multipoint": + this.map.dataLayers.get("default").markers.forEach((element) => { + currentElementCount++; + const currentElementIndex = this.getIndexByShape(element); + if (currentElementIndex === null) { + return; + } + if (currentElementIndex >= newIndex) { + newIndex = currentElementIndex + 1; + } + }); + break; + + default: + this.map.dataLayers.get("default").shapes.forEach((element) => { + currentElementCount++; + const currentElementIndex = this.getIndexByShape(element); + if (currentElementIndex === null) { + return; + } + if (currentElementIndex >= newIndex) { + newIndex = currentElementIndex + 1; + } + }); + } + + if (this.settings.cardinality <= currentElementCount && this.settings.cardinality !== -1) { + const warning = document.createElement("div"); + warning.innerHTML = `<p>${Drupal.t("Maximum number of element reached.")}</p>`; + Drupal.dialog(warning, { + title: Drupal.t("Synchronization"), + }).showModal(); + return; + } + + event.overlay.setEditable(true); + + switch (event.type) { + case google.maps.drawing.OverlayType.MARKER: + break; + + case google.maps.drawing.OverlayType.POLYLINE: + break; + + case google.maps.drawing.OverlayType.POLYGON: + case google.maps.drawing.OverlayType.RECTANGLE: + const polygon = this.map.createShapePolygon({ type: "Polygon", coordinates: [[]] }, {}); + this.setIndexByShape(polygon, newIndex); + polygon.updateByGoogleShape(event.overlay); + + this.map.dataLayers.get("default").shapeAdded(polygon); + break; + } + }); + + return Promise.resolve(); + } + + setWidgetSubscriber(subscriber) { + this.subscriber = subscriber; + } + + /** + * @param {GeolocationShape} shape + * Marker. + * + * @return {int|null} + * Index. + */ + getIndexByShape(shape) { + return Number(shape.wrapper.dataset.geolocationWidgetIndex ?? 0); + } + + /** + * @param {int} index + * Index. + * + * @return {GeolocationShape|null} + * Shape. + */ + getShapeByIndex(index) { + let returnValue = null; + this.map.dataLayers.get("default").shapes.forEach((shape) => { + if (index === this.getIndexByShape(shape)) { + returnValue = shape; + } + }); + + return returnValue; + } + + /** + * @param {GeolocationShape} shape + * Marker. + * @param {int|false} index + * Index. + */ + setIndexByShape(shape, index = false) { + if (index === false) { + delete shape.wrapper.dataset.geolocationWidgetIndex; + } else { + shape.wrapper.dataset.geolocationWidgetIndex = index.toString(); + } + } + + /** + * @param {Number} index + * Index. + * + * @return {String} + * Title. + */ + getShapeTitle(index) { + return `${index + 1}`; + } + + addShapeSilently(index, geometry) { + let shape; + + switch (geometry.type) { + case "Point": + const coordinates = new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0]); + const marker = this.map.createMarker(coordinates, { + title: this.getMarkerTitle(index, coordinates), + label: index + 1, + draggable: true, + }); + + this.setIndexByMarker(marker, index); + + marker.geolocationWidgetIgnore = true; + this.map.dataLayers.get("default").markerAdded(marker); + delete marker.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return marker; + + case "LineString": + shape = this.map.createShapeLine(geometry, { + title: this.getShapeTitle(index), + label: index + 1, + draggable: true, + }); + break; + + case "Polygon": + shape = this.map.createShapePolygon(geometry, { + title: this.getShapeTitle(index), + label: index + 1, + draggable: true, + }); + break; + } + + if (!shape) return; + + this.setIndexByShape(shape, index); + + shape.geolocationWidgetIgnore = true; + this.map.dataLayers.get("default").shapeAdded(shape); + delete shape.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return shape; + } + + reorderSilently(newOrder) { + this.map.dataLayers.get("default").markers.forEach((marker) => { + const oldIndex = this.getIndexByMarker(marker); + const newIndex = newOrder.indexOf(oldIndex); + + marker.geolocationWidgetIgnore = true; + marker.update(null, { + title: this.getShapeTitle(newIndex), + label: newIndex + 1, + }); + delete marker.geolocationWidgetIgnore; + + this.setIndexByMarker(marker, newIndex); + }); + } + + updateShapeSilently(index, geometry, settings = null) { + const shape = this.getShapeByIndex(index); + + if (!shape) return this.addShapeSilently(index, geometry); + + if (shape.geometry === geometry && !settings) { + return; + } + + shape.geolocationWidgetIgnore = true; + shape.update(geometry, settings ?? {}); + delete shape.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return shape; + } + + removeShapeSilently(index) { + const shape = this.getShapeByIndex(index); + + shape.geolocationWidgetIgnore = true; + shape.remove(); + + this.map.fitMapToElements(); + } + + onShapeAdded(shape) { + super.onShapeAdded(shape); + + shape.googleShapes.forEach((googleShape) => { + if (!googleShape.getEditable()) { + googleShape.setEditable(true); + } + }); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryAdded(shape.geometry, this.getIndexByShape(shape) ?? 0); + } + + onShapeClicked(shape, coordinates) { + super.onShapeClicked(shape, coordinates); + let editing = false; + shape.googleShapes.forEach((googleShape) => { + if (googleShape.getEditable()) { + editing = true; + } + }); + + if (editing) { + return; + } + + // Will trigger onShapeRemove and notify broker. + shape.remove(); + } + + onShapeUpdated(shape) { + super.onShapeUpdated(shape); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryAltered(shape.geometry, this.getIndexByShape(shape)); + } + + onShapeRemove(shape) { + super.onShapeRemove(shape); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryRemoved(this.getIndexByShape(shape)); + } +} diff --git a/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js b/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js index 7e290d51dab7cd5f1bfa217d735a4bc0fd18e367..d8fa2288b30973d1e96fb255e139140db39cd157 100644 --- a/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js +++ b/modules/geolocation_google_maps/js/MapProvider/GoogleMaps.js @@ -265,18 +265,30 @@ export default class GoogleMaps extends GeolocationMapBase { return new GoogleCircle(center, radius, this, settings); } + /** + * @return {GoogleShapeLine} + */ createShapeLine(geometry, settings) { return new GoogleShapeLine(geometry, settings, this); } + /** + * @return {GoogleShapePolygon} + */ createShapePolygon(geometry, settings) { return new GoogleShapePolygon(geometry, settings, this); } + /** + * @return {GoogleShapeMultiLine} + */ createShapeMultiLine(geometry, settings) { return new GoogleShapeMultiLine(geometry, settings, this); } + /** + * @return {GoogleShapeMultiPolygon} + */ createShapeMultiPolygon(geometry, settings) { return new GoogleShapeMultiPolygon(geometry, settings, this); } diff --git a/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js b/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js deleted file mode 100644 index eebfd33160bad6ec5f77b261eeca0e7a454955d0..0000000000000000000000000000000000000000 --- a/modules/geolocation_google_maps/js/geolocation-geometry-widget-google-maps.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @file - * Javascript for the geolocation geometry Google Maps widget. - */ - -/** - * @typedef {Object} GoogleGeojsonData - * - * @property {Object[]} features - */ - -(function (Drupal) { - /** - * Google maps GeoJSON widget. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Widget. - */ - Drupal.behaviors.geolocationGeometryWidgetGoogleMaps = { - attach: (context) => { - context.querySelectorAll(".geolocation-geometry-widget-google-maps-geojson").forEach((item) => { - if (item.classList.contains("processed")) { - return; - } - item.classList.add("processed"); - - const mapWrapper = item.querySelector(".geolocation-geometry-widget-google-maps-geojson-map"); - const inputWrapper = item.querySelector(".geolocation-geometry-widget-google-maps-geojson-input"); - const geometryType = item.getAttribute("data-geometry-type"); - - Drupal.geolocation.maps.getMap(mapWrapper.getAttribute("id")).then( - /** @param {GoogleMaps} map */ (map) => { - let availableControls = []; - switch (geometryType) { - case "polygon": - case "multipolygon": - availableControls = ["Polygon"]; - break; - - case "polyline": - case "multipolyline": - availableControls = ["LineString"]; - break; - - case "point": - case "multipoint": - availableControls = ["Point"]; - break; - - default: - availableControls = ["Point", "LineString", "Polygon"]; - break; - } - - map.googleMap.data.setControls(availableControls); - map.googleMap.data.setControlPosition(google.maps.ControlPosition.TOP_CENTER); - map.googleMap.data.setStyle({ - editable: true, - draggable: true, - }); - - if (inputWrapper.value) { - try { - const geometry = JSON.parse(inputWrapper.value); - map.googleMap.data.addGeoJson({ - type: "FeatureCollection", - features: [ - { - type: "Feature", - id: "value", - geometry, - }, - ], - }); - } catch (error) { - console.error(error.message); - return; - } - - const bounds = new google.maps.LatLngBounds(); - map.googleMap.data.forEach(function (feature) { - feature.getGeometry().forEachLatLng(function (latlng) { - bounds.extend(latlng); - }); - }); - map.setBoundaries(map.normalizeBoundaries(bounds)); - } - - function refreshGeoJsonFromData() { - map.googleMap.data.toGeoJson( - /** @param {GoogleGeojsonData} geoJson */ (geoJson) => { - if (typeof geoJson.features === "undefined") { - inputWrapper.value = ""; - } - - switch (geoJson.features.length) { - case 0: - inputWrapper.value = ""; - break; - - case 1: - inputWrapper.value = JSON.stringify(geoJson.features[0].geometry); - break; - - default: { - const types = { - multi_polygon: "MultiPolygon", - multi_polyline: "MultiPolyline", - multi_point: "MultiPoint", - default: "GeometryCollection", - }; - - const geometry = { - type: types[geometryType] || types.default, - geometries: [], - }; - - geoJson.features.forEach(function (feature) { - geometry.geometries.push(feature.geometry); - }); - inputWrapper.value = JSON.stringify(geometry); - break; - } - } - } - ); - } - - function bindDataLayerListeners(dataLayer) { - dataLayer.addListener("addfeature", refreshGeoJsonFromData); - dataLayer.addListener("removefeature", refreshGeoJsonFromData); - dataLayer.addListener("setgeometry", refreshGeoJsonFromData); - - map.googleMap.data.addListener("click", function (event) { - const newPolyPoints = []; - - event.feature.getGeometry().forEachLatLng(function (latlng) { - if (!(latlng.lat() === event.latLng.lat() && latlng.lng() === event.latLng.lng())) { - newPolyPoints.push(latlng); - } - }); - - if (newPolyPoints.length < 2) { - dataLayer.remove(event.feature); - } else { - event.feature.setGeometry(new google.maps.Data.Polygon([new google.maps.Data.LinearRing(newPolyPoints)])); - } - }); - } - - bindDataLayerListeners(map.googleMap.data); - - inputWrapper.addEventListener("change", () => { - const newData = new google.maps.Data({ - map: map.googleMap, - style: map.googleMap.data.getStyle(), - controls: availableControls, - }); - try { - newData.addGeoJson(JSON.parse(inputWrapper.value)); - } catch (error) { - newData.setMap(null); - return; - } - // No error means GeoJSON was valid! - map.googleMap.data.setMap(null); - map.googleMap.data = newData; - bindDataLayerListeners(newData); - }); - } - ); - }); - }, - detach: () => {}, - }; -})(Drupal); diff --git a/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php b/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php index 3d8dedf0b396060d8cdc9a42d52e1df770355baa..584ad6f2ccaff01cc7ddceaa7ad4a13a097a6862 100644 --- a/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php +++ b/modules/geolocation_google_maps/modules/geolocation_google_places_api/src/Plugin/geolocation/Geocoder/GooglePlacesAPI.php @@ -27,6 +27,7 @@ class GooglePlacesAPI extends GoogleGeocoderBase { * {@inheritdoc} */ public function alterRenderArray(array &$render_array, string $identifier): ?array { + // @phpstan-ignore-next-line $render_array = parent::alterRenderArray($render_array, $identifier); $render_array['#attached'] = BubbleableMetadata::mergeAttachments( diff --git a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php deleted file mode 100644 index 729d5d45a4a62b8be8edd11c8e18a308792a9ba8..0000000000000000000000000000000000000000 --- a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetGoogleMaps.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -namespace Drupal\geolocation_google_maps\Plugin\Field\FieldWidget; - -use Drupal\Core\Field\Attribute\FieldWidget; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationGeometryWidgetBase; - -/** - * Plugin implementation of 'geolocation_geometry_widget_google_maps' widget. - */ -#[FieldWidget( - id: 'geolocation_geometry_widget_google_maps', - label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Google Maps API - GeoJSON'), - field_types: [ - 'geolocation_geometry_point', - 'geolocation_geometry_multi_point', - 'geolocation_geometry_linestring', - 'geolocation_geometry_multi_linestring', - 'geolocation_geometry_polygon', - 'geolocation_geometry_multi_polygon', - 'geolocation_geometry_geometry', - 'geolocation_geometry_multi_geometry', - ] -)] -class GeolocationGeometryWidgetGoogleMaps extends GeolocationGeometryWidgetBase { - - /** - * {@inheritdoc} - */ - protected string $mapProviderId = 'google_maps'; - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { - $element = parent::formElement($items, $delta, $element, $form, $form_state); - - $element['#attached'] = [ - 'library' => [ - 'geolocation_google_maps/widget.google_maps.geojson', - ], - ]; - - return $element; - } - -} diff --git a/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php new file mode 100644 index 0000000000000000000000000000000000000000..8bdd9188a4b90f2190034a10412fe93a7d8fb362 --- /dev/null +++ b/modules/geolocation_google_maps/src/Plugin/Field/FieldWidget/GoogleGeolocationGeometry.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\geolocation_google_maps\Plugin\Field\FieldWidget; + +use Drupal\Core\Field\Attribute\FieldWidget; +use Drupal\geolocation_geometry\Plugin\Field\FieldWidget\GeolocationGeometryMapWidget; + +/** + * Plugin implementation of 'geolocation_geometry_widget_google' widget. + */ +#[FieldWidget( + id: 'geolocation_geometry_widget_google', + label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Google - GeoJSON'), + field_types: [ + 'geolocation_geometry_point', + 'geolocation_geometry_multi_point', + 'geolocation_geometry_linestring', + 'geolocation_geometry_polygon', + ] +)] +class GoogleGeolocationGeometry extends GeolocationGeometryMapWidget { + + /** + * {@inheritdoc} + */ + protected ?string $mapProviderId = 'google_maps'; + + /** + * {@inheritdoc} + */ + public function getWidgetFeatureId(): string { + return 'google_maps_geometry_widget_map_connector'; + } + +} diff --git a/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php b/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php new file mode 100644 index 0000000000000000000000000000000000000000..02c8993ac896fb8dc596eddc198f2ec26426eecf --- /dev/null +++ b/modules/geolocation_google_maps/src/Plugin/geolocation/MapFeature/GoogleGeometryWidgetMapConnector.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\geolocation_google_maps\Plugin\geolocation\MapFeature; + +use Drupal\geolocation\Attribute\MapFeature; +use Drupal\geolocation\Plugin\geolocation\MapFeature\GeolocationFieldWidgetMapConnector; + +/** + * Provides Recenter control element. + */ +#[MapFeature( + id: 'google_maps_geometry_widget_map_connector', + name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Leaflet Geometry Widget Map Connector'), + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'), + type: 'all', + hidden: TRUE, +)] +class GoogleGeometryWidgetMapConnector extends GeolocationFieldWidgetMapConnector {} diff --git a/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php b/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php index 037954493adcd2c1e314136ce312463399e74fc2..e3220b2f5df9d9b2a08c1c991859e678acb01288 100644 --- a/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php +++ b/modules/geolocation_gpx/src/Plugin/geolocation/DataProvider/GeolocationGpxFieldDataProvider.php @@ -195,7 +195,7 @@ class GeolocationGpxFieldDataProvider extends DataProviderBase implements DataPr } $shapes[] = [ - '#type' => 'geolocation_map_geometry', + '#type' => 'geolocation_map_shape', '#geometry' => $geometry, '#title' => $track->entity->name?->value ?? $gpx->name->value, '#stroke_color' => $settings['track_stroke_color_randomize'] ? sprintf('#%06X', mt_rand(0, 0xFFFFFF)) : $settings['track_stroke_color'], @@ -217,7 +217,7 @@ class GeolocationGpxFieldDataProvider extends DataProviderBase implements DataPr } $shapes[] = [ - '#type' => 'geolocation_map_geometry', + '#type' => 'geolocation_map_shape', '#geometry' => $geometry, '#title' => $route->entity->name->toString(), '#stroke_color' => $settings['track_stroke_color_randomize'] ? sprintf('#%06X', mt_rand(0, 0xFFFFFF)) : $settings['track_stroke_color'], diff --git a/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml b/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml deleted file mode 100644 index a7dabc064879b21531c32f49c74c54c9463043ce..0000000000000000000000000000000000000000 --- a/modules/geolocation_leaflet/geolocation_leaflet.libraries.yml +++ /dev/null @@ -1,6 +0,0 @@ -widget.leaflet.geojson: - version: 4.x - js: - js/geolocation-geometry-widget-leaflet.js: {} - dependencies: - - geolocation/geolocation.map diff --git a/modules/geolocation_leaflet/js/LeafletShapeLine.js b/modules/geolocation_leaflet/js/LeafletShapeLine.js index 11fee36e439aae4b449c4824dbb6039e128089e6..069f748ee141c2401d314fd550d5bf2e3029a800 100644 --- a/modules/geolocation_leaflet/js/LeafletShapeLine.js +++ b/modules/geolocation_leaflet/js/LeafletShapeLine.js @@ -1,4 +1,5 @@ import { GeolocationShapeLine } from "../../../js/Base/GeolocationShapeLine.js"; +import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js"; /** * @prop {Leaflet} map @@ -9,20 +10,46 @@ export class LeafletShapeLine extends GeolocationShapeLine { this.leafletShapes = []; - const line = L.polyline(geometry.points, { - color: settings.strokeColor, - opacity: this.strokeOpacity, - weight: this.strokeWidth, - }); + this.addShape(); + } + + addShape() { + const line = L.polyline( + [ + this.geometry.coordinates.map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], + { + color: this.strokeColor, + opacity: this.strokeOpacity, + weight: this.strokeWidth, + } + ); + if (this.title) { line.bindTooltip(this.title); } + line.on("click", (event) => { + this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng)); + }); + line.addTo(this.map.leafletMap); this.leafletShapes.push(line); } + update(geometry, settings) { + super.update(geometry, settings); + + this.leafletShapes.forEach((leafletShape) => { + leafletShape.remove(); + }); + + this.addShape(); + } + remove() { this.leafletShapes.forEach((leafletShape) => { leafletShape.remove(); diff --git a/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js b/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js index fc5fc978dde94cb3ebab6d18bf8bca15be9332a4..c4d23d01c52008f09211faecf58db259a8503bd9 100644 --- a/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js +++ b/modules/geolocation_leaflet/js/LeafletShapeMultiLine.js @@ -1,4 +1,5 @@ import { GeolocationShapeMultiLine } from "../../../js/Base/GeolocationShapeMultiLine.js"; +import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js"; /** * @prop {Leaflet} map @@ -8,21 +9,47 @@ export class LeafletShapeMultiLine extends GeolocationShapeMultiLine { super(geometry, settings, map); this.leafletShapes = []; + } + + addShape() { this.geometry.lines.forEach((lineGeometry) => { - const line = L.polyline(lineGeometry.points, { - color: this.strokeColor, - opacity: this.strokeOpacity, - weight: this.strokeWidth, - }); + const line = L.polyline( + [ + lineGeometry.coordinates.map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], + { + color: this.strokeColor, + opacity: this.strokeOpacity, + weight: this.strokeWidth, + } + ); + if (this.title) { line.bindTooltip(this.title); } + + line.on("click", (event) => { + this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng)); + }); + line.addTo(this.map.leafletMap); this.leafletShapes.push(line); }); } + update(geometry, settings) { + super.update(geometry, settings); + + this.leafletShapes.forEach((leafletShape) => { + leafletShape.remove(); + }); + + this.addShape(); + } + remove() { this.leafletShapes.forEach((leafletShape) => { leafletShape.remove(); diff --git a/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js b/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js index b14daced91323970258adf6cff6d7a89344a8774..f6ac531cf18b9f581e5504a068a5455b0b5a43e0 100644 --- a/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js +++ b/modules/geolocation_leaflet/js/LeafletShapeMultiPolygon.js @@ -1,4 +1,5 @@ import { GeolocationShapeMultiPolygon } from "../../../js/Base/GeolocationShapeMultiPolygon.js"; +import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js"; /** * @prop {Leaflet} map @@ -8,24 +9,53 @@ export class LeafletShapeMultiPolygon extends GeolocationShapeMultiPolygon { super(geometry, settings, map); this.leafletShapes = []; - this.geometry.polygons.forEach((polygonGeometry) => { - const polygon = L.polygon(polygonGeometry.points, { - color: this.strokeColor, - opacity: this.strokeOpacity, - weight: this.strokeWidth, - fillColor: this.fillColor, - fillOpacity: this.fillOpacity, - fill: this.fillOpacity > 0, - }); + + this.addShape(); + } + + addShape() { + this.geometry.forEach((polygonGeometry) => { + const polygon = L.polygon( + [ + polygonGeometry.coordinates[0].map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], + { + color: this.strokeColor, + opacity: this.strokeOpacity, + weight: this.strokeWidth, + fillColor: this.fillColor, + fillOpacity: this.fillOpacity, + fill: this.fillOpacity > 0, + } + ); + polygon.parent = this; + if (this.title) { polygon.bindTooltip(this.title); } + + polygon.on("click", (event) => { + this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng)); + }); + polygon.addTo(this.map.leafletMap); this.leafletShapes.push(polygon); }); } + update(geometry, settings) { + super.update(geometry, settings); + + this.leafletShapes.forEach((leafletShape) => { + leafletShape.remove(); + }); + + this.addShape(); + } + remove() { this.leafletShapes.forEach((leafletShape) => { leafletShape.remove(); diff --git a/modules/geolocation_leaflet/js/LeafletShapePolygon.js b/modules/geolocation_leaflet/js/LeafletShapePolygon.js index c272baa59792406899f61fa1eeaf81f841f36b5e..a2867b0d3ab4a0685f38aeb2967e9d25e9d381d8 100644 --- a/modules/geolocation_leaflet/js/LeafletShapePolygon.js +++ b/modules/geolocation_leaflet/js/LeafletShapePolygon.js @@ -1,4 +1,5 @@ import { GeolocationShapePolygon } from "../../../js/Base/GeolocationShapePolygon.js"; +import { GeolocationCoordinates } from "../../../js/Base/GeolocationCoordinates.js"; /** * @prop {Leaflet} map @@ -8,23 +9,51 @@ export class LeafletShapePolygon extends GeolocationShapePolygon { super(geometry, settings, map); this.leafletShapes = []; - const polygon = L.polyline(geometry.points, { - color: this.strokeColor, - opacity: this.strokeOpacity, - weight: this.strokeWidth, - fillColor: this.fillColor, - fillOpacity: this.fillOpacity, - fill: this.fillOpacity > 0, - }); + + this.addShape(); + } + + addShape() { + const polygon = L.polygon( + [ + this.geometry.coordinates[0].map((value) => { + return { lat: value[1], lng: value[0] }; + }), + ], + { + color: this.strokeColor, + opacity: this.strokeOpacity, + weight: this.strokeWidth, + fillColor: this.fillColor, + fillOpacity: this.fillOpacity, + fill: this.fillOpacity > 0, + editable: this.settings.editable ?? false, + } + ); + + polygon.parent = this; + if (this.title) { polygon.bindTooltip(this.title); } - polygon.addTo(this.map.leafletMap); + polygon.on("click", (event) => { + this.click(new GeolocationCoordinates(event.latlng.lat, event.latlng.lng)); + }); this.leafletShapes.push(polygon); } + update(geometry, settings) { + super.update(geometry, settings); + + this.leafletShapes.forEach((leafletShape) => { + leafletShape.remove(); + }); + + this.addShape(); + } + remove() { this.leafletShapes.forEach((leafletShape) => { leafletShape.remove(); diff --git a/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js b/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js new file mode 100644 index 0000000000000000000000000000000000000000..3f71e8417dea811593912ae1ff18396bb88ddc62 --- /dev/null +++ b/modules/geolocation_leaflet/js/MapFeature/LeafletGeometryWidgetMapConnector.js @@ -0,0 +1,362 @@ +import { LeafletMapFeature } from "./LeafletMapFeature.js"; +import { GeolocationCoordinates } from "../../../../js/Base/GeolocationCoordinates.js"; + +export default class LeafletGeometryWidgetMapConnector extends LeafletMapFeature { + constructor(settings, map) { + super(settings, map); + + const drawTypes = { + polyline: false, + polygon: false, + rectangle: false, + circle: false, + circlemarker: false, + marker: false, + }; + + switch (this.settings.field_type.replace("geolocation_geometry_", "")) { + case "multiline": + case "line": + drawTypes.polyline = true; + break; + case "multipolygon": + case "polygon": + drawTypes.polygon = true; + break; + case "multipoint": + case "point": + drawTypes.marker = true; + break; + } + + this.drawnItemsGroup = L.featureGroup().addTo(this.map.leafletMap); + + const drawControl = new L.Control.Draw({ + draw: drawTypes, + edit: { + featureGroup: this.drawnItemsGroup, + }, + }); + this.map.leafletMap.addControl(drawControl); + } + + onMapReady() { + super.onMapReady(); + + this.map.dataLayers.get("default").shapes.forEach((shape) => { + shape.leafletShapes.forEach((leafletShape) => { + const index = this.getIndexByShape(shape); + + let editableShape; + switch (shape.geometry.type) { + case "Point": + editableShape = L.marker(leafletShape.getLatLng()); + break; + case "LineString": + editableShape = L.polyline(leafletShape.getLatLngs()); + break; + case "Polygon": + editableShape = L.polygon(leafletShape.getLatLngs()); + break; + } + + editableShape.widgetIndex = index; + editableShape.bindTooltip((index + 1).toString()); + this.drawnItemsGroup.addLayer(editableShape); + }); + }); + + this.map.leafletMap.on( + L.Draw.Event.CREATED, + /** @param {DrawEvents.Created} event */ (event) => { + let currentElementCount = 0; + this.drawnItemsGroup.eachLayer(() => { + currentElementCount++; + }); + + let newIndex = 0; + if (this.settings.cardinality > currentElementCount || this.settings.cardinality === -1) { + this.drawnItemsGroup.eachLayer( + /** + * @param {int} element.widgetIndex + * */ + (element) => { + if (typeof element.widgetIndex === "undefined") { + return; + } + if (element.widgetIndex >= newIndex) { + newIndex = element.widgetIndex + 1; + } + } + ); + } else { + const warning = document.createElement("div"); + warning.innerHTML = `<p>${Drupal.t("Maximum number of element reached.")}</p>`; + Drupal.dialog(warning, { + title: Drupal.t("Synchronization"), + }).showModal(); + return; + } + + switch (event.layerType) { + case "marker": + const geometry = event.layer.toGeoJSON().geometry; + const marker = this.createMarker(new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0])); + this.setIndexByMarker(marker, newIndex); + this.map.dataLayers.get("default").markerAdded(marker); + break; + + case "polyline": + const polyline = this.map.createShapeLine(event.layer.toGeoJSON().geometry); + this.setIndexByShape(polyline, newIndex); + this.map.dataLayers.get("default").shapeAdded(polyline); + break; + + case "rectangle": + case "polygon": + const polygon = this.map.createShapePolygon(event.layer.toGeoJSON().geometry); + this.setIndexByShape(polygon, newIndex); + this.map.dataLayers.get("default").shapeAdded(polygon); + break; + } + + event.layer.widgetIndex = newIndex; + this.drawnItemsGroup.addLayer(event.layer); + } + ); + + this.map.leafletMap.on( + L.Draw.Event.EDITVERTEX, + /** + * @param {DrawEvents.EditVertex} event + */ + (event) => { + if (typeof event.poly === "undefined") { + return console.error("Updated poly could not be identified."); + } + + /** + * @type {Polyline | Polygon} + * + * @prop {int} widgetIndex + */ + const poly = event.poly; + + if (typeof poly.widgetIndex === "undefined") { + return console.error("Updated poly could not be associated to shape."); + } + + const shape = this.getShapeByIndex(poly.widgetIndex); + shape.update(poly.toGeoJSON().geometry); + this.map.dataLayers.get("default").shapeUpdated(shape); + } + ); + + this.map.leafletMap.on( + L.Draw.Event.DELETED, + /** + * @param {DrawEvents.Deleted} event + */ + (event) => { + event.layers.eachLayer( + /** + * @param {Layer} layer + * @param {int} layer.widgetIndex + */ + (layer) => { + if (typeof layer.widgetIndex === "undefined") { + return console.error("Updated layer could not be associated to shape."); + } + const shape = this.getShapeByIndex(layer.widgetIndex); + shape.remove(); + this.map.dataLayers.get("default").shapeRemoved(shape); + } + ); + } + ); + } + + setWidgetSubscriber(subscriber) { + this.subscriber = subscriber; + } + + /** + * @param {GeolocationShape} shape + * Marker. + * + * @return {int|null} + * Index. + */ + getIndexByShape(shape) { + return Number(shape.wrapper.dataset.geolocationWidgetIndex ?? 0); + } + + /** + * @param {int} index + * Index. + * + * @return {GeolocationShape|null} + * Shape. + */ + getShapeByIndex(index) { + let returnValue = null; + this.map.dataLayers.get("default").shapes.forEach((shape) => { + if (index === this.getIndexByShape(shape)) { + returnValue = shape; + } + }); + + return returnValue; + } + + /** + * @param {GeolocationShape} shape + * Marker. + * @param {int|false} index + * Index. + */ + setIndexByShape(shape, index = false) { + if (index === false) { + delete shape.wrapper.dataset.geolocationWidgetIndex; + } else { + shape.wrapper.dataset.geolocationWidgetIndex = index.toString(); + } + } + + /** + * @param {Number} index + * Index. + * + * @return {String} + * Title. + */ + getShapeTitle(index) { + return `${index + 1}`; + } + + addShapeSilently(index, geometry) { + let shape; + + switch (geometry.type) { + case "Point": + const coordinates = new GeolocationCoordinates(geometry.coordinates[1], geometry.coordinates[0]); + const marker = this.map.createMarker(coordinates, { + title: this.getMarkerTitle(index, coordinates), + label: index + 1, + draggable: true, + }); + + this.setIndexByMarker(marker, index); + + marker.geolocationWidgetIgnore = true; + this.map.dataLayers.get("default").markerAdded(marker); + delete marker.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return marker; + + case "LineString": + shape = this.map.createShapeLine(geometry, { + title: this.getShapeTitle(index), + label: index + 1, + draggable: true, + }); + break; + + case "Polygon": + shape = this.map.createShapePolygon(geometry, { + title: this.getShapeTitle(index), + label: index + 1, + draggable: true, + }); + break; + } + + if (!shape) return; + + this.setIndexByShape(shape, index); + + shape.geolocationWidgetIgnore = true; + this.map.dataLayers.get("default").shapeAdded(shape); + delete shape.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return shape; + } + + reorderSilently(newOrder) { + this.map.dataLayers.get("default").markers.forEach((marker) => { + const oldIndex = this.getIndexByMarker(marker); + const newIndex = newOrder.indexOf(oldIndex); + + marker.geolocationWidgetIgnore = true; + marker.update(null, { + title: this.getShapeTitle(newIndex), + label: newIndex + 1, + }); + delete marker.geolocationWidgetIgnore; + + this.setIndexByMarker(marker, newIndex); + }); + } + + updateShapeSilently(index, geometry, settings = null) { + const shape = this.getShapeByIndex(index); + + if (!shape) return this.addShapeSilently(index, geometry); + + if (shape.geometry === geometry && !settings) { + return; + } + + shape.geolocationWidgetIgnore = true; + shape.update(geometry, settings ?? {}); + delete shape.geolocationWidgetIgnore; + + this.map.fitMapToElements(); + + return shape; + } + + removeShapeSilently(index) { + const shape = this.getShapeByIndex(index); + + shape.geolocationWidgetIgnore = true; + shape.remove(); + + this.map.fitMapToElements(); + } + + onShapeAdded(shape) { + super.onShapeAdded(shape); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryAdded(shape.geometry, this.getIndexByShape(shape) ?? 0); + } + + onShapeClicked(shape, coordinates) { + super.onShapeClicked(shape, coordinates); + + // Will trigger onShapeRemove and notify broker. + shape.remove(); + } + + onShapeUpdated(shape) { + super.onShapeUpdated(shape); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryAltered(shape.geometry, this.getIndexByShape(shape)); + } + + onShapeRemove(shape) { + super.onShapeRemove(shape); + + if (shape.geolocationWidgetIgnore ?? false) return; + + this.subscriber?.geometryRemoved(this.getIndexByShape(shape)); + } +} diff --git a/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js b/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js index e89ebb952195756f9c2be5eecdef510e2aeff344..a61cdcfe3f1899a68ccb2c346885e81eab952a80 100644 --- a/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js +++ b/modules/geolocation_leaflet/js/MapFeature/LeafletMapFeature.js @@ -1,5 +1,8 @@ import { GeolocationMapFeature } from "../../../../js/MapFeature/GeolocationMapFeature.js"; +/** + * @prop {L.Map} map.leafletMap + */ export class LeafletMapFeature extends GeolocationMapFeature { /** * @param {LeafletMapMarker} marker diff --git a/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js b/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js deleted file mode 100644 index eb7b1abb0f78a1898f7ae33277340f7d1c7da796..0000000000000000000000000000000000000000 --- a/modules/geolocation_leaflet/js/geolocation-geometry-widget-leaflet.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file - * Javascript for the geolocation geometry Leaflet widget. - */ - -(function (Drupal) { - /** - * Leaflet GeoJSON widget. - * - * @type {Drupal~behavior} - * - * @prop {Function} layerToGeoJson - * - * @prop {Drupal~behaviorAttach} attach - * Widget. - */ - Drupal.behaviors.geolocationGeometryWidgetLeaflet = { - /** - * @param {String} geometryType - */ - getDrawSettingsByTyp: (geometryType) => { - switch (geometryType) { - case "polygon": - case "multipolygon": - return { - polyline: false, - marker: false, - circlemarker: false, - }; - - case "polyline": - case "multipolyline": - return { - polygon: false, - rectangle: false, - circle: false, - marker: false, - circlemarker: false, - }; - - case "point": - case "multipoint": - return { - polyline: false, - polygon: false, - rectangle: false, - circle: false, - circlemarker: false, - }; - - default: - return { - circlemarker: false, - }; - } - }, - /** - * @param {GeoJSON} layer - * @param {String} geometryType - */ - layerToGeoJson: (layer, geometryType) => { - const featureCollection = layer.toGeoJSON(); - - switch (featureCollection.features.length) { - case 0: - return JSON.stringify(""); - - case 1: - return JSON.stringify(featureCollection.features[0].geometry); - - default: { - const types = { - multipolygon: "MultiPolygon", - multipolyline: "MultiPolyline", - multipoint: "MultiPoint", - default: "GeometryCollection", - }; - - const geometryCollection = { - type: types[geometryType] || types.default, - geometries: [], - }; - - featureCollection.features.forEach((feature) => { - geometryCollection.geometries.push(feature.geometry); - }); - - return JSON.stringify(geometryCollection); - } - } - }, - attach: (context) => { - context.querySelectorAll(".geolocation-geometry-widget-leaflet-geojson").forEach((item) => { - if (item.classList.contains("processed")) { - return; - } - item.classList.add("processed"); - - const mapWrapper = item.querySelector(".geolocation-geometry-widget-leaflet-geojson-map"); - const inputWrapper = item.querySelector(".geolocation-geometry-widget-leaflet-geojson-input"); - const geometryType = item.getAttribute("data-geometry-type"); - - Drupal.geolocation.maps.getMap(mapWrapper.getAttribute("id")).then( - /** @param {Leaflet} map */ (map) => { - Drupal.geolocation.addStylesheet("https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css"); - Drupal.geolocation.addScript("https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js").then(() => { - const geoJsonLayer = L.geoJSON().addTo(map.leafletMap); - const drawControl = new L.Control.Draw({ - draw: this.getDrawSettingsByTyp(geometryType), - edit: { - featureGroup: geoJsonLayer, - }, - }); - map.leafletMap.addControl(drawControl); - - map.leafletMap.on( - L.Draw.Event.CREATED, - /** @param {Created} event */ (event) => { - geoJsonLayer.addLayer(event.layer); - inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType); - } - ); - map.leafletMap.on(L.Draw.Event.EDITED, () => { - inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType); - }); - map.leafletMap.on(L.Draw.Event.DELETED, () => { - inputWrapper.value = this.layerToGeoJson(geoJsonLayer, geometryType); - }); - - if (inputWrapper.value) { - try { - geoJsonLayer.addData(JSON.parse(inputWrapper.value)); - } catch (error) { - console.error(error.message); - return; - } - - map.setBoundaries(map.normalizeBoundaries(geoJsonLayer.getBounds())); - } - }); - } - ); - }); - }, - detach: () => {}, - }; -})(Drupal); diff --git a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php deleted file mode 100644 index 4c1eb383473cc9e41e4077d768c0b95111011ccb..0000000000000000000000000000000000000000 --- a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetLeaflet.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -namespace Drupal\geolocation_leaflet\Plugin\Field\FieldWidget; - -use Drupal\Core\Field\Attribute\FieldWidget; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\geolocation\Plugin\Field\FieldWidget\GeolocationGeometryWidgetBase; - -/** - * Plugin implementation of 'geolocation_geometry_widget_leaflet' widget. - */ -#[FieldWidget( - id: 'geolocation_geometry_widget_leaflet', - label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Leaflet - GeoJSON'), - field_types: [ - 'geolocation_geometry_point', - 'geolocation_geometry_multi_point', - 'geolocation_geometry_linestring', - 'geolocation_geometry_multi_linestring', - 'geolocation_geometry_polygon', - 'geolocation_geometry_multi_polygon', - 'geolocation_geometry_geometry', - 'geolocation_geometry_multi_geometry', - ] -)] -class GeolocationGeometryWidgetLeaflet extends GeolocationGeometryWidgetBase { - - /** - * {@inheritdoc} - */ - protected string $mapProviderId = 'leaflet'; - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { - $element = parent::formElement($items, $delta, $element, $form, $form_state); - - $element['#attached'] = [ - 'library' => [ - 'geolocation_leaflet/widget.leaflet.geojson', - ], - ]; - - return $element; - } - -} diff --git a/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php new file mode 100644 index 0000000000000000000000000000000000000000..0408b7c12935ecacc53c024c196da248b5ad4d27 --- /dev/null +++ b/modules/geolocation_leaflet/src/Plugin/Field/FieldWidget/LeafletGeolocationGeometry.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\geolocation_leaflet\Plugin\Field\FieldWidget; + +use Drupal\Core\Field\Attribute\FieldWidget; +use Drupal\geolocation_geometry\Plugin\Field\FieldWidget\GeolocationGeometryMapWidget; + +/** + * Plugin implementation of 'geolocation_geometry_widget_leaflet' widget. + */ +#[FieldWidget( + id: 'geolocation_geometry_widget_leaflet', + label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Geometry Leaflet - GeoJSON'), + field_types: [ + 'geolocation_geometry_point', + 'geolocation_geometry_multi_point', + 'geolocation_geometry_linestring', + 'geolocation_geometry_polygon', + ] +)] +class LeafletGeolocationGeometry extends GeolocationGeometryMapWidget { + + /** + * {@inheritdoc} + */ + protected ?string $mapProviderId = 'leaflet'; + + /** + * {@inheritdoc} + */ + public function getWidgetFeatureId(): string { + return 'leaflet_geometry_widget_map_connector'; + } + +} diff --git a/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php b/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php new file mode 100644 index 0000000000000000000000000000000000000000..f88eefd857722aa64c1cbee4ab2f708bc39bd1f0 --- /dev/null +++ b/modules/geolocation_leaflet/src/Plugin/geolocation/MapFeature/LeafletGeometryWidgetMapConnector.php @@ -0,0 +1,34 @@ +<?php + +namespace Drupal\geolocation_leaflet\Plugin\geolocation\MapFeature; + +use Drupal\geolocation\Attribute\MapFeature; +use Drupal\geolocation\Plugin\geolocation\MapFeature\GeolocationFieldWidgetMapConnector; + +/** + * Provides Recenter control element. + */ +#[MapFeature( + id: 'leaflet_geometry_widget_map_connector', + name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Leaflet Geometry Widget Map Connector'), + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'), + type: 'all', + hidden: TRUE, +)] +class LeafletGeometryWidgetMapConnector extends GeolocationFieldWidgetMapConnector { + + /** + * {@inheritdoc} + */ + protected array $scripts = [ + 'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js', + ]; + + /** + * {@inheritdoc} + */ + protected array $stylesheets = [ + 'https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css', + ]; + +} diff --git a/package.json b/package.json index 6ec0832617b44c1a35356e4f0287db7908f1c486..899df71d3e497f415c8f9e9928fa61d593f1be4f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "devDependencies": { "@types/bmapgl": "^0.0.7", - "@types/chart.js": "0.0.15", + "@types/chart.js": "^0.0.15", "@types/google.maps": "^3.50", "@types/leaflet": "^1.9.0", "@types/leaflet.markercluster": "^1.5.4", - "@types/leaflet-draw": "1.0.11", + "@types/leaflet-draw": "^1.0.11", "@yandex/ymaps3-types": "^0.0.24", "prettier": "^2.8.1" }, diff --git a/phpstan.neon b/phpstan.neon index 946ccc2740f1bbb23b2b910f06f04467bd1e3a13..ac59f3caaaf0858f9c3559973dc166916a32a5c0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -29,6 +29,4 @@ parameters: - '#Unsafe usage of new static\(\)#' - '#\\Drupal calls should be avoided in classes, use dependency injection instead#' - '#no value type specified in iterable type#' - - "#with generic interface Drupal\\\\Core\\\\Field\\\\FieldItemListInterface but does not specify its types#" - - "#Call to an undefined method Drupal\\\\Tests\\\\WebAssert::waitForElement#" - - "#Call to an undefined method Drupal\\\\Tests\\\\WebAssert::assertWaitOnAjaxRequest#" + - "#but does not specify its types#" diff --git a/src/Attribute/LayerFeature.php b/src/Attribute/LayerFeature.php index 2be671986b6c5e6728a6f69f5ee435ff16e6b0c5..c5dc41ad111f98a1fdea5a4e81126b9553d2a636 100644 --- a/src/Attribute/LayerFeature.php +++ b/src/Attribute/LayerFeature.php @@ -21,6 +21,7 @@ class LayerFeature extends Plugin { public readonly ?TranslatableMarkup $name = NULL, public readonly ?TranslatableMarkup $description = NULL, public readonly ?string $type = NULL, + public readonly bool $hidden = FALSE, public readonly ?string $deriver = NULL, ) {} diff --git a/src/Attribute/MapFeature.php b/src/Attribute/MapFeature.php index e39b0a22fa5323a94a2e26d8ff4b82da6632ed8e..4907456cb142e6f39aaebdd991e5d70b0506388b 100644 --- a/src/Attribute/MapFeature.php +++ b/src/Attribute/MapFeature.php @@ -21,6 +21,7 @@ class MapFeature extends Plugin { public readonly ?TranslatableMarkup $name = NULL, public readonly ?TranslatableMarkup $description = NULL, public readonly ?string $type = NULL, + public readonly bool $hidden = FALSE, public readonly ?string $deriver = NULL, ) {} diff --git a/src/DataProviderBase.php b/src/DataProviderBase.php index 391661a26a1eef6cc544a19903ed7eacf153d163..0e217a36d1d282a44a713a40f339ce4ddf021395 100644 --- a/src/DataProviderBase.php +++ b/src/DataProviderBase.php @@ -123,10 +123,7 @@ abstract class DataProviderBase extends PluginBase implements DataProviderInterf $element['token_items'][] = $item; } - if ( - $this->moduleHandler->moduleExists('token') - && method_exists($fieldDefinition, 'getTargetEntityTypeId') - ) { + if ($this->moduleHandler->moduleExists('token')) { // Add the token UI from the token module if present. $element['token_help'] = [ '#theme' => 'token_tree_link', diff --git a/src/Element/GeolocationMapGeometry.php b/src/Element/GeolocationMapShape.php similarity index 84% rename from src/Element/GeolocationMapGeometry.php rename to src/Element/GeolocationMapShape.php index 4573d6528dbddf524f5d17ce9a3feb2b8c8001d4..17aad62711ae0c39b18889a87730eda309710664 100644 --- a/src/Element/GeolocationMapGeometry.php +++ b/src/Element/GeolocationMapShape.php @@ -13,7 +13,7 @@ use Drupal\Core\Template\Attribute; * * @code * $form['map'] = [ - * '#type' => 'geolocation_map_geometry', + * '#type' => 'geolocation_map_shape', * '#geometry' => [[[1,1],[2,2],[3,3]], [[4,4],[5,5],[6,6]]], * '#id' => NULL, * '#stroke_color' => NULL, @@ -24,9 +24,9 @@ use Drupal\Core\Template\Attribute; * ]; * @endcode * - * @RenderElement("geolocation_map_geometry") + * @RenderElement("geolocation_map_shape") */ -class GeolocationMapGeometry extends RenderElementBase { +class GeolocationMapShape extends RenderElementBase { /** * {@inheritdoc} @@ -40,7 +40,7 @@ class GeolocationMapGeometry extends RenderElementBase { ], '#pre_render' => [ [$class, 'preRenderGroup'], - [$this, 'preRenderGeolocationGeometry'], + [$this, 'preRenderGeolocationShape'], ], '#title' => NULL, '#geometry' => NULL, @@ -63,14 +63,15 @@ class GeolocationMapGeometry extends RenderElementBase { * @return array * Renderable map. */ - public function preRenderGeolocationGeometry(array $render_array): array { - $render_array['#theme'] = 'geolocation_map_geometry'; + public function preRenderGeolocationShape(array $render_array): array { + $render_array['#theme'] = 'geolocation_map_shape'; $render_array['#attributes'] = new Attribute($render_array['#attributes'] ?? []); $render_array['#attributes']->addClass('geolocation-geometry'); $render_array['#attributes']->addClass('js-hide'); $render_array['#attributes']->setAttribute('id', $render_array['#id'] ?? uniqid('geometry-')); + $render_array['#attributes']->setAttribute('data-geometry-type', $render_array['#geometry_type'] ?? ''); $render_array['#attributes']->setAttribute('data-stroke-color', $render_array['#stroke_color'] ?? '#0000FF'); $render_array['#attributes']->setAttribute('data-stroke-width', $render_array['#stroke_width'] ?? 2); $render_array['#attributes']->setAttribute('data-stroke-opacity', $render_array['#stroke_opacity'] ?? 1); diff --git a/src/LayerFeatureManager.php b/src/LayerFeatureManager.php index c55cc4bcf1b104f11c8d6e059531ff6fea154572..d74d9c25cf4a92472dc999ac6b85dadfa936bef3 100644 --- a/src/LayerFeatureManager.php +++ b/src/LayerFeatureManager.php @@ -26,7 +26,7 @@ class LayerFeatureManager extends DefaultPluginManager { use DependencySerializationTrait; /** - * Constructs an LayerFeatureManager object. + * Constructs a LayerFeatureManager object. * * @param \Traversable $namespaces * An object that implements \Traversable which contains the root paths @@ -128,7 +128,7 @@ class LayerFeatureManager extends DefaultPluginManager { $layer_features_form = [ '#type' => 'table', '#weight' => 100, - '#caption' => $this->t('<p>Select features to alter functionality of this layer.</p>'), + '#caption' => $this->t('<p>Select features to alter the functionality of this layer.</p>'), '#header' => [ $this->t('Enable'), $this->t('Feature'), @@ -153,6 +153,10 @@ class LayerFeatureManager extends DefaultPluginManager { continue; } + if ($feature->getPluginDefinition()['hidden'] ?? FALSE) { + continue; + } + $feature_enable_id = Html::getUniqueId($feature_id . '_enabled'); $weight = $settings[$feature_id]['weight'] ?? 0; @@ -242,8 +246,7 @@ class LayerFeatureManager extends DefaultPluginManager { continue; } - $feature = $this->getLayerFeature($feature_id); - if ($feature && method_exists($feature, 'validateSettingsForm')) { + if ($feature = $this->getLayerFeature($feature_id)) { $feature_parents = $parents; array_push($feature_parents, $feature_id, 'settings'); $feature->validateSettingsForm(empty($feature_settings['settings']) ? [] : $feature_settings['settings'], $form_state, $feature_parents); diff --git a/src/MapFeatureManager.php b/src/MapFeatureManager.php index 72c24591cadea3e78b848949bfb52f070526549f..9d02654a49009e5bc5e8e7608a3de610c406dfcf 100644 --- a/src/MapFeatureManager.php +++ b/src/MapFeatureManager.php @@ -26,7 +26,7 @@ class MapFeatureManager extends DefaultPluginManager { use DependencySerializationTrait; /** - * Constructs an MapFeatureManager object. + * Constructs a MapFeatureManager object. * * @param \Traversable $namespaces * An object that implements \Traversable which contains the root paths @@ -77,13 +77,9 @@ class MapFeatureManager extends DefaultPluginManager { * Map feature list. */ public function getMapFeaturesByMapType(string $type): array { - $definitions = $this->getDefinitions(); - $list = []; - foreach ($definitions as $id => $definition) { - if ($definition['type'] == $type || $definition['type'] == 'all') { - $list[$id] = $definition; - } - } + $list = array_filter($this->getDefinitions(), function ($definition) use ($type) { + return $definition['type'] == $type || $definition['type'] == 'all'; + }); uasort($list, [self::class, 'sortByName']); @@ -153,6 +149,10 @@ class MapFeatureManager extends DefaultPluginManager { continue; } + if ($feature->getPluginDefinition()['hidden'] ?? FALSE) { + continue; + } + $feature_enable_id = Html::getUniqueId($feature_id . '_enabled'); $weight = $settings[$feature_id]['weight'] ?? 0; @@ -246,8 +246,7 @@ class MapFeatureManager extends DefaultPluginManager { continue; } - $feature = $this->getMapFeature($feature_id); - if ($feature && method_exists($feature, 'validateSettingsForm')) { + if ($feature = $this->getMapFeature($feature_id)) { $feature_parents = $parents; array_push($feature_parents, $feature_id, 'settings'); $feature->validateSettingsForm($feature_settings['settings'] ?? [], $form_state, $feature_parents); diff --git a/src/MapProviderInterface.php b/src/MapProviderInterface.php index 0814308b746eeedd9d0a88428621aa5d39a9fea9..5427cdc606932d085bfc5886ceced672e7c8f387 100644 --- a/src/MapProviderInterface.php +++ b/src/MapProviderInterface.php @@ -40,7 +40,7 @@ interface MapProviderInterface extends PluginInspectionInterface { public function getSettingsSummary(array $settings): array; /** - * Provide a generic map settings form array. + * Provide the generic map settings form array. * * @param array $settings * The current map settings. diff --git a/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php b/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php deleted file mode 100644 index 151b99c4dd22a3086a1445a7a8d7d09562c1f6b9..0000000000000000000000000000000000000000 --- a/src/Plugin/Field/FieldWidget/GeolocationGeometryWidgetBase.php +++ /dev/null @@ -1,150 +0,0 @@ -<?php - -namespace Drupal\geolocation\Plugin\Field\FieldWidget; - -use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Field\WidgetBase; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\geolocation\MapProviderInterface; -use Drupal\geolocation\MapProviderManager; -use Drupal\views\FieldAPIHandlerTrait; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Map geometry widget base. - */ -abstract class GeolocationGeometryWidgetBase extends WidgetBase implements ContainerFactoryPluginInterface { - - use FieldAPIHandlerTrait; - - /** - * Map provider ID. - * - * @var string - */ - protected string $mapProviderId; - - /** - * Map provider. - * - * @var \Drupal\geolocation\MapProviderInterface - */ - protected MapProviderInterface $mapProvider; - - /** - * {@inheritdoc} - */ - public function __construct( - $plugin_id, - $plugin_definition, - FieldDefinitionInterface $field_definition, - array $settings, - array $third_party_settings, - protected MapProviderManager $mapProviderManager, - ) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); - - if ($this->mapProviderId) { - $this->mapProvider = $this->mapProviderManager->getMapProvider($this->mapProviderId); - } - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): GeolocationGeometryWidgetBase { - return new static( - $plugin_id, - $plugin_definition, - $configuration['field_definition'], - $configuration['settings'], - $configuration['third_party_settings'], - $container->get('plugin.manager.geolocation.mapprovider'), - ); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state): array { - $settings = $this->getSettings(); - $element = parent::settingsForm($form, $form_state); - - $parents = [ - 'fields', - $this->fieldDefinition->getName(), - 'settings_edit_form', - 'settings', - ]; - - $user_input = $form_state->getUserInput(); - $map_provider_settings = NestedArray::getValue($user_input, array_merge($parents, ['map_provider_settings'])) ?? $settings['map_provider_settings'] ?? []; - $map_provider_settings = NestedArray::mergeDeep($this->mapProviderManager->getMapProviderDefaultSettings($this->mapProviderId) ?? [], $map_provider_settings); - - $element['map_provider_settings'] = $this->mapProvider->getSettingsForm( - $map_provider_settings, - array_merge($parents, ['map_provider_settings']) - ); - - return $element; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary(): array { - $summary = []; - $settings = $this->getSettings(); - - return array_replace_recursive($summary, $this->mapProvider->getSettingsSummary($settings['map_provider_settings'] ?? [])); - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { - - $settings = $this->getSettings(); - - $element['#type'] = 'container'; - $element['#attributes'] = [ - 'data-geometry-type' => str_replace('geolocation_geometry_', '', $this->fieldDefinition->getType()), - 'class' => [ - str_replace('_', '-', $this->getPluginId()) . '-geojson', - ], - ]; - - $element['geojson'] = [ - '#type' => 'textarea', - '#title' => $this->t('GeoJSON'), - '#default_value' => $items[$delta]->geojson ?? NULL, - '#empty_value' => '', - '#required' => $element['#required'], - '#attributes' => [ - 'class' => [ - 'geolocation-geometry-widget-geojson-input', - str_replace('_', '-', $this->getPluginId()) . '-geojson-input', - ], - ], - ]; - - $element['map'] = [ - '#type' => 'geolocation_map', - '#maptype' => $this->mapProviderId, - '#weight' => -10, - '#settings' => $settings['map_provider_settings'], - '#context' => ['widget' => $this], - '#attributes' => [ - 'class' => [ - str_replace('_', '-', $this->getPluginId()) . '-geojson-map', - ], - ], - ]; - - return $element; - } - -} diff --git a/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php b/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php index 3152e4ab873de9d9679b07821a5b02acd7dc7afe..09ce6efa07673246a6a1132deaa14faf8a0f7589 100644 --- a/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php +++ b/src/Plugin/Field/FieldWidget/GeolocationMapWidget.php @@ -10,8 +10,11 @@ use Drupal\Core\Render\BubbleableMetadata; /** * Plugin implementation of the 'geolocation_map' widget. */ -#[FieldWidget(id: 'geolocation_map', - label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Map'), field_types: ['geolocation'])] +#[FieldWidget( + id: 'geolocation_map', + label: new \Drupal\Core\StringTranslation\TranslatableMarkup('Geolocation Map'), + field_types: ['geolocation'] +)] class GeolocationMapWidget extends GeolocationMapWidgetBase { /** @@ -66,39 +69,36 @@ class GeolocationMapWidget extends GeolocationMapWidgetBase { public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL): array { $element = parent::form($items, $form, $form_state, $get_delta); - $element['#attached'] = BubbleableMetadata::mergeAttachments( - $element['#attached'] ?? [], - [ - 'drupalSettings' => [ - 'geolocation' => [ - 'widgetSettings' => [ - $element['#attributes']['id'] => [ - 'widgetSubscribers' => [ - 'geolocation_map' => [ - 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldMapWidget.js', - 'settings' => [ - 'mapId' => $element['map']['#id'], - 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), - 'field_name' => $this->fieldDefinition->getName(), - 'featureSettings' => [ - 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/MapFeature/GeolocationFieldWidgetMapConnector.js', - ], - ], + $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], [ + 'drupalSettings' => [ + 'geolocation' => [ + 'widgetSettings' => [ + $element['#attributes']['id'] => [ + 'widgetSubscribers' => [ + 'geolocation_field' => [ + 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldWidget.js', + 'settings' => [ + 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + 'field_name' => $this->fieldDefinition->getName(), + 'field_type' => $this->fieldDefinition->getType(), ], - 'geolocation_field' => [ - 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldWidget.js', - 'settings' => [ - 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), - 'field_name' => $this->fieldDefinition->getName(), - ], + ], + 'geolocation_map' => [ + 'import_path' => base_path() . $this->moduleHandler->getModule('geolocation')->getPath() . '/js/WidgetSubscriber/GeolocationFieldMapWidget.js', + 'settings' => [ + 'mapId' => $element['map']['#id'], + 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + 'field_name' => $this->fieldDefinition->getName(), + 'field_type' => $this->fieldDefinition->getType(), + 'feature_id' => $this->getWidgetFeatureId(), ], ], ], ], ], ], - ] - ); + ], + ]); /** * @var Integer $index @@ -123,16 +123,6 @@ class GeolocationMapWidget extends GeolocationMapWidgetBase { ]; } - $context = [ - 'widget' => $this, - 'form_state' => $form_state, - 'field_definition' => $this->fieldDefinition, - ]; - - if (!$this->isDefaultValueWidget($form_state)) { - $this->moduleHandler->alter('geolocation_field_map_widget', $element, $context); - } - return $element; } diff --git a/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php b/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php index 3c8116988e7e7401589465bce6833a4e0e822935..bca58d3fad85d0c44681571902753f829d048cea 100644 --- a/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php +++ b/src/Plugin/Field/FieldWidget/GeolocationMapWidgetBase.php @@ -18,7 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; /** - * Base class for map based field widgets. + * Base class for map-based field widgets. */ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerFactoryPluginInterface { @@ -284,6 +284,19 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF $element['map_provider_settings']['#title'] .= ' - ' . $this->t('Override Map Default Preset'); } + $element['map']['#settings'] = array_merge($element['map']['#settings'], [ + 'map_features' => [ + $this->getWidgetFeatureId() => [ + 'enabled' => TRUE, + 'settings' => [ + 'field_name' => $this->fieldDefinition->getName(), + 'field_type' => $this->fieldDefinition->getType(), + 'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), + ], + ], + ], + ]); + $element['map'] = $this->mapCenterManager->alterMap($element['map'], $settings['centre']); if ($settings['hide_inputs'] ?? FALSE) { @@ -314,6 +327,15 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF } } } + $context = [ + 'widget' => $this, + 'form_state' => $form_state, + 'field_definition' => $this->fieldDefinition, + ]; + + if (!$this->isDefaultValueWidget($form_state)) { + $this->moduleHandler->alter('geolocation_field_map_widget', $element, $context); + } return $element; } @@ -375,4 +397,14 @@ abstract class GeolocationMapWidgetBase extends WidgetBase implements ContainerF return NULL; } + /** + * Get ID of feature to connect map and widget. + * + * @return string + * Feature ID. + */ + protected function getWidgetFeatureId(): string { + return 'geolocation_field_widget_map_connector'; + } + } diff --git a/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php b/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php index 645d706a10e390fbc9427ede753b5fbcea9a00f6..f6a3ef9ca502a3ee4e7c4e7719a91c7eb7cfd42f 100644 --- a/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php +++ b/src/Plugin/geolocation/LayerFeature/MarkerZoomByAnchor.php @@ -12,7 +12,9 @@ use Drupal\geolocation\MapProviderInterface; */ #[LayerFeature(id: 'marker_zoom_by_anchor', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Marker Zoom By Anchor'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Set a URL anchor.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Set a URL anchor.'), + type: 'all', +)] class MarkerZoomByAnchor extends LayerFeatureBase { /** diff --git a/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php b/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php index 03568dc3da0857d93b41edb8be2e86722618c896..a09e6c05ec6864c54efdf47a0f3d27a9d2ddef83 100644 --- a/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php +++ b/src/Plugin/geolocation/MapFeature/ControlCustomGeocoder.php @@ -15,9 +15,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides Geocoding control element. */ -#[MapFeature(id: 'control_geocoder', +#[MapFeature( + id: 'control_geocoder', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Geocoder'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add address search with geocoding functionality map.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add address search with geocoding functionality map.'), + type: 'all', +)] class ControlCustomGeocoder extends ControlCustomElementBase { /** diff --git a/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php b/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php index 15941da30063fb916c8d06dce414db77d3161260..5b135503cc30e49655078ad6a594c3150fe800da 100644 --- a/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php +++ b/src/Plugin/geolocation/MapFeature/ControlCustomRecenter.php @@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface; /** * Provides Recenter control element. */ -#[MapFeature(id: 'control_recenter', +#[MapFeature( + id: 'control_recenter', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Recenter'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add button to recenter map.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Add button to recenter map.'), + type: 'all', +)] class ControlCustomRecenter extends ControlCustomElementBase { /** diff --git a/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php b/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php index 6dab8222f0d14485790e569eec207c2bd7943188..868f0440fd2d9a7ac712af900792cb6faeafb0ed 100644 --- a/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php +++ b/src/Plugin/geolocation/MapFeature/ControlLoadingIndicator.php @@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface; /** * Provides Recenter control element. */ -#[MapFeature(id: 'control_loading_indicator', +#[MapFeature( + id: 'control_loading_indicator', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Loading Indicator'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('When using an interactive map, shows a loading icon and label if there is currently data fetched from the backend via AJAX.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('When using an interactive map, shows a loading icon and label if there is currently data fetched from the backend via AJAX.'), + type: 'all', +)] class ControlLoadingIndicator extends ControlCustomElementBase { /** diff --git a/src/Plugin/geolocation/MapFeature/ControlTileLayers.php b/src/Plugin/geolocation/MapFeature/ControlTileLayers.php index e5b8ae7388c4b46785e4632b2ebd9ed2e4203fc8..d6f7dff730326accc306d6ce37f299255b160e8d 100644 --- a/src/Plugin/geolocation/MapFeature/ControlTileLayers.php +++ b/src/Plugin/geolocation/MapFeature/ControlTileLayers.php @@ -15,9 +15,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides Recenter control element. */ -#[MapFeature(id: 'control_tile_layers', +#[MapFeature( + id: 'control_tile_layers', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - Tile Layers'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Shows list of toggleable tile layers.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Shows list of toggleable tile layers.'), + type: 'all', +)] class ControlTileLayers extends ControlCustomElementBase { /** diff --git a/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php b/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php index 0f93a0a746d94bafc7673ba3bf5a3ce0d29a11cf..749fad9e8cf533acd0b68fe5cb4034ef50098ed8 100644 --- a/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php +++ b/src/Plugin/geolocation/MapFeature/ControlViewFullscreen.php @@ -8,9 +8,12 @@ use Drupal\geolocation\MapProviderInterface; /** * Provides Recenter control element. */ -#[MapFeature(id: 'control_view_fullscreen', +#[MapFeature( + id: 'control_view_fullscreen', name: new \Drupal\Core\StringTranslation\TranslatableMarkup('Map Control - View Fullscreen'), - description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Trigger Fullscreen on entire View container.'), type: 'all')] + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Trigger Fullscreen on entire View container.'), + type: 'all', +)] class ControlViewFullscreen extends ControlCustomElementBase { /** diff --git a/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php b/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php new file mode 100644 index 0000000000000000000000000000000000000000..2d08837fa639a105e28716a74a474782080ca4f6 --- /dev/null +++ b/src/Plugin/geolocation/MapFeature/GeolocationFieldWidgetMapConnector.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\geolocation\Plugin\geolocation\MapFeature; + +use Drupal\geolocation\Attribute\MapFeature; +use Drupal\geolocation\MapFeatureBase; + +/** + * Provides Recenter control element. + */ +#[MapFeature( + id: 'geolocation_field_widget_map_connector', + name: new \Drupal\Core\StringTranslation\TranslatableMarkup('GeolocationFieldWidgetMapConnector'), + description: new \Drupal\Core\StringTranslation\TranslatableMarkup('Internal use.'), + type: 'all', + hidden: TRUE, +)] +class GeolocationFieldWidgetMapConnector extends MapFeatureBase {} diff --git a/src/Plugin/views/argument/ProximityArgument.php b/src/Plugin/views/argument/ProximityArgument.php index b56f23e560cb3e47c744a3818792a31f7d01ab34..a883e4cfba38e61516be0491336397412c22e8ae 100644 --- a/src/Plugin/views/argument/ProximityArgument.php +++ b/src/Plugin/views/argument/ProximityArgument.php @@ -88,6 +88,7 @@ class ProximityArgument extends Formula { // The addWhere function is only available for SQL queries. if ($this->query instanceof Sql) { + // @phpstan-ignore-next-line $this->query->addWhere(0, $formula, $placeholders, 'formula'); } } diff --git a/src/Plugin/views/field/ProximityField.php b/src/Plugin/views/field/ProximityField.php index be2ba12f4f669a570ddb1f3eeed6bd8578a5c61f..d62f54494257f676dd19db8a2c19ea60a58bd16e 100644 --- a/src/Plugin/views/field/ProximityField.php +++ b/src/Plugin/views/field/ProximityField.php @@ -112,7 +112,8 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter // Get a placeholder for this query and save the field_alias for it. // Remove the initial ':' from the placeholder and avoid collision with - // original field name. + // the original field name. + // @phpstan-ignore-next-line $this->field_alias = $query->addField(NULL, $expression, substr($this->placeholder(), 1)); } diff --git a/src/Plugin/views/sort/ProximitySort.php b/src/Plugin/views/sort/ProximitySort.php index d627769e98868b14f4a9fb014d101707c76f5ed6..a22fc069270af7197b9873dae396d3d4a01e2f1e 100644 --- a/src/Plugin/views/sort/ProximitySort.php +++ b/src/Plugin/views/sort/ProximitySort.php @@ -26,6 +26,7 @@ class ProximitySort extends SortPluginBase { $field = $this->displayHandler->getHandler('field', $this->field); if (!empty($field->field_alias) && $field->field_alias != 'unknown') { + // @phpstan-ignore-next-line $this->query->addOrderBy(NULL, NULL, $this->options['order'], $field->field_alias); if (!empty($field->tableAlias)) { $this->tableAlias = $field->tableAlias; diff --git a/templates/geolocation-map-geometry.html.twig b/templates/geolocation-map-geometry.html.twig deleted file mode 100644 index fa9fe81e9f4b4eea9c6d4c7bf110526d5c56394c..0000000000000000000000000000000000000000 --- a/templates/geolocation-map-geometry.html.twig +++ /dev/null @@ -1,62 +0,0 @@ -<div {{ attributes.addClass('geolocation-geometry') }} typeof="Place"> - - <span class="geometry" data-type="{{ geometry.type }}"> - {% if geometry.type == 'point' %} - <span property="geo" typeof="GeoCoordinates"> - <meta property="latitude" content="{{ geometry.lat }}" /> - <meta property="longitude" content="{{ geometry.lng }}" /> - </span> - {% elseif geometry.type == 'line' %} - <span property="geo" typeof="GeoShape"> - <meta property="line" content=" - {%- for point in geometry.points %} - {{- point.lat ~ ',' ~ point.lng ~ ' ' -}} - {% endfor -%} - "> - </span> - {% elseif geometry.type == 'polygon' %} - <span property="geo" typeof="GeoShape"> - <meta property="polygon" content=" - {%- for point in geometry.points %} - {{- point.lat ~ ',' ~ point.lng ~ ' ' -}} - {% endfor -%} - "> - </span> - {% elseif geometry.type == 'multipoint' %} - {% for point in geometry.points %} - <span property="geo" typeof="GeoCoordinates"> - <meta property="latitude" content="{{ point.lat }}" /> - <meta property="longitude" content="{{ point.lng }}" /> - </span> - {% endfor %} - {% elseif geometry.type == 'multiline' %} - {% for line in geometry.lines %} - <span property="geo" typeof="GeoShape"> - <meta property="line" content=" - {%- for point in line.points %} - {{- point.lat ~ ',' ~ point.lng ~ ' ' -}} - {% endfor -%} - "> - </span> - {% endfor %} - {% elseif geometry.type == 'multipolygon' %} - {% for polygon in geometry.polygons %} - <span property="geo" typeof="GeoShape"> - <meta property="polygon" content=" - {%- for point in polygon.points %} - {{- point.lat ~ ',' ~ point.lng ~ ' ' -}} - {% endfor -%} - "> - </span> - {% endfor %} - {% endif %} - </span> - - {% if title is not empty %} - <h2 class="title" property="name">{{ title }}</h2> - {% endif %} - - {% if children is not empty %} - <div class="content">{{ children }}</div> - {% endif %} -</div> diff --git a/templates/geolocation-map-shape.html.twig b/templates/geolocation-map-shape.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..6234abbc50643d7a46b57737c8da9242031442e9 --- /dev/null +++ b/templates/geolocation-map-shape.html.twig @@ -0,0 +1,14 @@ +<div {{ attributes.addClass('geolocation-geometry') }} typeof="Place"> + + <script class="geometry" type="application/json"> + {{ geometry|raw }} + </script> + + {% if title is not empty %} + <h2 class="title" property="name">{{ title }}</h2> + {% endif %} + + {% if children is not empty %} + <div class="content">{{ children }}</div> + {% endif %} +</div> diff --git a/tests/src/Kernel/GeolocationItemTest.php b/tests/src/Kernel/GeolocationItemTest.php index e8789ed766f35372da82759b4fedcef319e023cb..222cfae70789fb83b2ab157fe6e8f6091b6e120d 100644 --- a/tests/src/Kernel/GeolocationItemTest.php +++ b/tests/src/Kernel/GeolocationItemTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\geolocation\Kernel; use Drupal\Core\Field\FieldItemInterface; -use Drupal\Core\Field\FieldItemListInterface; use Drupal\Tests\field\Kernel\FieldKernelTestBase; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -65,7 +64,6 @@ class GeolocationItemTest extends FieldKernelTestBase { /** @var \Drupal\entity_test\Entity\EntityTest $entity */ $entity = $entityTestStorage->load($id); - $this->assertInstanceOf(FieldItemListInterface::class, $entity->get('field_test'), 'Field implements interface.'); $this->assertInstanceOf(FieldItemInterface::class, $entity->get('field_test')[0], 'Field item implements interface.'); /** @var \Drupal\geolocation\GeolocationItemListInterface $field_item */